"""B-run S15 sh5/sh7 실측 fix (2026-06-12) — back_to_camera ref 계약 핵심 테스트.

shot_staging 의 structured character_angles enum 이 SOT — 글자 패턴 0.
F2 (S11 sh5 — background_chain_ref 개구부 정합) 계약도 함께 잠근다.
"""
from __future__ import annotations

from app.services.prompt_service import make_labeled_ref_payload, resolve_ref_roles
from app.services.scene_reference_service import apply_back_to_camera_constraints


def _staging(angle: str = "back_to_camera", name: str = "수리영"):
    return {"character_angles": [
        {"angle": angle, "character": name, "body_pose": "running",
         "gaze_direction_kind": "off_screen", "subject_state": "alive"},
    ]}


_VES = [
    {"id": "e-c09", "short_id": "C09", "name": "수리영", "entity_type": "character"},
]
_LOOKUP = {"e-c09": {"short_id": "C09", "name": "수리영", "entity_type": "character"}}


def test_back_to_camera_stamps_attached_character_ref():
    attached = [("character", "C09"), ("character_outlook", "C09O02"), ("prop", "P02")]
    roles = ["character_ref", "outfit_ref_inline", "prop_ref"]
    meta = [{}, {}, {}]
    apply_back_to_camera_constraints(
        attached, roles, meta,
        staging=_staging(), visible_entities=_VES, entity_lookup=_LOOKUP)
    assert meta[0] == {"view_angle_constraint": "back_to_camera"}
    assert meta[1] == {"view_angle_constraint": "back_to_camera"}  # outlook 도 base sid 매칭
    assert meta[2] == {}  # prop 비대상

    # 비대상 enum(profile 등) / staging 부재 → no-op (byte-identical)
    meta2 = [{}, {}, {}]
    apply_back_to_camera_constraints(
        attached, roles, meta2,
        staging=_staging(angle="profile_right"), visible_entities=_VES, entity_lookup=_LOOKUP)
    assert meta2 == [{}, {}, {}]
    apply_back_to_camera_constraints(
        attached, roles, meta2,
        staging=None, visible_entities=_VES, entity_lookup=_LOOKUP)
    assert meta2 == [{}, {}, {}]


def test_back_to_camera_ambiguous_name_skips():
    ves = _VES + [{"id": "e-dup", "short_id": "C77", "name": "수리영",
                   "entity_type": "character"}]
    attached = [("character", "C09")]
    roles = ["character_ref"]
    meta = [{}]
    apply_back_to_camera_constraints(
        attached, roles, meta,
        staging=_staging(), visible_entities=ves, entity_lookup={})
    assert meta == [{}]  # ambiguous → skip (잘못 stamp 하느니 no-op)


def test_prompt_instruction_emitted_only_with_constraint():
    payload = make_labeled_ref_payload(
        labeled_refs=[("char C09 identity", b"x"), ("char C09 identity", b"y")],
        ref_roles=["character_ref", "character_ref"],
        ref_role_metadata=[{"view_angle_constraint": "back_to_camera"}, {}],
        attached_meta=[("character", "C09"), ("character", "C10")],
    )
    res = resolve_ref_roles(payload)
    joined = "\n".join(res.ref_instructions)
    assert "image 1: this character faces AWAY from the camera" in joined
    assert "face must NOT be visible" in joined
    assert "image 2: this character faces AWAY" not in joined


def test_background_chain_ref_openings_contract():
    payload = make_labeled_ref_payload(
        labeled_refs=[("bg", b"x")],
        ref_roles=["background_chain_ref"],
        ref_role_metadata=[{}],
        attached_meta=[("background", "")],
    )
    joined = "\n".join(resolve_ref_roles(payload).ref_instructions)
    assert "architectural openings" in joined
    assert "do NOT invent additional doors or windows" in joined
