"""Area #6 v1: _apply_scene_fixes 폐기. mutation=0 invariant + diagnostic
emit 검증으로 전환. _build_item_id_map 은 Patch A defensive guard 로 보존."""
import pytest
from unittest.mock import patch

from app.core.errors import AppError
from app.modules.pipeline.t2i_review import _build_item_id_map, run_t2i_review


def _scene_data():
    return {
        "scenes": [
            {"scene_index": 1, "_shot_index": 0, "t2i_variations": [{"t2i_prompt": "old1"}, {"t2i_prompt": "old2"}]},
            {"scene_index": 2, "_shot_index": 0, "t2i_variations": [{"t2i_prompt": "old3"}]},
        ]
    }


def _shot_extract():
    return {
        "scenes": [
            {"scene_index": 1, "shots": [{"shot_index": 0, "description": "shot1 desc"}]},
            {"scene_index": 2, "shots": [{"shot_index": 0, "description": "shot2 desc"}]},
        ]
    }


def test_run_t2i_review_no_mutation_invariant(monkeypatch):
    """Area #6 v1 canary: scene diagnostic emit 시에도 mutation 0 + applied 0 invariant.

    이전 (v0): _apply_scene_fixes 가 t2i_prompt 를 in-place mutate → applied count.
    이후 (v1): mutation 폐기 + diagnostic 누적 only → scene_applied == 0 / scene_applied_indices == [].

    ★2026-08-27: v1 은 기본으로 **씬 갈래를 건너뛴다**(그 산문을 이미지에
     안 쓴다). 그러면 이 시험이 재려는 invariant(「돌아도 안 고친다」)를
     한 번도 안 태우게 되므로 **그 축을 꺼서** 갈래를 돌린다. 계약은
     그대로다 — 바뀐 것은 「언제 도는가」이고 이 시험은 「돌면 어떤가」다.
    """
    from app.core.config import settings
    from app.modules.pipeline import t2i_review as mod

    monkeypatch.setattr(settings, "t2i_review_skip_scene_on_v1", False,
                        raising=False)

    monkeypatch.setattr(mod, "_review_entity_t2i", lambda *a, **kw: [])
    monkeypatch.setattr(
        mod, "_review_scene_detail",
        lambda *a, **kw: [
            {
                "id": "S1_L0_V0",
                "scope": "scene",
                "issue_code": "awkward_translation",
                "action": "regen_required",
                "hint_diagnostic": "원어 표기 권장",
                "item_id": "S1_L0_V0",
            },
        ],
    )

    scene_data = _scene_data()
    result = mod.run_t2i_review(
        entity_t2i_data={"entities": []},
        scene_detail_data=scene_data,
        entity_merge_data={}, entity_detail_data={},
        shot_extract_data=_shot_extract(), shot_staging_data={},
        vwr_data={},
    )

    # mutation=0 invariant
    assert result["scene_applied"] == 0
    assert result["scene_applied_indices"] == []
    assert scene_data["scenes"][0]["t2i_variations"][0]["t2i_prompt"] == "old1"  # untouched
    assert scene_data["scenes"][0]["t2i_variations"][1]["t2i_prompt"] == "old2"  # untouched
    assert scene_data["scenes"][1]["t2i_variations"][0]["t2i_prompt"] == "old3"  # untouched

    # diagnostic 누적
    assert result["scene_fixes"] == 1
    assert len(result["diagnostics"]) == 1
    diag = result["diagnostics"][0]
    assert diag["id"] == "S1_L0_V0"
    assert diag["scope"] == "scene"
    assert diag["issue_code"] == "awkward_translation"
    assert diag["action"] == "regen_required"
    assert diag["hint_diagnostic"] == "원어 표기 권장"


def test_run_t2i_review_empty_diagnostics_when_no_issues(monkeypatch):
    """v1: no-issue 시 diagnostics == [], scene_fixes == 0."""
    from app.modules.pipeline import t2i_review as mod

    monkeypatch.setattr(mod, "_review_entity_t2i", lambda *a, **kw: [])
    monkeypatch.setattr(mod, "_review_scene_detail", lambda *a, **kw: [])

    result = mod.run_t2i_review(
        entity_t2i_data={"entities": []},
        scene_detail_data={"scenes": []},
        entity_merge_data={}, entity_detail_data={},
        shot_extract_data={}, shot_staging_data={},
        vwr_data={},
    )
    assert result["scene_fixes"] == 0
    assert result["entity_fixes"] == 0
    assert result["scene_applied"] == 0
    assert result["entity_applied"] == 0
    assert result["scene_applied_indices"] == []
    assert result["diagnostics"] == []


