"""Area #3 W3 — detect_offscreen_drift_structured Path 1 unit tests.

hybrid gate: camera_direction OFFSCREEN_RE gate + structured
character_angles[].gaze_target_id resolution. Frame-internal mutual
gaze 정상 (drift 아님). structured path는 name matching 안 함
(gaze_target_id resolve only).
"""
import pytest

from app.modules.pipeline.shot_visibility import detect_offscreen_drift_structured


class TestCaseAFrameInternalMutualGaze:
    """Frame-internal mutual gaze 정상 — drift 아님 (CRITICAL false-positive guard)."""

    def test_no_drift_when_camera_direction_lacks_offscreen_phrase(self):
        result = detect_offscreen_drift_structured(
            visible_ids=["C01", "C02"],
            camera_direction="two-shot, mid distance, both characters visible",
            character_angles=[
                {
                    "character": "캐릭터A",
                    "gaze_direction_kind": "looks_at_character",
                    "gaze_target_id": "C02",
                },
            ],
            id_to_name={"C01": "캐릭터A", "C02": "캐릭터B"},
        )
        assert result == {}, (
            "OFFSCREEN_RE gate 미통과 → drift 아님 (frame-internal mutual gaze 정상)"
        )


class TestCaseBHybridGateBlocking:
    """OFFSCREEN_RE gate + structured matching → blocking candidate."""

    def test_drift_detected_when_offscreen_phrase_and_structured_match(self):
        result = detect_offscreen_drift_structured(
            visible_ids=["C01", "C02"],
            camera_direction="캐릭터A is in frame while 캐릭터B remains off-screen.",
            character_angles=[
                {
                    "character": "캐릭터A",
                    "gaze_direction_kind": "looks_at_character",
                    "gaze_target_id": "C02",
                },
            ],
            id_to_name={"C01": "캐릭터A", "C02": "캐릭터B"},
        )
        assert result == {"C02": "캐릭터B"}, (
            "OFFSCREEN_RE 매치 + gaze_target_id resolve → blocking candidate"
        )


class TestCaseCStructuredPathDoesNotMatchNames:
    """Structured path는 name matching 안 함 — gaze_target_id resolve only."""

    def test_drift_detected_via_structured_resolution_only(self):
        """camera_direction에 캐릭터B 이름이 없어도 gaze_target_id로 resolve."""
        result = detect_offscreen_drift_structured(
            visible_ids=["C01", "C02"],
            camera_direction="캐릭터A looks off-screen toward the unseen person.",
            character_angles=[
                {
                    "character": "캐릭터A",
                    "gaze_direction_kind": "looks_at_character",
                    "gaze_target_id": "C02",
                },
            ],
            id_to_name={"C01": "캐릭터A", "C02": "캐릭터B"},
        )
        assert result == {"C02": "캐릭터B"}, (
            "OFFSCREEN_RE = gate only, gaze_target_id 가 structured resolution"
        )


class TestCaseDInFrameGuarantee:
    """character_angles[].character ↔ visible canonical_name 매칭 sid 영구 제외."""

    def test_in_frame_sid_excluded_from_drift(self):
        """캐릭터A 가 character_angles 안 character로 명시 → visible C01 영구 제외."""
        result = detect_offscreen_drift_structured(
            visible_ids=["C01"],
            camera_direction="캐릭터A looks off-screen.",
            character_angles=[
                {
                    "character": "캐릭터A",
                    "gaze_direction_kind": "looks_at_character",
                    "gaze_target_id": "C01",  # gaze_target_id 가 visible 안에 있어도
                },
            ],
            id_to_name={"C01": "캐릭터A"},
        )
        # 캐릭터A 가 staging 안 character == visible canonical_name → in-frame guarantee
        assert result == {}, (
            "in_frame_guarantee: character_angles[].character == visible name → 영구 제외"
        )


class TestEmptyInputs:
    def test_empty_camera_direction_returns_empty(self):
        result = detect_offscreen_drift_structured(
            visible_ids=["C01"],
            camera_direction="",
            character_angles=[],
            id_to_name={"C01": "캐릭터A"},
        )
        assert result == {}

    def test_no_offscreen_phrase_returns_empty(self):
        result = detect_offscreen_drift_structured(
            visible_ids=["C01"],
            camera_direction="standard mid-shot",
            character_angles=[
                {
                    "character": "캐릭터A",
                    "gaze_direction_kind": "looks_at_character",
                    "gaze_target_id": "C02",
                },
            ],
            id_to_name={"C01": "캐릭터A", "C02": "캐릭터B"},
        )
        assert result == {}
