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

verbatim signature · TTL · pr_numbers/head_shas 매칭 단언.

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

from datetime import datetime, timedelta, timezone

from utils.chair_authorization_validator import (
    AUTH_EXPIRED,
    AUTH_HEAD_SHA_MISMATCH,
    AUTH_INVALID_SCHEMA,
    AUTH_MISSING,
    AUTH_OK,
    AUTH_PR_MISMATCH,
    AUTH_SIGNATURE_MISSING,
    AUTH_TTL_TOO_LARGE,
    CHAIR_AUTH_MAX_TTL_HOURS,
    CHAIR_AUTH_SCHEMA,
    is_authorized,
    validate_chair_authorization,
)

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 _valid_auth(pr=200, head="abc" + "0" * 37, expires="2026-05-23T13:00:00+09:00",
                issued="2026-05-23T11:00:00+09:00"):
    return {
        "schema": CHAIR_AUTH_SCHEMA,
        "scope": "per_pr",
        "pr_numbers": [pr],
        "head_shas": [head],
        "expires_at_kst": expires,
        "issued_at_kst": issued,
        "chair_signature": "회장 verbatim chair token v1",
        "task_id": "task-2637-ok",
    }


def _pid(pr=200, head="abc" + "0" * 37):
    return {"pr": pr, "head_sha": head, "task_id": "task-2637-pid"}


def test_missing_authorization_returns_AUTH_MISSING():
    ok, code, reasons = validate_chair_authorization(None, _pid())
    assert ok is False
    assert code == AUTH_MISSING
    assert reasons


def test_non_dict_authorization_returns_AUTH_MISSING():
    ok, code, _ = validate_chair_authorization("not a dict", _pid())
    assert ok is False
    assert code == AUTH_MISSING


def test_wrong_schema_returns_AUTH_INVALID_SCHEMA():
    auth = _valid_auth()
    auth["schema"] = "something.else.v1"
    ok, code, _ = validate_chair_authorization(auth, _pid(), clock=_clock())
    assert ok is False
    assert code == AUTH_INVALID_SCHEMA


def test_missing_chair_signature_returns_AUTH_SIGNATURE_MISSING():
    auth = _valid_auth()
    auth["chair_signature"] = ""
    ok, code, _ = validate_chair_authorization(auth, _pid(), clock=_clock())
    assert ok is False
    assert code == AUTH_SIGNATURE_MISSING


def test_expired_authorization_returns_AUTH_EXPIRED():
    # now=12:00, expires=11:00 → expired
    auth = _valid_auth(expires="2026-05-23T11:00:00+09:00", issued="2026-05-23T10:00:00+09:00")
    ok, code, reasons = validate_chair_authorization(auth, _pid(), clock=_clock())
    assert ok is False
    assert code == AUTH_EXPIRED
    assert any("expires_at_kst" in r for r in reasons)


def test_ttl_too_large_returns_AUTH_TTL_TOO_LARGE():
    # 48 hour ttl > 24h cap (회장 결정 #1)
    auth = _valid_auth(
        issued="2026-05-23T11:00:00+09:00",
        expires="2026-05-25T11:00:00+09:00",
    )
    ok, code, _ = validate_chair_authorization(
        auth, _pid(), clock=_clock(), max_ttl_hours=CHAIR_AUTH_MAX_TTL_HOURS
    )
    assert ok is False
    assert code == AUTH_TTL_TOO_LARGE


def test_pr_mismatch_returns_AUTH_PR_MISMATCH():
    auth = _valid_auth(pr=200)
    ok, code, _ = validate_chair_authorization(
        auth, _pid(pr=999), clock=_clock()
    )
    assert ok is False
    assert code == AUTH_PR_MISMATCH


def test_head_sha_mismatch_returns_AUTH_HEAD_SHA_MISMATCH():
    auth = _valid_auth(head="aaa" + "0" * 37)
    ok, code, reasons = validate_chair_authorization(
        auth, _pid(head="bbb" + "0" * 37), clock=_clock()
    )
    assert ok is False
    assert code == AUTH_HEAD_SHA_MISMATCH
    assert any("head_shas" in r for r in reasons)


def test_per_pr_scope_requires_exactly_one_pr():
    auth = _valid_auth()
    auth["pr_numbers"] = [200, 201]
    ok, code, _ = validate_chair_authorization(auth, _pid(), clock=_clock())
    assert ok is False
    assert code == AUTH_INVALID_SCHEMA


def test_batch_scope_allows_multiple_prs():
    auth = _valid_auth()
    auth["scope"] = "batch"
    auth["pr_numbers"] = [200, 201, 202]
    auth["head_shas"] = ["abc" + "0" * 37, "def" + "0" * 37, "fed" + "0" * 37]
    ok, code, _ = validate_chair_authorization(
        auth, _pid(pr=201, head="def" + "0" * 37), clock=_clock()
    )
    assert ok is True
    assert code == AUTH_OK


def test_happy_path_returns_AUTH_OK():
    auth = _valid_auth()
    ok, code, reasons = validate_chair_authorization(auth, _pid(), clock=_clock())
    assert ok is True
    assert code == AUTH_OK
    assert reasons == []


def test_is_authorized_convenience_predicate():
    auth = _valid_auth()
    assert is_authorized(auth, _pid(), clock=_clock()) is True
    bad = dict(auth)
    bad["expires_at_kst"] = "2026-05-23T11:00:00+09:00"
    assert is_authorized(bad, _pid(), clock=_clock()) is False


def test_string_pr_id_int_normalization():
    auth = _valid_auth(pr=200)
    auth["pr_numbers"] = ["200"]  # stringified — still ok
    ok, code, _ = validate_chair_authorization(auth, _pid(pr=200), clock=_clock())
    assert ok is True
    assert code == AUTH_OK
