"""W22 직행 place-continuity (2026-07-10, Codex BLOCKING_1) — 결정론 잠금.

2회차 E2E 육안: 같은 장소 직행 샷 간 외형 drift. prev 연속성 ref 재도입 시
기본 previous_shot_continuity 지시(조명/무드만 사용)는 구조 일관성 신호가
아니고 TIME_LOCK 과 충돌 — 직행 전용 metadata
``outdoor_direct_place_continuity`` 로 전용 consumer 분기를 탄다:
- 캐논 실사/맵 = 장소 identity·topology 1차 SOT
- prev = 겹치는 영역의 stable visible details 연속성
- 본문(shot text) = 시간/조명/카메라/액션 SOT (prev 조명 복사 금지)
- prev 의 인물/포즈/구도 복사 금지
"""
import inspect

from app.services.prompt_service import (
    make_labeled_ref_payload,
    resolve_ref_roles,
)
from app.services.scene_generation_coordinator import (
    _insert_outdoor_canon_refs,
)


def _render(meta):
    return resolve_ref_roles(make_labeled_ref_payload(
        labeled_refs=[("PREV STILL", b"png")],
        ref_roles=["previous_shot_continuity"],
        ref_role_metadata=[meta],
        attached_meta=[("background_prev_shot", "L01")],
    ))


def test_direct_place_continuity_branch_instructions():
    res = _render({"outdoor_direct_place_continuity": True})
    blob = "\n".join(res.ref_instructions)
    assert "stable visible details" in blob
    assert "do NOT copy lighting or time of day" in blob
    assert "do NOT copy characters, people, poses" in blob
    assert "LOCATION PHOTOGRAPH/SITE PLAN" in blob
    assert "PREVIOUS STILL at the same property" in res.roles_text


def test_default_branch_unchanged_without_marker():
    """metadata 마커 없으면 기존 지시(조명/무드) 그대로 — byte-identical."""
    res = _render({})
    blob = "\n".join(res.ref_instructions)
    assert "use ONLY the lighting, color palette, and environment mood" in blob
    assert "stable visible details" not in blob


def test_canon_insert_after_prev_keeps_photo_first():
    """prev 가 먼저 삽입돼 있어도 캐논 insert 후 순서=[실사, 맵, prev, ...]."""
    labeled_refs = [("PREV STILL", b"p"), ("CHAR", b"c")]
    attached_meta = [("background_prev_shot", "L01"), ("character", "C01")]
    ref_roles = ["previous_shot_continuity", "character_ref"]
    ref_role_metadata = [{"outdoor_direct_place_continuity": True}, {}]

    _insert_outdoor_canon_refs(
        labeled_refs, attached_meta, ref_roles, ref_role_metadata,
        {
            "prompt": "p", "place_id": "g1",
            "master_bytes": b"M", "map_bytes": b"MAP",
            "master_asset_id": "am", "map_asset_id": "amap",
        },
        scene_index=1, shot_index=1, where="test",
    )
    assert ref_roles == [
        "outdoor_canon_photo_ref", "outdoor_canon_map_ref",
        "previous_shot_continuity", "character_ref",
    ]
    assert len(labeled_refs) == len(attached_meta) == len(ref_roles) == \
        len(ref_role_metadata) == 4


def test_stamp_sites_parity_single_and_batch():
    """단건/배치 양 경로 모두 직행 prev 에 marker stamp (drift 가드)."""
    import app.services.scene_generation_coordinator as m
    src = inspect.getsource(m)
    assert src.count('"outdoor_direct_place_continuity": True') == 2


def test_combined_payload_canon_primary_prev_details_no_contradiction():
    """결합 계약 (Codex NARROW_1/2): [캐논 실사, 캐논 맵, 직행 prev] 한 payload.

    - 캐논 지시에 SOLE 없음 / PRIMARY 있음 (prev 동반 시 전환)
    - prev = stable visible details 연속성
    - 시간/조명 = 본문 SOT (캐논·prev 양쪽에서 복사 금지)
    - v2 no_annotation: 오버레이 주석 금지와 diegetic signage 보존 공존
    """
    res = resolve_ref_roles(make_labeled_ref_payload(
        labeled_refs=[("PHOTO", b"a"), ("PLAN", b"b"), ("PREV", b"c")],
        ref_roles=[
            "outdoor_canon_photo_ref", "outdoor_canon_map_ref",
            "previous_shot_continuity",
        ],
        ref_role_metadata=[
            {"pipeline_role": "outdoor_canon_photo"},
            {"pipeline_role": "outdoor_canon_map"},
            {"outdoor_direct_place_continuity": True},
        ],
        attached_meta=[
            ("background", "outdoor_canon:g1"),
            ("background", "outdoor_canon:g1"),
            ("background_prev_shot", "L01"),
        ],
    ))
    blob = "\n".join(res.ref_instructions)
    assert "SOLE source" not in blob
    assert "PRIMARY source" in blob
    assert "stable visible details" in blob
    # 시간/조명 본문 SOT — 캐논/prev 양쪽 금지 문구
    assert blob.count("time of\nday or lighting — the prompt text") >= 1 or \
        "time of day or lighting — the prompt text" in blob.replace("\n", " ")
    assert "do NOT copy lighting or time of day" in blob

    # v2 no_annotation 팩: 오버레이 금지 + diegetic signage 보존 공존
    from app.modules.pipeline.outdoor_direct_compose import load_blocks
    na = load_blocks("2")["no_annotation"]
    assert "overlay annotations" in na
    assert "In-world signage" in na and "preserved" in na
    assert "no signage lettering" not in na


def test_canon_solo_keeps_sole_wording():
    """prev marker 없는 직행 payload = 기존 SOLE 문구 byte-identical."""
    res = resolve_ref_roles(make_labeled_ref_payload(
        labeled_refs=[("PHOTO", b"a"), ("PLAN", b"b")],
        ref_roles=["outdoor_canon_photo_ref", "outdoor_canon_map_ref"],
        ref_role_metadata=[{}, {}],
        attached_meta=[
            ("background", "outdoor_canon:g1"),
            ("background", "outdoor_canon:g1"),
        ],
    ))
    blob = "\n".join(res.ref_instructions)
    assert "SOLE source" in blob
    assert "PRIMARY source" not in blob
