"""Area #2 — gaze_direction helper unit tests (Q1 + Q3 + Q7 closure)."""
import pytest

from app.core.errors import AppError
from app.core.gaze_direction import (
    GAZE_DIRECTION_KINDS,
    TARGET_REQUIRED_KINDS,
    is_target_required,
    validate_pairing,
    resolve_visible_character_target,
)


class TestGazeDirectionConstants:
    def test_kinds_tuple(self):
        assert GAZE_DIRECTION_KINDS == (
            "camera", "down", "up", "distant", "closed_eyes", "off_screen",
            "looks_at_character", "looks_at_object",
        )

    def test_target_required_kinds(self):
        assert TARGET_REQUIRED_KINDS == frozenset({"looks_at_character", "looks_at_object"})


class TestIsTargetRequired:
    @pytest.mark.parametrize("kind,expected", [
        ("looks_at_character", True),
        ("looks_at_object", True),
        ("camera", False),
        ("down", False),
        ("closed_eyes", False),
        ("off_screen", False),
    ])
    def test_is_target_required(self, kind, expected):
        assert is_target_required(kind) is expected


class TestValidatePairing:
    @pytest.mark.parametrize("kind,target_id", [
        ("looks_at_character", "C01"),
        ("looks_at_character", "C123"),
        ("looks_at_object", "P01"),
        ("looks_at_object", "B12"),
        ("camera", None),
        ("distant", None),
        ("off_screen", None),
    ])
    def test_valid_pairing_pass(self, kind, target_id):
        validate_pairing(kind, target_id, where="test")  # no raise

    def test_invalid_kind_fails(self):
        with pytest.raises(AppError) as exc_info:
            validate_pairing("invalid_kind", "C01", where="test")
        assert "gaze_direction_kind.enum" in exc_info.value.code

    def test_target_required_missing_fails(self):
        with pytest.raises(AppError) as exc_info:
            validate_pairing("looks_at_character", None, where="test")
        assert "gaze_target_id.missing_for_target_kind" in exc_info.value.code

    @pytest.mark.parametrize("target_id", ["C01", ""])
    def test_non_target_with_id_fails(self, target_id):
        with pytest.raises(AppError) as exc_info:
            validate_pairing("camera", target_id, where="test")
        assert "gaze_target_id.present_for_non_target_kind" in exc_info.value.code

    @pytest.mark.parametrize("target_id", ["phone", "O01", "강민준", "c01", "C1"])
    def test_invalid_shape_for_character_fails(self, target_id):
        with pytest.raises(AppError) as exc_info:
            validate_pairing("looks_at_character", target_id, where="test")
        assert "gaze_target_id.shape" in exc_info.value.code

    @pytest.mark.parametrize("target_id", ["phone", "C01", "O01", "p01"])
    def test_invalid_shape_for_object_fails(self, target_id):
        with pytest.raises(AppError) as exc_info:
            validate_pairing("looks_at_object", target_id, where="test")
        assert "gaze_target_id.shape" in exc_info.value.code


class TestResolveVisibleCharacterTarget:
    def test_valid_target_returns_tuple(self):
        result = resolve_visible_character_target(
            "C01",
            visible_character_ids={"C01", "C02"},
            id_to_name={"C01": "Alpha", "C02": "Beta"},
            where="test",
        )
        assert result == ("C01", "Alpha")

    def test_invalid_shape_returns_none(self):
        result = resolve_visible_character_target(
            "phone",
            visible_character_ids={"C01"},
            id_to_name={"C01": "Alpha"},
            where="test",
        )
        assert result is None

    def test_not_in_visible_returns_none(self):
        result = resolve_visible_character_target(
            "C03",
            visible_character_ids={"C01", "C02"},
            id_to_name={"C01": "Alpha", "C02": "Beta"},
            where="test",
        )
        assert result is None

    def test_missing_name_returns_none(self):
        result = resolve_visible_character_target(
            "C01",
            visible_character_ids={"C01"},
            id_to_name={},  # name 누락
            where="test",
        )
        assert result is None
