"""FINDING 9 W4 (Cat4) — reference_phrase_kinds 'prop' over-declaration 정규화.

scene_image_pipeline E2E partial 의 prop-without-meta 위반 2건 (e4d27b1f S7.5 P05 /
d2d0e2f1 S12.17 P09):
  scene_detail LLM 이 per-variation sidecar `reference_phrase_kinds` 에 'prop' 을
  declare 했으나, 그 prop 은 numbered image mapping 에 매핑되지 않은 descriptive
  P## 언급일 뿐 — `render_prompt_card.asset_requirements.required_refs` 에
  kind="prop" required_ref 가 없다. 결과적으로 ref_contract_validator step 6
  phantom guard 가 image-gen 직전 fail-fast.

W4 fix = scene_detail producer (`_analyze_one`) 가 narrow render_prompt_card 의
required_refs SOT 와 sidecar 를 정합시키는 deterministic normalization:
  required_refs 에 kind="prop" 이 없으면 모든 variation 의
  `reference_phrase_kinds` 에서 'prop' 제거. 'character'/'background' 는 무변경
  (Cat2 / id_policy / background contract 와 얽혀 W4 scope 밖).

validator step 6 는 약화하지 않는다 — producer self-consistency normalization 만
추가하고 downstream fail-fast 는 그대로 둔다.
"""
from __future__ import annotations

import pytest

from app.core.ref_contract_validator import RefContractError, validate_attached_refs
from app.core.steps.detail_steps import (
    SCENE_DETAIL_SCHEMA_VERSION,
    _strip_overdeclared_prop_phrase_kind,
)


# ── fixtures ──────────────────────────────────────────────────────────
def _card(required_refs: list) -> dict:
    """최소 render_prompt_card — asset_requirements.required_refs 만 사용."""
    return {"asset_requirements": {"required_refs": required_refs}}


def _var(reference_phrase_kinds: list) -> dict:
    return {"t2i_prompt": "(test)", "reference_phrase_kinds": list(reference_phrase_kinds)}


_PROP_REF = {"kind": "prop", "id": "P05", "policy": "required"}
_CHAR_REF = {"kind": "character_outlook", "id": "C01O01", "policy": "required"}
_BG_REF = {"kind": "background", "id": "L08B01", "policy": "required"}


# ══ G1 — required_refs prop 부재 → 'prop' 제거 ═════════════════════════
def test_g1_no_prop_required_ref_strips_prop_phrase_kind() -> None:
    """required_refs 에 kind=prop 없음 + reference_phrase_kinds 에 'prop' →
    normalization 후 'prop' 제거."""
    variations = [_var(["character", "background", "prop"])]
    _strip_overdeclared_prop_phrase_kind(variations, _card([_CHAR_REF, _BG_REF]))
    assert variations[0]["reference_phrase_kinds"] == ["character", "background"]


# ══ G2 — required_refs prop 존재 → 'prop' 유지 ═════════════════════════
def test_g2_prop_required_ref_keeps_prop_phrase_kind() -> None:
    """required_refs 에 kind=prop 있음 → reference_phrase_kinds 'prop' 유지."""
    variations = [_var(["character", "prop"])]
    _strip_overdeclared_prop_phrase_kind(variations, _card([_CHAR_REF, _PROP_REF]))
    assert variations[0]["reference_phrase_kinds"] == ["character", "prop"]


# ══ G3 — 'prop' 만 제거, character/background 보존 ═════════════════════
def test_g3_only_prop_removed_character_background_preserved() -> None:
    """prop 만 normalize 대상 — character/background 는 보존 (W4 scope)."""
    variations = [_var(["character", "background", "prop"])]
    _strip_overdeclared_prop_phrase_kind(variations, _card([]))
    assert variations[0]["reference_phrase_kinds"] == ["character", "background"]


