"""유형 사전조사 — **좁히기** 계약의 결정론 유닛 (2026-08-03).

## 무엇이 바뀌었나

전판(`test_typology_prior_axes.py`, 삭제)은 4축(입지·규모·구성·작명)과 그
순서 규칙을 잠갔다. 그런데 **축을 넓힌 것 자체가 결함**이었다 — 8문항 중
7개가 그 유형이면 늘 있는 것이었고, 하나뿐인 특별한 대상은 한 문항에 열
가지를 뭉쳐 물어 "증거 없음"으로 끝났다(2026-08-03 실측). 축을 없애고
당연하지 않은 것만 남긴다.

여기서 다루는 것은 **결정론 영역만**이다 — 상한 / 형태 검증 / 블록 조립.
*무엇이 특별한가*는 유닛으로 증명할 수 없고 코드가 알아서도 안 된다.
"""
from __future__ import annotations

from app.modules.pipeline.typology_prior import (
    MAX_GAP_QUESTIONS,
    TYPOLOGY_PRIOR_VERSION,
    build_gap_schema,
    build_spec_item_lines,
    build_typology_facts_block,
    validate_gap_output,
)


def _q(target: str = "대상", native: str = "질문") -> dict:
    return {"target_native": target, "question_native": native,
            "why_ko": "사유"}


def test_cap_is_two_and_version_moved():
    """"보통 하나, 많아야 둘" — 상한이 계약의 일부다.

    넉넉히 두면 모델이 자리를 채우려고 당연한 것을 다시 끌어온다.
    """
    assert MAX_GAP_QUESTIONS == 2
    assert TYPOLOGY_PRIOR_VERSION == "4"


def test_schema_has_target_and_question_only():
    item = build_gap_schema()["properties"]["questions"]["items"]
    assert set(item["required"]) == {
        "target_native", "question_native", "why_ko"}
    assert build_gap_schema(2)["properties"]["questions"]["maxItems"] == 2


def test_validate_requires_target_and_question():
    assert validate_gap_output({"questions": [_q()]}) == []
    assert any("target_native" in v for v in validate_gap_output(
        {"questions": [{"question_native": "질문"}]}))
    assert any("question_native" in v for v in validate_gap_output(
        {"questions": [{"target_native": "대상"}]}))
    assert any("상한" in v for v in validate_gap_output(
        {"questions": [_q(), _q(), _q()]}, 2))
    assert validate_gap_output({"questions": "nope"}) == [
        "questions 가 배열이 아님"]
    assert validate_gap_output({"questions": []}) == []


def test_region_block_leads_the_input():
    """조사 모델은 문항과 briefing 말고 아무것도 못 본다.

    확정 지역·시대가 입력에 없으면 문항이 "이 지역"으로 나가고, 조사가
    "지역이 명시되지 않았다"로 전건 폐기된다(2026-08-03 실측 2그룹 4건).
    """
    from app.modules.pipeline.typology_prior import build_gap_user_content

    u = build_gap_user_content(
        structure_desc="D", layout_narration_en="", interior_note_en="",
        exterior_note_en="", scene_blocks=["S"], spec_items=[],
        world_facts_block="SAMPLE_FIXTURE_REGION")
    assert u.startswith("REGION AND ERA")
    assert "SAMPLE_FIXTURE_REGION" in u
    # 없으면 그 블록만 빠지고 나머지는 그대로 (구 호출자 보호)
    assert "REGION AND ERA" not in build_gap_user_content(
        structure_desc="D", layout_narration_en="", interior_note_en="",
        exterior_note_en="", scene_blocks=["S"], spec_items=[])


def test_spec_item_lines_carry_the_quote_not_only_the_name():
    """대상 선정의 근거는 명세가 붙인 **이름이 아니라 원문 인용**이다.

    실측: 이름 때문에 사진 속 실물을 "없다"고 판정해 요구된 물건이 통째로
    빠졌다.
    """
    out = build_spec_item_lines([
        {"name_en": "SAMPLE_FIXTURE_thing",
         "evidence": {"quote_ko": "SAMPLE_FIXTURE_원문"}},
        {"name_en": "SAMPLE_FIXTURE_bare"},
        {"name_en": ""},
    ])
    assert "SAMPLE_FIXTURE_원문" in out
    assert out.count("- SAMPLE_FIXTURE_") == 2


def _adopted(target: str, fact: str, strength: str = "strong") -> dict:
    return {"adopted": True, "target_native": target,
            "agreed_fact_en": fact, "strength": strength}


def test_block_labels_lines_with_the_researched_target():
    b = build_typology_facts_block([_adopted("SAMPLE_FIXTURE_대상", "FACT")])
    assert "(SAMPLE_FIXTURE_대상)" in b and "FACT" in b


def test_block_marks_strength_and_orders_corroborated_first():
    b = build_typology_facts_block([
        _adopted("t1", "WEAKFACT", "weak"),
        _adopted("t2", "STRONGFACT"),
    ])
    assert b.index("STRONGFACT") < b.index("WEAKFACT")
    assert "[corroborated]" in b and "[single source]" in b


def test_block_is_empty_when_nothing_adopted():
    assert build_typology_facts_block([]) == ""
    assert build_typology_facts_block(
        [{"adopted": False, "agreed_fact_en": "X"}]) == ""
    # 채택이라 해놓고 사실이 비면 버린다(fail-closed).
    assert build_typology_facts_block([_adopted("t", "")]) == ""


def test_block_ranks_below_screenplay_and_spec():
    b = build_typology_facts_block([_adopted("t", "FACT")]).lower()
    assert "screenplay" in b and "structure spec" in b
