"""Offline overenie inkluzie zapecatenej hlavy v globalnej kotve (vendored).

Samostatna kopia overovacej casti api/app/anchor.py — verifikator je NEZAVISLY od
beziacej appky. Re-derivuje running-hash retazec epoch rootu, overuje KMS podpisy
(offline, verejnym klucom) a TSA token na HLAVE kotvy, a potvrdi, ze dana
(sealed_inspection_id, sealed_head_hash) JE v kotve (FIX #5).

KRITICKE: anchor_core tvar + kanonikalizacia + ISO MUSIA byt bajt-za-bajt zhodne s
api/app/anchor.py (parita zabezpecuju testy v api/tests/test_audit_chain.py).
"""

from __future__ import annotations

import hashlib
import json
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any

ANCHOR_GENESIS = "0" * 64


def _canonical_bytes(obj: Any) -> bytes:  # noqa: ANN401
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode(
        "utf-8"
    )


def _sha256_hex(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def _iso(ts: Any) -> str:  # noqa: ANN401 — datetime alebo ISO string z exportu
    dt: datetime
    if isinstance(ts, str):
        dt = datetime.fromisoformat(ts)
    elif isinstance(ts, datetime):
        dt = ts
    else:
        msg = f"server_ts must be a datetime or ISO string, got {type(ts)!r}"
        raise TypeError(msg)
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=UTC)
    return dt.astimezone(UTC).isoformat().replace("+00:00", "Z")


def _anchor_core(row: dict[str, Any]) -> dict[str, Any]:
    """Re-zostav anchor_core PRESNE ako api/app/anchor.py:_anchor_core."""
    return {
        "seq": int(row["seq"]),
        "sealed_inspection_id": str(row["sealed_inspection_id"]),
        "sealed_head_hash": row["sealed_head_hash"],
        "prev_anchor_hash": row["prev_anchor_hash"],
        "ts": _iso(row["server_ts"]),
    }


@dataclass(frozen=True)
class InclusionResult:
    """Vysledok overenia inkluzie zapecatenej hlavy v globalnej kotve."""

    ok: bool
    reason: str | None = None


def expected_sealed_head(
    rows: Sequence[dict[str, Any]], inspection_id: str
) -> str | None:
    """Vrat sealed_head_hash, ktory kotva (epoch_anchor export) VIAZE pre inspekciu.

    Offline parita s api/app/anchor.py:expected_sealed_head. Toto je IMMUTABLE zdroj
    pravdy pre "musi byt zapecatena+ukotvena" — verifikator ho pouziva NAMIESTO
    mutovatelneho RDS inspections.status (FIX #4 round-3). Vrati hlavu NAJNOVSIEHO
    (najvyssi seq) anchor riadku pre danu inspekciu; None ak kotva inspekciu
    nereferencuje. NEoveruje tu running-hash retazec — to robi verify_inclusion.
    """
    head: str | None = None
    best_seq = -1
    for row in rows:
        if str(row["sealed_inspection_id"]) != inspection_id:
            continue
        seq = int(row["seq"])
        if seq > best_seq:
            best_seq = seq
            head = row["sealed_head_hash"]
    return head


def verify_inclusion(  # noqa: PLR0911, PLR0913 — guard clauses + offline parita s app.anchor
    rows: Sequence[dict[str, Any]],
    sealed_inspection_id: str,
    sealed_head_hash: str,
    *,
    require_signatures: bool,
    offline_kms_verify: Callable[[str, bytes | None], bool],
    verify_tsa: bool,
    tsa_verify: Callable[[str, bytes | None], Any],
) -> InclusionResult:
    """Over inkluziu (rows = epoch_anchor zoradene seq ASC).

    `offline_kms_verify(anchor_hash, signature)->bool` overi KMS podpis verejnym
    klucom; `tsa_verify(anchor_hash, token)->TimestampResult` overi TSA na hlave.
    """
    if not rows:
        return InclusionResult(ok=False, reason="anchor_empty")

    prev = ANCHOR_GENESIS
    found = False
    head_hash: str | None = None
    head_token: bytes | None = None
    for row in rows:
        seq = int(row["seq"])
        if row["prev_anchor_hash"] != prev:
            return InclusionResult(ok=False, reason=f"anchor_link_mismatch:{seq}")
        stored_anchor = row["anchor_hash"]
        if _sha256_hex(_canonical_bytes(_anchor_core(row))) != stored_anchor:
            return InclusionResult(ok=False, reason=f"anchor_hash_mismatch:{seq}")
        if require_signatures and not offline_kms_verify(stored_anchor, row.get("signature")):
            return InclusionResult(ok=False, reason=f"anchor_signature_invalid:{seq}")
        if (
            str(row["sealed_inspection_id"]) == sealed_inspection_id
            and row["sealed_head_hash"] == sealed_head_hash
        ):
            found = True
        prev = stored_anchor
        head_hash = stored_anchor
        head_token = row.get("tsa_token")

    if not found:
        return InclusionResult(ok=False, reason="anchor_inclusion_missing")

    if verify_tsa and head_hash is not None:
        result = tsa_verify(head_hash, head_token)
        if not result.ok:
            return InclusionResult(ok=False, reason=f"anchor_tsa:{result.reason}")

    return InclusionResult(ok=True)