# ══ G4 — validator step 6 약화 0 ══════════════════════════════════════
def test_g4_validator_step6_still_fails_on_phantom_prop() -> None:
    """W4 는 producer normalization 만 추가 — validator step 6 phantom guard 는
    약화 0. raw bad payload (reference_phrase_kinds=['prop'] + prop meta 미첨부)
    는 여전히 RefContractError fail-fast."""
    with pytest.raises(RefContractError) as exc:
        validate_attached_refs(
            rpc=None,
            labeled_refs=[],
            attached_meta=[],
            prompt="",
            is_close_framing=False,
            reference_phrase_kinds=["prop"],
        )
    assert "no prop meta" in str(exc.value)


# ══ G5 — schema shape 불변 → version bump 0 ═══════════════════════════
def test_g5_scene_detail_schema_version_unchanged() -> None:
    """W4 는 reference_phrase_kinds 의 값만 제거 — schema shape 불변. producer
    output schema 변경 없음 → W4 자체는 SCENE_DETAIL_SCHEMA_VERSION bump 없음.
    reference-necessity Phase 2 (2026-05-23): render_prompt_card overlay → 12→13 bump."""
    assert SCENE_DETAIL_SCHEMA_VERSION == 13


# ══ 재현 — 실패 2건 ════════════════════════════════════════════════════
def test_repro_e4d27b1f_smartphone_p05() -> None:
    """e4d27b1f (S7.5): required_refs=[character_outlook C01O01, background
    L08B01], reference_phrase_kinds=['character','background','prop'] (P05
    descriptive smartphone, prop ref image 미매핑) → 'prop' 제거."""
    variations = [_var(["character", "background", "prop"])]
    card = _card([
        {"kind": "character_outlook", "id": "C01O01", "policy": "required"},
        {"kind": "background", "id": "L08B01", "policy": "required"},
    ])
    _strip_overdeclared_prop_phrase_kind(variations, card)
    assert variations[0]["reference_phrase_kinds"] == ["character", "background"]


def test_repro_d2d0e2f1_wall_mark_p09() -> None:
    """d2d0e2f1 (S12.17): required_refs=[background L05B09],
    reference_phrase_kinds=['background','prop'] (P09 descriptive wall mark) →
    'prop' 제거."""
    variations = [_var(["background", "prop"])]
    card = _card([{"kind": "background", "id": "L05B09", "policy": "required"}])
    _strip_overdeclared_prop_phrase_kind(variations, card)
    assert variations[0]["reference_phrase_kinds"] == ["background"]


# ══ edge ══════════════════════════════════════════════════════════════
def test_edge_multi_variation_all_stripped() -> None:
    """여러 variation 전부 'prop' 제거 ("all variations failed" 대응)."""
    variations = [
        _var(["prop"]),
        _var(["character", "prop"]),
        _var(["background"]),
    ]
    _strip_overdeclared_prop_phrase_kind(variations, _card([]))
    assert variations[0]["reference_phrase_kinds"] == []
    assert variations[1]["reference_phrase_kinds"] == ["character"]
    assert variations[2]["reference_phrase_kinds"] == ["background"]


def test_edge_no_prop_phrase_kind_is_noop() -> None:
    """reference_phrase_kinds 에 'prop' 없음 → 무변경 (list 객체 동일성 보존)."""
    rpk = ["character", "background"]
    variations = [{"reference_phrase_kinds": rpk}]
    _strip_overdeclared_prop_phrase_kind(variations, _card([]))
    assert variations[0]["reference_phrase_kinds"] is rpk


def test_edge_empty_and_none_variations_noop() -> None:
    """t2i_variations 빈 list / None → no-op, error 0."""
    _strip_overdeclared_prop_phrase_kind([], _card([]))
    _strip_overdeclared_prop_phrase_kind(None, _card([]))


def test_edge_non_dict_variation_skipped() -> None:
    """malformed (non-dict) variation 은 skip — 정상 variation 만 처리."""
    variations = ["not-a-dict", _var(["prop"])]
    _strip_overdeclared_prop_phrase_kind(variations, _card([]))
    assert variations[1]["reference_phrase_kinds"] == []
