"""Watcher contract schema validation — task-2643 Track D.

schemas/watcher_contract_v1.json + schemas/watcher_dead_letter_v1.json
+ tests/fixtures/watcher_contract/delegated_watcher_allow/ 정합 검증.

- 9 필수 필드 모두 존재
- owner 가 ANU 본체 식별자면 fail
- collector_role 이 "ANU" 가 아니면 fail
- callback_target.owner_key == "c119085addb0f8b7"
- dead-letter state enum 4종
"""

import json
import sys
from pathlib import Path

_WT = Path(__file__).resolve().parents[2]
if str(_WT) not in sys.path:
    sys.path.insert(0, str(_WT))

CONTRACT_SCHEMA_PATH = _WT / "schemas" / "watcher_contract_v1.json"
DEAD_LETTER_SCHEMA_PATH = _WT / "schemas" / "watcher_dead_letter_v1.json"
ALLOW_FIXTURE_DIR = _WT / "tests" / "fixtures" / "watcher_contract" / "delegated_watcher_allow"

REQUIRED_FIELDS = [
    "schema",
    "task_id",
    "pr_number",
    "head_sha",
    "terminal_states",
    "ttl_seconds",
    "callback_target",
    "duplicate_policy",
    "owner",
    "collector_role",
]

ANU_SELF_IDENTIFIERS = {"anu", "ANU", "anu_main", "ANU_MAIN", "anu_session", "ANU_SESSION"}

TERMINAL_STATES_5 = {
    "MERGE_READY",
    "CHAIR_REQUIRED",
    "GEMINI_EXTERNAL_TRIGGER_STALE",
    "CI_FAILED_NON_REMEDIABLE",
    "LOOP_BOUNDARY",
}

DEAD_LETTER_STATES_4 = {
    "WATCHER_TIMEOUT_HOLD",
    "WATCHER_CALLBACK_MISSING",
    "WATCHER_STALE_HEAD",
    "DUPLICATE_WATCHER",
}


def _load_schema(p: Path):
    return json.loads(p.read_text(encoding="utf-8"))


def test_contract_schema_required_fields():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    assert schema["$id"] == "anu.watcher_contract.v1"
    assert set(schema["required"]) == set(REQUIRED_FIELDS), (
        f"required fields mismatch: got={schema['required']}"
    )


def test_contract_schema_terminal_states_enum_is_5():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    enum = set(schema["properties"]["terminal_states"]["items"]["enum"])
    assert enum == TERMINAL_STATES_5


def test_contract_schema_owner_blocks_anu_identifiers():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    owner_not = schema["properties"]["owner"]["not"]["pattern"]
    # 패턴이 ANU 본체 식별자를 모두 차단해야 한다
    import re
    pat = re.compile(owner_not)
    for ident in ANU_SELF_IDENTIFIERS:
        assert pat.match(ident), f"owner pattern fails to block {ident!r}"


def test_contract_schema_collector_role_is_ANU_only():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    assert schema["properties"]["collector_role"]["enum"] == ["ANU"]


def test_contract_schema_owner_key_anu_constant():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    assert (
        schema["properties"]["callback_target"]["properties"]["owner_key"]["const"]
        == "c119085addb0f8b7"
    )


def test_dead_letter_schema_4_states():
    schema = _load_schema(DEAD_LETTER_SCHEMA_PATH)
    enum = set(schema["properties"]["dead_letter_state"]["enum"])
    assert enum == DEAD_LETTER_STATES_4


def test_dead_letter_schema_fail_closed_always_true():
    schema = _load_schema(DEAD_LETTER_SCHEMA_PATH)
    assert schema["properties"]["fail_closed"]["const"] is True
    assert schema["properties"]["chair_report_required"]["const"] is True


def test_allow_fixture_passes_contract_assertions():
    evidence = json.loads((ALLOW_FIXTURE_DIR / "evidence.json").read_text(encoding="utf-8"))
    expected = json.loads((ALLOW_FIXTURE_DIR / "expected.json").read_text(encoding="utf-8"))
    contract = evidence["contract"]

    for field in REQUIRED_FIELDS:
        assert field in contract, f"missing required field {field}"

    assert contract["owner"] not in ANU_SELF_IDENTIFIERS
    assert contract["collector_role"] == "ANU"
    assert contract["callback_target"]["owner_key"] == "c119085addb0f8b7"
    assert contract["callback_only_reporting"] is True
    assert 0 < contract["ttl_seconds"] <= 7200
    assert len(contract["head_sha"]) == 40
    assert set(contract["terminal_states"]).issubset(TERMINAL_STATES_5)

    # expected.json 정합
    assert expected["validation_result"] == "valid"
    assert expected["anti_impersonation_assertions"]["callback_owner_key_anu"] == (
        "c119085addb0f8b7"
    )


def test_contract_schema_example_self_validates_required():
    schema = _load_schema(CONTRACT_SCHEMA_PATH)
    for ex in schema.get("examples", []):
        for field in REQUIRED_FIELDS:
            assert field in ex, f"schema example missing {field}"
        assert ex["owner"] not in ANU_SELF_IDENTIFIERS
        assert ex["collector_role"] == "ANU"


def test_dead_letter_schema_example_self_validates():
    schema = _load_schema(DEAD_LETTER_SCHEMA_PATH)
    for ex in schema.get("examples", []):
        assert ex["dead_letter_state"] in DEAD_LETTER_STATES_4
        assert ex["fail_closed"] is True
        assert ex["chair_report_required"] is True
