"""zoom_in_detail source provenance hardening — 10 helper direct unit test.

helper direct: Layer 2 / 2.5 / 2.6 / 3 × 6 + §4.5 metadata selection branching.
coordinator emit 검증 (spec §7 test #9a single + #9b batch = 2 test) 는 별도
`backend/tests/services/test_build_scene_attached_refs.py` 의 dep_scene fixture
variant 로 작성 (Task 7 Step 7.3). 총 신규 unit test = 10 + 2 = 12.

spec: docs/superpowers/specs/2026-05-15-zoom-in-detail-source-provenance-design.md
"""
from __future__ import annotations

import pytest

from app.core.ref_contract_validator import RefContractError
from app.services.scene_reference_service import SceneReferenceService


def _make_svc() -> SceneReferenceService:
    """SceneReferenceService stub — DB 미사용 (본 test path 는 helper 직접 호출).

    기존 close_ref_usage_matrix test 패턴 따름 (helper 직접 호출, DB X).
    """
    svc = SceneReferenceService.__new__(SceneReferenceService)
    return svc


def _base_kwargs(**overrides):
    """공통 helper kwarg base — overrides 로 분기.

    framing_scale="medium" (non-close) default — Layer 4 (close × ref_usage matrix v1)
    trigger 차단으로 본 area 의 Layer 2/2.5/2.6/3 + §4.5 selection 만 검증.
    """
    base = {
        "best_prev_bytes": b"PREV",
        "bytes_source_kind": "dep_scene",
        "still_data": {"scene_index": 1, "shot_index": 1},
        "visible_entities": [],
        "current_location_ids": [],
        "dep_scene_id": "S0_Shot1",
        "stills": [{"id": "S0_Shot1", "visible_entities_json": "[]"}],
        "location_scene_history": {},
        "dep_detail_map": {"1_1": {"ref_usage": "zoom_in_detail"}},
        "staging": {"framing_scale": "medium"},
        "state_variant_sids": {},
        "entity_lookup": {},
    }
    base.update(overrides)
    return base


# Layer 2 — source kind invalid
def test_layer2_bytes_source_kind_invalid_raises():
    svc = _make_svc()
    kwargs = _base_kwargs(bytes_source_kind="invalid_kind")
    with pytest.raises(RefContractError, match="prev_ref_source_missing.*bytes_source_kind invalid"):
        svc.build_prev_shot_background_ref(**kwargs)


# Layer 2.5 — dep_scene + no dep_scene_id
def test_layer25_dep_scene_without_dep_scene_id_raises():
    svc = _make_svc()
    kwargs = _base_kwargs(bytes_source_kind="dep_scene", dep_scene_id=None)
    with pytest.raises(RefContractError, match="prev_ref_source_missing.*dep_scene source requires dep_scene_id"):
        svc.build_prev_shot_background_ref(**kwargs)


# §4.5 — Metadata selection branching: dep_scene_id 있어도 location_history fallback bytes 면
# metadata 도 location_history 에서 와야 (split-provenance 차단).
def test_metadata_selection_split_provenance_blocked():
    """dep_scene_id 존재 + bytes_source_kind=location_history + ref_usage=exact_background
    + framing_scale=medium (non-close, Layer 4 trigger 차단) → prev_still_data 가
    location_scene_history 에서 와야 함 (dep_scene_id stills 매칭 X).
    """
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="location_history",
        dep_scene_id="S0_Shot1",  # dep_scene_id 존재 (LLM 이 emit 했지만 path read 실패한 case)
        stills=[
            {"id": "S0_Shot1", "visible_entities_json": '[{"id": "loc1", "entity_type": "location", "short_id": "L01_DEP"}]'},
        ],
        current_location_ids=["loc_other"],
        location_scene_history={
            "loc_other": (None, {"id": "S0_Shot_OTHER", "visible_entities_json": '[{"id": "loc2", "entity_type": "location", "short_id": "L02_HIST"}]'})
        },
        dep_detail_map={"1_1": {"ref_usage": "exact_background"}},
        staging={"framing_scale": "medium"},
        entity_lookup={
            "loc1": {"entity_type": "location", "short_id": "L01_DEP", "name": "dep_loc"},
            "loc2": {"entity_type": "location", "short_id": "L02_HIST", "name": "hist_loc"},
        },
    )
    result = svc.build_prev_shot_background_ref(**kwargs)
    assert result is not None
    label, bytes_returned, loc_id, _ref_role, _ref_role_metadata = result  # Area #11 v1 W3: 5-tuple
    # bytes_source_kind="location_history" → loc_id_from_history 가 결정.
    # helper D5 contract: location_history match key (current_location_ids x location_scene_history)
    # 가 loc_id 로 반환 (단순 match key, not entity short_id).
    assert loc_id == "loc_other", (
        f"expected loc_id='loc_other' (location_history match key), got {loc_id!r} — "
        f"split-provenance leak (dep_scene metadata 사용)"
    )


