"""장소 신원 검사기 — **좌표로만** 잇고, 접힌 산출은 잡고, 갈라진 산출은 통과시킨다.

★양성 대조는 실제 run 69e821758f3d 의 산출 모양(location 하나 `골목` 이 셋을 덮음)을 그대로
합성한다. 이름 비교는 없다 — production 행 이름은 아무 값이어도 된다.
"""
from __future__ import annotations

import pytest

from tools.grounding_audit import location_identity_check as lic


def _fx():
    from tools.grounding_audit import canary_bootstrap as cbs
    return cbs.load_fixture("period_episode")


def _segments(fx):
    """production `scene_save.segments` 모양 — 씬 제목을 앞에 붙인 글."""
    segs = []
    for i, s in enumerate(fx.segments(), 1):
        text = f"{s['heading']}\n\n{s['text']}"
        segs.append({"scene_index": i, "text": text})
    return segs


def _span(segs, scene, word, nth=1):
    text = segs[scene - 1]["text"]
    pos = -1
    for _ in range(nth):
        pos = text.find(word, pos + 1)
        assert pos >= 0
    return {"segment_id": f"scene-{scene}", "start": pos, "end": pos + len(word)}


def _row(short_id, name, spans):
    return {"short_id": short_id, "name": name,
            "grounding_provenance": {"occurrences": [{"source_span": sp} for sp in spans]}}


def _outputs(segs, locations, parts, primary, part_parent, deps):
    return {
        "scene_save": {"data": {"segments": segs}},
        "entity_merge": {"data": {"locations": locations, "location_parts": parts}},
        "scene_director": {"data": {"scenes": [{"scene_index": k, "primary_location": v} for k, v in primary.items()]}},
        "reference_acquisition": {"data": {"rows": [
            {"ledger_row": {"owner_type": "location_part", "final_id": k, "parent_final_id": v}}
            for k, v in part_parent.items()]}},
        "shot_dependency": {"data": {"dependencies": deps}},
    }


def test_the_collapsed_run_is_caught():
    fx = _fx(); segs = _segments(fx)
    locs = [_row("L01", "아무 이름", [_span(segs, 1, "골목"), _span(segs, 4, "골목")])]
    parts = [_row("LP01", "x", [_span(segs, 1, "미닫이문")]), _row("LP03", "y", [_span(segs, 2, "종이")])]
    out = _outputs(segs, locs, parts, {1: "L01", 2: "L01", 3: "L01", 4: "L01"},
                   {"LP01": None, "LP03": None},
                   [{"scene_index": 2, "shot_index": 1, "location_refs": [{"scene_index": 1, "shot_index": 1}]}])
    got = lic.check_outputs(out, fx)
    assert got["ok"] is False
    assert any("씬1" in f for f in got["findings"]) and any("씬2" in f for f in got["findings"])


def test_the_distinct_run_passes_and_cross_refs_are_counted():
    fx = _fx(); segs = _segments(fx)
    barber = [_span(segs, 1, "이발소", 2), _span(segs, 3, "이발소"), _span(segs, 4, "이발소")]
    locs = [_row("L01", "a", barber), _row("L02", "b", [_span(segs, 2, "국밥집")]),
            _row("L03", "c", [_span(segs, 1, "골목"), _span(segs, 4, "골목")])]
    parts = [_row("LP01", "x", [_span(segs, 1, "미닫이문")]), _row("LP03", "y", [_span(segs, 2, "종이")])]
    primary = {1: "L01", 2: "L02", 3: "L01", 4: "L03"}
    ok = lic.check_outputs(_outputs(segs, locs, parts, primary, {"LP01": "L01", "LP03": "L02"},
                                    [{"scene_index": 3, "shot_index": 1, "location_refs": [{"scene_index": 1, "shot_index": 2}]}]), fx)
    assert ok["ok"] is True, ok["findings"]
    assert ok["scene_location"] == {"1": "L01", "2": "L02", "3": "L01", "4": "L03"}
    # 같은 산출에서 국밥집 샷이 이발소 샷을 물려받으면 — 그 하나만 어긋난다
    bad = lic.check_outputs(_outputs(segs, locs, parts, primary, {"LP01": "L01", "LP03": "L02"},
                                     [{"scene_index": 2, "shot_index": 4, "location_refs": [{"scene_index": 1, "shot_index": 1}]}]), fx)
    assert bad["ok"] is False and len(bad["cross_location_refs"]) == 1
    wrong_parent = lic.check_outputs(_outputs(segs, locs, parts, primary, {"LP01": "L03", "LP03": "L02"}, []), fx)
    assert any("LP01" in f for f in wrong_parent["findings"])


