"""Area #1 — subject_reference_policy helper unit tests."""
import logging
import pytest

from app.core.errors import AppError


def test_normalize_subject_id_base():
    from app.core.subject_reference_policy import normalize_subject_id
    assert normalize_subject_id("C01") == "C01"
    assert normalize_subject_id("C123") == "C123"


def test_normalize_subject_id_outlook_to_base():
    from app.core.subject_reference_policy import normalize_subject_id
    assert normalize_subject_id("C01O02") == "C01"
    assert normalize_subject_id("C123O456") == "C123"


def test_normalize_subject_id_invalid_shape():
    from app.core.subject_reference_policy import normalize_subject_id
    for bad in ["CAT", "Cfoo", "C01XYZ", "", "P05", 42, None]:
        with pytest.raises(AppError) as exc:
            normalize_subject_id(bad)
        assert "invalid_subject_id_shape" in exc.value.code


def test_normalize_subject_id_warn_opt_in(caplog):
    from app.core.subject_reference_policy import normalize_subject_id
    with caplog.at_level(logging.WARNING):
        normalize_subject_id("C01O02", warn_on_outlook=True, where="test")
    assert any(
        "outlook_id_normalized" in (rec.__dict__.get("event") or "")
        for rec in caplog.records
    )


def test_normalize_subject_id_no_warn_default(caplog):
    from app.core.subject_reference_policy import normalize_subject_id
    with caplog.at_level(logging.WARNING):
        normalize_subject_id("C01O02", where="test")
    assert not any(
        "outlook_id_normalized" in (rec.__dict__.get("event") or "")
        for rec in caplog.records
    )


def test_derive_visible_subject_ids_basic():
    from app.core.subject_reference_policy import derive_visible_subject_ids
    assert derive_visible_subject_ids(["C01", "C02O03", "P05", "B01"]) == {"C01", "C02"}


def test_derive_visible_subject_ids_filters_noise():
    from app.core.subject_reference_policy import derive_visible_subject_ids
    assert derive_visible_subject_ids(["CAT", "Cfoo", "C01XYZ", "C01"]) == {"C01"}


def test_derive_visible_subject_ids_empty():
    from app.core.subject_reference_policy import derive_visible_subject_ids
    assert derive_visible_subject_ids([]) == set()
    assert derive_visible_subject_ids(None) == set()


def test_normalize_items_none_graceful():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    assert normalize_subject_reference_policy_items(
        None, visible_subject_ids=None, where="test"
    ) == {}


def test_normalize_items_empty_array_graceful():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    assert normalize_subject_reference_policy_items(
        [], visible_subject_ids=None, where="test"
    ) == {}


def test_normalize_items_valid_single():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{
        "subject_id": "C01",
        "policy_type": "identity_reference",
        "policy": "id_and_outlook_required",
        "reason": "primary subject",
    }]
    result = normalize_subject_reference_policy_items(
        items, visible_subject_ids={"C01"}, where="test"
    )
    assert "C01" in result
    assert result["C01"].policy == "id_and_outlook_required"


def test_normalize_items_outlook_id_normalized():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{
        "subject_id": "C01O02",
        "policy_type": "identity_reference",
        "policy": "base_id_required",
        "reason": "outlook context absent",
    }]
    result = normalize_subject_reference_policy_items(
        items, visible_subject_ids={"C01"}, where="test"
    )
    assert "C01" in result
    assert "C01O02" not in result


def test_normalize_items_duplicate_subject():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [
        {"subject_id": "C01", "policy_type": "identity_reference",
         "policy": "id_and_outlook_required", "reason": "a"},
        {"subject_id": "C01", "policy_type": "identity_reference",
         "policy": "base_id_required", "reason": "b"},
    ]
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01"}, where="test"
        )
    assert "duplicate_subject" in exc.value.code


def test_normalize_items_duplicate_after_normalization():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [
        {"subject_id": "C01", "policy_type": "identity_reference",
         "policy": "id_and_outlook_required", "reason": "a"},
        {"subject_id": "C01O02", "policy_type": "identity_reference",
         "policy": "base_id_required", "reason": "b"},
    ]
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01"}, where="test"
        )
    assert "duplicate_subject" in exc.value.code


def test_normalize_items_invalid_policy_enum():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{"subject_id": "C01", "policy_type": "identity_reference",
              "policy": "bogus", "reason": "x"}]
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01"}, where="test"
        )
    assert "invalid_enum_policy" in exc.value.code


def test_normalize_items_invalid_policy_type_enum():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{"subject_id": "C01", "policy_type": "future_type",
              "policy": "id_and_outlook_required", "reason": "x"}]
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01"}, where="test"
        )
    assert "invalid_enum_policy_type" in exc.value.code


def test_normalize_items_missing_required_key():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{"subject_id": "C01", "policy_type": "identity_reference",
              "policy": "id_and_outlook_required"}]  # missing 'reason'
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01"}, where="test"
        )
    assert "invalid_shape" in exc.value.code


def test_normalize_items_unknown_subject():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{"subject_id": "C99", "policy_type": "identity_reference",
              "policy": "id_and_outlook_required", "reason": "phantom"}]
    with pytest.raises(AppError) as exc:
        normalize_subject_reference_policy_items(
            items, visible_subject_ids={"C01", "C02"}, where="test"
        )
    assert "unknown_subject" in exc.value.code


