# -*- coding: utf-8 -*-
"""tests.regression.test_snapshot_crossref_validator — task-2639.

7 fixture parametrized 검증 + snapshot crossref 결과 분류 단언.

Spec: memory/specs/system_real_merge_hooks_snapshot_crossref_spec_260523.md §6
sha256: 12b8af006913833596562c55ab9a0acca935830be90c5f17f2af4b7e1e632621

회장 verbatim (spec §6 fixture 7 시나리오 × 3 = 21 files):
    fixture_in_snapshot_pass_candidate              → allow_reason 기록
    fixture_in_snapshot_mismatch_no_op              → unauthorized_forbidden_hits
    fixture_wrong_head_sha                          → sha_match=False
    sanctioned_lock_separated                       → sanctioned_artifacts 분리
    production_in_snapshot_chair_required           → production_in_snapshot
    blocking_secret_in_snapshot_chair_required      → blocking_secret_in_snapshot
    admin_override_required_chair_required          → crossref snapshot side PASS
"""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict

import pytest

from utils.real_merge_hooks import FORBIDDEN_DIR_PREFIXES, FORBIDDEN_PATHS
from utils.snapshot_crossref_validator import (
    ALLOW_REASON_SNAPSHOT_CROSSREF,
    SNAPSHOT_CROSSREF_SCHEMA,
    validate_snapshot_crossref,
)

FIXTURE_ROOT = (
    Path(__file__).resolve().parent.parent / "fixtures" / "snapshot_crossref"
)

SCENARIOS = [
    "fixture_in_snapshot_pass_candidate",
    "fixture_in_snapshot_mismatch_no_op",
    "fixture_wrong_head_sha",
    "sanctioned_lock_separated",
    "production_in_snapshot_chair_required",
    "blocking_secret_in_snapshot_chair_required",
    "admin_override_required_chair_required",
]


def _load(scenario: str) -> Dict[str, Any]:
    base = FIXTURE_ROOT / scenario
    evidence = json.loads((base / "evidence.json").read_text(encoding="utf-8"))
    expected = json.loads((base / "expected.json").read_text(encoding="utf-8"))
    return {"evidence": evidence, "expected": expected}


@pytest.mark.parametrize("scenario", SCENARIOS)
def test_validator_crossref_matches_expected(scenario: str) -> None:
    """각 fixture 의 expected.crossref 필드와 validator 반환값을 정확히 비교."""
    payload = _load(scenario)
    evidence = payload["evidence"]
    expected_crossref = payload["expected"]["crossref"]

    out = validate_snapshot_crossref(
        evidence["pr_identity"],
        evidence["chair_authorization"],
        evidence.get("changed_files"),
        forbidden_paths=list(FORBIDDEN_PATHS),
        forbidden_dir_prefixes=list(FORBIDDEN_DIR_PREFIXES),
    )

    # schema 식별자 단언.
    assert out["schema"] == SNAPSHOT_CROSSREF_SCHEMA

    # pr/sha match boolean 단언.
    assert out["pr_match"] is expected_crossref["pr_match"], scenario
    assert out["sha_match"] is expected_crossref["sha_match"], scenario

    # snapshot_present boolean + snapshot_keys 정렬 단언.
    assert out["snapshot_present"] is expected_crossref["snapshot_present"], scenario
    assert out["snapshot_keys"] == expected_crossref["snapshot_keys"], scenario

    # classification (sorted comparison via set for task_outputs/sanctioned/forbidden).
    exp_clf = expected_crossref["classification"]
    got_clf = out["classification"]
    assert set(got_clf["task_outputs"]) == set(exp_clf["task_outputs"]), scenario
    assert set(got_clf["sanctioned_artifacts"]) == set(
        exp_clf["sanctioned_artifacts"]
    ), scenario
    assert set(got_clf["unauthorized_forbidden_hits"]) == set(
        exp_clf["unauthorized_forbidden_hits"]
    ), scenario
    assert set(got_clf["authorized_forbidden_hits"]) == set(
        exp_clf["authorized_forbidden_hits"]
    ), scenario

    # allow_reason 단언 (snapshot exact match forbidden hit → token, 그 외 None).
    assert out["allow_reason"] == expected_crossref["allow_reason"], scenario
    if expected_crossref["allow_reason"] is not None:
        assert (
            expected_crossref["allow_reason"] == ALLOW_REASON_SNAPSHOT_CROSSREF
        ), scenario

    # snapshot 내 production / secret 검출 단언.
    assert sorted(out["production_in_snapshot"]) == sorted(
        expected_crossref["production_in_snapshot"]
    ), scenario
    assert sorted(out["blocking_secret_in_snapshot"]) == sorted(
        expected_crossref["blocking_secret_in_snapshot"]
    ), scenario


def test_validator_none_changed_files_fail_closed() -> None:
    """changed_files=None → fail-closed sentinel 1건 forbidden hit (spec §3 Step 0a)."""
    out = validate_snapshot_crossref(
        {"pr": 0, "head_sha": ""},
        None,
        None,
        forbidden_paths=list(FORBIDDEN_PATHS),
        forbidden_dir_prefixes=list(FORBIDDEN_DIR_PREFIXES),
    )
    assert out["classification"]["unauthorized_forbidden_hits"] == [
        "__INPUT_NONE_FAIL_CLOSED__"
    ]
    assert out["allow_reason"] is None
    assert out["snapshot_present"] is False


def test_validator_no_chair_authorization_returns_safe_default() -> None:
    """chair_authorization=None → 모든 match False, snapshot 비어있음."""
    out = validate_snapshot_crossref(
        {"pr": 999, "head_sha": "fffffffffffffffffffffffffffffffffffff999"},
        None,
        ["memory/reports/x.md"],
        forbidden_paths=list(FORBIDDEN_PATHS),
        forbidden_dir_prefixes=list(FORBIDDEN_DIR_PREFIXES),
    )
    assert out["pr_match"] is False
    assert out["sha_match"] is False
    assert out["snapshot_present"] is False
    assert out["snapshot_keys"] == []
    assert out["classification"]["task_outputs"] == ["memory/reports/x.md"]


def test_validator_pr_str_normalization() -> None:
    """pr 이 str(숫자) 형태로 들어와도 정상 매칭 (defensive)."""
    out = validate_snapshot_crossref(
        {"pr": "300", "head_sha": "abc"},
        {
            "pr_numbers": ["300"],
            "head_shas": ["abc"],
            "expected_files_snapshot": ["x"],
        },
        ["x"],
        forbidden_paths=[],
        forbidden_dir_prefixes=[],
    )
    assert out["pr_match"] is True
    assert out["sha_match"] is True


def test_validator_broad_allowlist_doctrine_holds() -> None:
    """snapshot exact match 만 허용. 동일 prefix 다른 path 는 unauthorized."""
    out = validate_snapshot_crossref(
        {"pr": 500, "head_sha": "deadbeef"},
        {
            "pr_numbers": [500],
            "head_shas": ["deadbeef"],
            "expected_files_snapshot": ["tests/fixtures/AAA.md"],
        },
        # 다른 fixture 파일 — prefix 는 같지만 exact match 가 아님.
        ["tests/fixtures/BBB.md"],
        forbidden_paths=[],
        forbidden_dir_prefixes=["tests/fixtures/"],
    )
    assert out["classification"]["unauthorized_forbidden_hits"] == [
        "tests/fixtures/BBB.md"
    ]
    assert out["classification"]["authorized_forbidden_hits"] == []
    assert out["allow_reason"] is None
