"""FINDING 11 — reference_phrase_kinds 'character' over-declaration normalization.

W5 scene_image_pipeline 재실행 잔여 1건 (still ec601991, scene 10 shot 6):
인물이 창문 반사·실루엣·무-안면으로만 등장 → id_policy 상 generic descriptor
사용(C## ID 미사용) → render_prompt_card.required_refs 에 character ref 없음이
정상. 그러나 scene_detail LLM 이 per-variation sidecar `reference_phrase_kinds`
에 'character' 를 over-declare → ref_contract_validator step 6 phantom guard
fail-fast.

FINDING 9 W4 Cat4 의 'prop' over-declaration 과 동일 클래스의 'character' 변종.
fix = W4 와 동일한 deterministic producer/consume-side normalization, 단
'character' 는 Cat2/id_policy 와 얽혀 strip 조건을 좁게 잡는다:
  variation 의 reference_phrase_kinds 에 'character' 가 있고 AND
  render_prompt_card.required_refs 에 kind in {character, character_outlook} 가
  없고 AND 해당 variation 의 t2i_prompt 에 resolver 가 character meta 를 만들 수
  있는 ID-form signal (C##, C##O##, O00) 이 없을 때만 'character' 제거.
validator step 6 약화 아님 (producer self-consistency normalization).
"""
from __future__ import annotations

import pytest


def _card(required_refs):
    return {"asset_requirements": {"required_refs": required_refs}}


# scene 10 shot 6 (ec601991) 의 실제 형태 — generic silhouette descriptor, C## 토큰 없음.
_GENERIC_PROMPT = (
    "Photorealistic cinematic still. In the middle-center midground, the lit "
    "window glass reflection captures a bulky adult Korean male figure in a "
    "black hoodie overlapping an early 40s Korean female figure in a home "
    "apron, both reduced to distorted shadow shapes with no discernible "
    "facial features."
)
_PROMPT_WITH_BARE_ID = (
    "Photorealistic cinematic still. C04 in a dark detective coat stands at "
    "a three-quarter angle, her face catching cold overhead light."
)
_PROMPT_WITH_COMPOSITE_ID = (
    "Photorealistic cinematic still. C04O06, an Asian woman in her 40s, "
    "leans over the desk under hard fluorescent light."
)


class TestStripOverdeclaredCharacterPhraseKind:
    def test_strips_character_when_no_required_character_and_no_id_signal(self):
        """ec601991 재현: required_refs background-only + generic/no-ID prompt +
        rpk ['background','character'] → 'character' 제거, 'background' 보존."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _GENERIC_PROMPT,
            "reference_phrase_kinds": ["background", "character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["background"]

    def test_keeps_character_when_required_refs_has_character(self):
        """required_refs 에 kind=character 있으면 'character' 보존 (W2 base_id_required)."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _GENERIC_PROMPT,
            "reference_phrase_kinds": ["character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "character", "id": "C04"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["character"]

    def test_keeps_character_when_required_refs_has_character_outlook(self):
        """required_refs 에 kind=character_outlook 있으면 'character' 보존."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _GENERIC_PROMPT,
            "reference_phrase_kinds": ["character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "character_outlook", "id": "C04O06"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["character"]

    def test_keeps_character_when_prompt_has_bare_id_form(self):
        """required character 없어도 prompt 에 명시적 bare C## ID-form 이 있으면
        보존 — 명시적 ID signal 은 silent-normalize 하지 않고 validator step 6
        fail-fast 에 맡긴다 (bare C## 단독은 required_ref 없이 attach 되지 않음)."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _PROMPT_WITH_BARE_ID,
            "reference_phrase_kinds": ["character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["character"]

    def test_keeps_character_when_prompt_has_composite_id_form(self):
        """required character 없어도 prompt 에 C##O## composite 있으면 보존."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _PROMPT_WITH_COMPOSITE_ID,
            "reference_phrase_kinds": ["character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["character"]

    def test_does_not_touch_prop_kind(self):
        """본 helper 는 'character' 만 처리 — 'prop' 은 W4 helper 소관, 무변경."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _GENERIC_PROMPT,
            "reference_phrase_kinds": ["prop", "character"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["prop"]

    def test_noop_when_no_character_in_rpk(self):
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [{
            "t2i_prompt": _GENERIC_PROMPT,
            "reference_phrase_kinds": ["background"],
        }]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == ["background"]

    def test_handles_empty_and_none_variations(self):
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        _strip_overdeclared_character_phrase_kind(None, _card([]))  # no raise
        _strip_overdeclared_character_phrase_kind([], _card([]))  # no raise

    def test_multi_variation_each_normalized_independently(self):
        """variation 마다 자기 t2i_prompt 의 ID-form signal 로 독립 판정."""
        from app.core.steps.detail_steps import _strip_overdeclared_character_phrase_kind

        variations = [
            {"t2i_prompt": _GENERIC_PROMPT,
             "reference_phrase_kinds": ["character"]},          # strip
            {"t2i_prompt": _PROMPT_WITH_BARE_ID,
             "reference_phrase_kinds": ["character"]},          # keep (has C04)
        ]
        _strip_overdeclared_character_phrase_kind(
            variations, _card([{"kind": "background", "id": "L03B03"}]),
        )
        assert variations[0]["reference_phrase_kinds"] == []
        assert variations[1]["reference_phrase_kinds"] == ["character"]