# Layer 2.6 — location_history + no current_location_ids match
def test_layer26_location_history_without_match_raises():
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="location_history",
        current_location_ids=["loc_no_match"],
        location_scene_history={},  # 매칭 없음
        dep_scene_id=None,  # location_history fallback 의 정상 case (dep 없을 때) — 단 match 도 없는 거짓 source
        dep_detail_map={"1_1": {"ref_usage": "exact_background"}},
    )
    with pytest.raises(RefContractError, match="prev_ref_source_missing.*location_history source requires"):
        svc.build_prev_shot_background_ref(**kwargs)


# Layer 3 — zoom_in_detail invariant (close 무관, ref_usage 기준)
def test_zoom_in_detail_with_dep_scene_passes():
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="dep_scene",
        dep_scene_id="S0_Shot1",
        stills=[{"id": "S0_Shot1", "visible_entities_json": "[]"}],
        dep_detail_map={"1_1": {"ref_usage": "zoom_in_detail"}},
        staging={"framing_scale": "medium"},
    )
    result = svc.build_prev_shot_background_ref(**kwargs)
    assert result is not None
    label, _, _, _ref_role, _ref_role_metadata = result  # Area #11 v1 W3: 5-tuple
    # W3 (2026-06-11): exact-frame 문구 완화 — SAME MOMENT continuity.
    assert "SAME MOMENT, zoomed-in reframing" in label


def test_zoom_in_detail_with_location_history_raises():
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="location_history",
        dep_scene_id=None,
        current_location_ids=["loc1"],
        location_scene_history={"loc1": (None, {"id": "S0_HIST", "visible_entities_json": "[]"})},
        dep_detail_map={"1_1": {"ref_usage": "zoom_in_detail"}},
        staging={"framing_scale": "medium"},
    )
    with pytest.raises(RefContractError, match="zoom_in_detail_source_violation"):
        svc.build_prev_shot_background_ref(**kwargs)


def test_zoom_in_detail_with_no_bytes_returns_none():
    """Layer 1 — bytes None 시 early return, invariant 검증 X."""
    svc = _make_svc()
    kwargs = _base_kwargs(
        best_prev_bytes=None,
        bytes_source_kind="none",
        dep_detail_map={"1_1": {"ref_usage": "zoom_in_detail"}},
    )
    result = svc.build_prev_shot_background_ref(**kwargs)
    assert result is None


def test_medium_zoom_in_detail_with_dep_scene_passes():
    """framing_scale=medium (non-close) 도 zoom_in_detail + dep_scene 이면 pass.
    Layer 3 가 ref_usage 만 trigger, framing_scale 무관."""
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="dep_scene",
        dep_scene_id="S0_Shot1",
        stills=[{"id": "S0_Shot1", "visible_entities_json": "[]"}],
        dep_detail_map={"1_1": {"ref_usage": "zoom_in_detail"}},
        staging={"framing_scale": "medium"},
    )
    result = svc.build_prev_shot_background_ref(**kwargs)
    assert result is not None


def test_medium_zoom_in_detail_with_location_history_raises():
    """framing_scale=medium 도 zoom_in_detail + location_history 이면 raise.
    Layer 3 가 ref_usage 만 trigger, framing_scale 무관."""
    svc = _make_svc()
    kwargs = _base_kwargs(
        bytes_source_kind="location_history",
        dep_scene_id=None,
        current_location_ids=["loc1"],
        location_scene_history={"loc1": (None, {"id": "S0_HIST", "visible_entities_json": "[]"})},
        dep_detail_map={"1_1": {"ref_usage": "zoom_in_detail"}},
        staging={"framing_scale": "medium"},
    )
    with pytest.raises(RefContractError, match="zoom_in_detail_source_violation"):
        svc.build_prev_shot_background_ref(**kwargs)


def test_medium_non_zoom_in_detail_with_location_history_passes():
    """exact_background / atmosphere_reference / fallback 은 location_history bytes OK."""
    for ref_usage in ["exact_background", "atmosphere_reference", ""]:
        svc = _make_svc()
        kwargs = _base_kwargs(
            bytes_source_kind="location_history",
            dep_scene_id=None,
            current_location_ids=["loc1"],
            location_scene_history={"loc1": (None, {"id": "S0_HIST", "visible_entities_json": "[]"})},
            dep_detail_map={"1_1": {"ref_usage": ref_usage}},
            staging={"framing_scale": "medium"},
        )
        result = svc.build_prev_shot_background_ref(**kwargs)
        assert result is not None, f"ref_usage={ref_usage!r} unexpectedly raised"