def test_normalize_items_visible_set_none_skips_unknown_check():
    from app.core.subject_reference_policy import normalize_subject_reference_policy_items
    items = [{"subject_id": "C99", "policy_type": "identity_reference",
              "policy": "id_and_outlook_required", "reason": "no visible set"}]
    result = normalize_subject_reference_policy_items(
        items, visible_subject_ids=None, where="test"
    )
    assert "C99" in result


def test_serialize_exceptions_first():
    from app.core.subject_reference_policy import (
        normalize_subject_reference_policy_items,
        serialize_subject_reference_policy_map,
    )
    items = [{"subject_id": "C01", "policy_type": "identity_reference",
              "policy": "id_and_outlook_required", "reason": "test"}]
    pmap = normalize_subject_reference_policy_items(
        items, visible_subject_ids={"C01", "C02"}, where="test"
    )
    arr = serialize_subject_reference_policy_map(pmap)
    assert len(arr) == 1  # C02 default 행 생성 X
    assert arr[0]["subject_id"] == "C01"


def test_serialize_empty():
    from app.core.subject_reference_policy import serialize_subject_reference_policy_map
    assert serialize_subject_reference_policy_map({}) == []


def test_serialize_deterministic_ordering():
    from app.core.subject_reference_policy import (
        SubjectReferencePolicy,
        serialize_subject_reference_policy_map,
    )
    pmap = {
        "C03": SubjectReferencePolicy("C03", "identity_reference", "id_and_outlook_required", "c"),
        "C01": SubjectReferencePolicy("C01", "identity_reference", "id_and_outlook_required", "a"),
        "C02": SubjectReferencePolicy("C02", "identity_reference", "id_and_outlook_required", "b"),
    }
    arr = serialize_subject_reference_policy_map(pmap)
    assert [r["subject_id"] for r in arr] == ["C01", "C02", "C03"]


def test_get_or_default_omitted_returns_default():
    from app.core.subject_reference_policy import get_subject_reference_policy_or_default
    policy = get_subject_reference_policy_or_default({}, "C99", where="test")
    assert policy.policy == "id_and_outlook_required"
    assert policy.policy_type == "identity_reference"


def test_get_or_default_present():
    from app.core.subject_reference_policy import (
        SubjectReferencePolicy,
        get_subject_reference_policy_or_default,
    )
    pmap = {"C01": SubjectReferencePolicy(
        "C01", "identity_reference", "base_id_required", "test"
    )}
    policy = get_subject_reference_policy_or_default(pmap, "C01", where="test")
    assert policy.policy == "base_id_required"


def test_get_or_default_invalid_subject_id_shape():
    from app.core.subject_reference_policy import get_subject_reference_policy_or_default
    with pytest.raises(AppError) as exc:
        get_subject_reference_policy_or_default({}, "CAT", where="test")
    assert "invalid_subject_id_shape" in exc.value.code


def test_get_or_default_outlook_id_normalized_to_base():
    from app.core.subject_reference_policy import (
        SubjectReferencePolicy,
        get_subject_reference_policy_or_default,
    )
    pmap = {"C01": SubjectReferencePolicy(
        "C01", "identity_reference", "base_id_required", "test"
    )}
    policy = get_subject_reference_policy_or_default(pmap, "C01O02", where="test")
    assert policy.policy == "base_id_required"


def test_policy_to_id_usage_rule_matrix():
    from app.core.subject_reference_policy import policy_to_id_usage_rule

    r = policy_to_id_usage_rule("id_and_outlook_required")
    assert (r.base_required, r.outlook_required, r.outlook_forbidden) == (True, True, False)

    r = policy_to_id_usage_rule("base_id_required")
    assert (r.base_required, r.outlook_required, r.outlook_forbidden) == (True, False, True)

    r = policy_to_id_usage_rule("generic_descriptor_allowed")
    assert (r.base_required, r.outlook_required, r.outlook_forbidden) == (False, False, True)


def test_policy_to_id_usage_rule_invalid():
    from app.core.subject_reference_policy import policy_to_id_usage_rule
    with pytest.raises(AppError) as exc:
        policy_to_id_usage_rule("bogus")
    assert "invalid_enum_policy" in exc.value.code


# ---------------------------------------------------------------------------
# Phase 3 W1 — base_forbidden flag on IdUsageRule
# ---------------------------------------------------------------------------

def test_id_usage_rule_generic_descriptor_allowed_flags():
    """generic_descriptor_allowed → base_forbidden=True, others as expected."""
    from app.core.subject_reference_policy import policy_to_id_usage_rule
    r = policy_to_id_usage_rule("generic_descriptor_allowed")
    assert r.base_required is False
    assert r.outlook_required is False
    assert r.outlook_forbidden is True
    assert r.base_forbidden is True


def test_id_usage_rule_base_id_required_base_forbidden_false():
    """base_id_required → base_forbidden=False (bare C## must still pass)."""
    from app.core.subject_reference_policy import policy_to_id_usage_rule
    r = policy_to_id_usage_rule("base_id_required")
    assert r.base_forbidden is False


def test_id_usage_rule_id_and_outlook_required_base_forbidden_false():
    """id_and_outlook_required → base_forbidden=False."""
    from app.core.subject_reference_policy import policy_to_id_usage_rule
    r = policy_to_id_usage_rule("id_and_outlook_required")
    assert r.base_forbidden is False