def test_build_item_id_map_preserved_after_v1():
    """Patch A V3 patch I1 defensive guard: _build_item_id_map 은 mutation 폐기 후에도 보존."""
    scene_data = _scene_data()
    m = _build_item_id_map(scene_data)
    assert m == {
        "S1_L0_V0": (0, 0),
        "S1_L0_V1": (0, 1),
        "S2_L1_V0": (1, 0),
    }


def test_run_t2i_review_returns_v1_keys():
    """v1 contract: 반환 dict 키 = entity_fixes / scene_fixes / entity_applied / scene_applied / scene_applied_indices / diagnostics. legacy 'details' 키 폐기."""
    from app.modules.pipeline import t2i_review as mod
    from unittest.mock import MagicMock

    with patch.object(mod, "_review_entity_t2i", return_value=[]), \
         patch.object(mod, "_review_scene_detail", return_value=[]):
        result = mod.run_t2i_review(
            entity_t2i_data={"entities": []},
            scene_detail_data={"scenes": []},
            entity_merge_data={}, entity_detail_data={},
            shot_extract_data={}, shot_staging_data={},
            vwr_data={},
        )

    expected_keys = {
        "entity_fixes", "scene_fixes",
        "entity_applied", "scene_applied",
        "scene_applied_indices",
        # ★2026-08-27: 건너뛴 것과 「검사했는데 0」을 가르는 칸.
        #  이것이 없으면 `scene_fixes=0` 이 「깨끗하다」로 오독된다.
        "scene_review_status",
        "diagnostics",
    }
    assert set(result.keys()) == expected_keys, (
        f"v1 return dict keys mismatch: {set(result.keys())} != {expected_keys}"
    )
    # legacy keys 폐기 검증
    assert "details" not in result, "legacy 'details' key v1 폐기 위반"


def test_review_scene_detail_alias_equality_enforced(monkeypatch):
    """Codex Round 1 Important #4 fix-up: _review_scene_detail 내부 reader 가 iss['id']
    != iss['item_id'] != result_item_id 시 AppError fail-fast (silent default 금지).

    spec §3 Q8 line 211 strict: alias 는 id 와 동일 값. mismatch 시 schema 위반으로
    diagnostic 폐기 + 운영자 force re-run 의무.
    """
    from app.modules.pipeline import t2i_review as mod
    from app.core.errors import AppError

    monkeypatch.setattr(mod, "load_prompt", lambda *a, **k: "system {t2i_context} {char_names}")
    monkeypatch.setattr(mod, "load_schema", lambda *a, **k: {})

    def _fake_mismatch(**kwargs):
        return {
            "results": [
                {
                    "item_id": "S1_L0_V0",
                    "scene_index": 1,
                    "var_index": 0,
                    "has_issues": True,
                    "issues": [
                        {
                            "id": "S99_DIFFERENT",  # mismatch with result item_id
                            "scope": "scene",
                            "issue_code": "awkward_translation",
                            "action": "regen_required",
                            "hint_diagnostic": "x",
                            "item_id": "S1_L0_V0",
                        },
                    ],
                },
            ],
        }

    with patch.object(mod, "call_structured", side_effect=_fake_mismatch):
        with pytest.raises(AppError) as exc_info:
            mod._review_scene_detail(
                scene_detail_data={
                    "scenes": [
                        {"scene_index": 1, "_shot_index": 0,
                         "t2i_variations": [{"t2i_prompt": "old"}]},
                    ]
                },
                shot_map={1: [{"shot_index": 0, "description": "d"}]},
                staging_map={},
                t2i_context="",
                char_names_str="",
                opik_metadata=None,
            )

    assert exc_info.value.code == "t2i_review.diagnostic_alias_mismatch"
