# -*- coding: utf-8 -*-
"""tests.regression.test_gate_snapshot_validator — task-2637.

11종 gate 측정 + STALE_SNAPSHOT TTL 단언.

Spec: memory/specs/system_real_merge_executor_wiring_spec_260523.md §4
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

from utils.gate_snapshot_validator import (
    GATE_FAIL,
    GATE_INVALID_SCHEMA,
    GATE_OK,
    GATE_SNAPSHOT_SCHEMA,
    GATE_SNAPSHOT_TTL_SECONDS,
    GATE_STALE,
    REQUIRED_GATE_NAMES,
    is_snapshot_fresh,
    validate_gate_snapshot,
)

KST = timezone(timedelta(hours=9))


def _clock(year=2026, month=5, day=23, hour=12, minute=0):
    dt = datetime(year, month, day, hour, minute, tzinfo=KST)
    return lambda: dt


def _gate_ts():
    return "2026-05-23T11:59:00+09:00"


def _ok_gates():
    return [
        {"name": "ci_checks", "conclusion": "success", "count": "11/11", "source": "gh", "ts": _gate_ts()},
        {"name": "gemini_review_gate", "conclusion": "PASS", "source": "gh", "ts": _gate_ts()},
        {"name": "phase3_merge_gate", "conclusion": "PASS", "source": "gh", "ts": _gate_ts()},
        {"name": "unresolved_threads", "value": 0, "total": 3, "source": "gh", "ts": _gate_ts()},
        {"name": "mergeStateStatus", "value": "CLEAN", "mergeable": "MERGEABLE", "source": "gh", "ts": _gate_ts()},
        {"name": "critical7_hits", "value": 0, "breakdown": {}, "source": "classifier", "ts": _gate_ts()},
        {"name": "blocking_secret", "value": 0, "net_new_identifier_exposure": 0, "source": "scan", "ts": _gate_ts()},
        {"name": "expected_files_exact", "value": True, "source": "scope", "ts": _gate_ts()},
        {"name": "forbidden_path", "value": 0, "checked_files": [], "source": "guard", "ts": _gate_ts()},
        {"name": "admin_override_required", "value": False, "source": "non-admin", "ts": _gate_ts()},
        {"name": "callback_lifecycle_artifact", "value": "normal", "delivery_outcome": "DELIVERED", "ts": _gate_ts()},
    ]


def _snapshot(**overrides):
    snap = {
        "schema": GATE_SNAPSHOT_SCHEMA,
        "ts_kst": _gate_ts(),
        "snapshot_ttl_seconds": GATE_SNAPSHOT_TTL_SECONDS,
        "gates": _ok_gates(),
        "all_pass": True,
    }
    snap.update(overrides)
    return snap


def test_happy_path_returns_GATE_OK():
    ok, code, reasons = validate_gate_snapshot(_snapshot(), clock=_clock())
    assert ok is True
    assert code == GATE_OK
    assert reasons == []


def test_wrong_schema_returns_GATE_INVALID_SCHEMA():
    snap = _snapshot()
    snap["schema"] = "wrong.schema.v1"
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_INVALID_SCHEMA


def test_missing_required_gate_returns_GATE_INVALID_SCHEMA():
    snap = _snapshot()
    snap["gates"] = [g for g in snap["gates"] if g["name"] != "blocking_secret"]
    ok, code, reasons = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_INVALID_SCHEMA
    assert any("blocking_secret" in r for r in reasons)


def test_extra_unexpected_gate_returns_GATE_INVALID_SCHEMA():
    snap = _snapshot()
    snap["gates"].append({"name": "bogus_extra_gate", "value": True})
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_INVALID_SCHEMA


def test_ttl_5_min_stale_snapshot_returns_GATE_STALE():
    snap = _snapshot(ts_kst="2026-05-23T11:30:00+09:00")  # 30 min before now=12:00
    ok, code, reasons = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_STALE
    assert any("ttl" in r.lower() for r in reasons)


def test_ttl_5_min_exact_boundary_passes():
    # ts = 11:55, now = 12:00 → age = 300s == TTL → not stale
    snap = _snapshot(ts_kst="2026-05-23T11:55:00+09:00")
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is True
    assert code == GATE_OK


def test_admin_override_required_returns_GATE_FAIL():
    snap = _snapshot()
    for g in snap["gates"]:
        if g["name"] == "admin_override_required":
            g["value"] = True
    snap["all_pass"] = False
    ok, code, reasons = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_FAIL
    assert any("admin_override_required" in r for r in reasons)


def test_blocking_secret_value_nonzero_returns_GATE_FAIL():
    snap = _snapshot()
    for g in snap["gates"]:
        if g["name"] == "blocking_secret":
            g["value"] = 3
            g["net_new_identifier_exposure"] = 3
    snap["all_pass"] = False
    ok, code, reasons = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_FAIL
    assert any("blocking_secret" in r for r in reasons)


def test_unresolved_threads_nonzero_returns_GATE_FAIL():
    snap = _snapshot()
    for g in snap["gates"]:
        if g["name"] == "unresolved_threads":
            g["value"] = 2
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_FAIL


def test_merge_state_dirty_returns_GATE_FAIL():
    snap = _snapshot()
    for g in snap["gates"]:
        if g["name"] == "mergeStateStatus":
            g["value"] = "DIRTY"
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_FAIL


def test_callback_lifecycle_incident_returns_GATE_FAIL():
    snap = _snapshot()
    for g in snap["gates"]:
        if g["name"] == "callback_lifecycle_artifact":
            g["value"] = "incident"
    ok, code, _ = validate_gate_snapshot(snap, clock=_clock())
    assert ok is False
    assert code == GATE_FAIL


def test_is_snapshot_fresh_predicate():
    fresh = _snapshot(ts_kst="2026-05-23T11:59:00+09:00")
    stale = _snapshot(ts_kst="2026-05-23T11:30:00+09:00")
    assert is_snapshot_fresh(fresh, clock=_clock()) is True
    assert is_snapshot_fresh(stale, clock=_clock()) is False


def test_eleven_gate_names_complete():
    assert len(REQUIRED_GATE_NAMES) == 11
    # Deterministic order (spec §4 Table).
    assert REQUIRED_GATE_NAMES[0] == "ci_checks"
    assert REQUIRED_GATE_NAMES[-1] == "callback_lifecycle_artifact"


def test_chair_5_min_ttl_constant_is_300():
    # 회장 결정 #3.
    assert GATE_SNAPSHOT_TTL_SECONDS == 300