def test_an_establishment_split_into_front_and_inside_is_not_a_collapse():
    """★판독기가 한 업소를 「앞」과 「안」두 행으로 낼 수 있다 — 서로 다른 참조 세트라 어긋남이
    아니다. 어긋남은 **다른 자리**가 한 ID 를 나눠 갖는 것뿐이다."""
    fx = _fx(); segs = _segments(fx)
    locs = [_row("L01", "a", [_span(segs, 1, "이발소", 1)]), _row("L04", "d", [_span(segs, 3, "이발소")]),
            _row("L03", "b", [_span(segs, 2, "국밥집")]), _row("L02", "c", [_span(segs, 1, "골목"), _span(segs, 4, "골목")])]
    parts = [_row("LP01", "x", [_span(segs, 1, "미닫이문")]), _row("LP03", "y", [_span(segs, 2, "종이")])]
    primary = {1: "L01", 2: "L03", 3: "L04", 4: "L02"}
    got = lic.check_outputs(_outputs(segs, locs, parts, primary, {"LP01": "L01", "LP03": "L03"}, []), fx)
    assert got["ok"] is True, got["findings"]
    assert got["same_place_split"] == {"이발소": ["L01", "L04"]}
    # 같은 산출에서 골목이 이발소 앞과 한 ID 를 나눠 가지면 — 그건 어긋남이다
    shared = [_row("L01", "a", [_span(segs, 1, "이발소", 1), _span(segs, 1, "골목"), _span(segs, 4, "골목")]),
              _row("L04", "d", [_span(segs, 3, "이발소")]), _row("L03", "b", [_span(segs, 2, "국밥집")])]
    bad = lic.check_outputs(_outputs(segs, shared, parts, {1: "L01", 2: "L03", 3: "L04", 4: "L01"}, {"LP01": "L01", "LP03": "L03"}, []), fx)
    assert any("같은 location ID 로 접혔다" in f for f in bad["findings"])


def test_a_scene_whose_place_was_not_cited_there_is_joined_through_the_same_declared_word():
    """★실측: 판독기가 골목을 씬 1 에서만 불렀다(씬 4 제목의 골목은 안 적음). 원고가 씬 1·4 를 같은
    낱말로 선언했으니 씬 4 는 그 행으로 잇되 `covered_via` 에 적는다 — primary 가 다르면 여전히 ✗."""
    fx = _fx(); segs = _segments(fx)
    locs = [_row("L01", "a", [_span(segs, 1, "이발소", 2), _span(segs, 3, "이발소")]), _row("L03", "b", [_span(segs, 2, "국밥집")]),
            _row("L02", "c", [_span(segs, 1, "골목")])]          # 씬 4 언급 없음
    parts = [_row("LP01", "x", [_span(segs, 1, "미닫이문")]), _row("LP03", "y", [_span(segs, 2, "종이")])]
    ok = lic.check_outputs(_outputs(segs, locs, parts, {1: "L01", 2: "L03", 3: "L01", 4: "L02"}, {"LP01": "L01", "LP03": "L03"}, []), fx)
    assert ok["ok"] is True, ok["findings"]
    assert ok["scene_location"]["4"] == "L02" and "4" in ok["covered_via"]
    bad = lic.check_outputs(_outputs(segs, locs, parts, {1: "L01", 2: "L03", 3: "L01", 4: "L01"}, {"LP01": "L01", "LP03": "L03"}, []), fx)
    assert any("씬4 primary_location" in f for f in bad["findings"])


def test_a_missing_anchor_stops_rather_than_passing():
    fx = _fx(); segs = _segments(fx)
    with pytest.raises(AssertionError):
        lic.spans_in_run({"scene_save": {"data": {"segments": segs}}}, 2, "이발소", 0)
