"""Schema registry + payload identification + validation — the contract authority.

Loads every ``*.schema.json`` shipped as package data, wires them into one
``referencing`` registry so cross-file ``$ref`` resolves, and dispatches any
payload to its schema. Self-describing documents carry ``{schema_version, kind}``;
ephemeral live-wire events carry ``{"v": ...}`` and are discriminated by their
``session`` (fed) vs ``run_id`` (test) field.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from functools import cache, lru_cache
from importlib.resources import files
from typing import TYPE_CHECKING, Any

from jsonschema import Draft202012Validator
from referencing import Registry, Resource

from .states import SUPPORTED_SCHEMA_VERSION, SUPPORTED_WIRE_VERSION

if TYPE_CHECKING:
    from jsonschema.protocols import Validator

BASE_URI = "https://schemas.magogi.org/fedres/0.1/"

#: Documents that self-describe via ``{schema_version, kind}``.
DOC_KINDS: frozenset[str] = frozenset(
    {
        "manifest",
        "status",
        "roadmap",
        "architecture",
        "focus",
        "suggestions",
        "federation",
        "test_catalog",
        "run_record",
        "test_run_record",
        "feedback",
        "response",
        "test_status",
    }
)
FED_EVENT_ID = BASE_URI + "live_event_fed.schema.json"
TEST_EVENT_ID = BASE_URI + "live_event_test.schema.json"


def _schema_id_for_kind(kind: str) -> str:
    return f"{BASE_URI}{kind}.schema.json"


@lru_cache(maxsize=1)
def _load() -> tuple[dict[str, dict[str, Any]], Registry]:
    docs: dict[str, dict[str, Any]] = {}
    schema_dir = files("mgf.fedres") / "schemas"
    for entry in schema_dir.iterdir():
        if entry.name.endswith(".schema.json"):
            doc = json.loads(entry.read_text(encoding="utf-8"))
            docs[doc["$id"]] = doc
    registry = Registry().with_resources(
        [(uri, Resource.from_contents(doc)) for uri, doc in docs.items()]
    )
    return docs, registry


@cache
def _validator(schema_id: str) -> Validator:
    docs, registry = _load()
    return Draft202012Validator(
        docs[schema_id],
        registry=registry,
        format_checker=Draft202012Validator.FORMAT_CHECKER,
    )


@dataclass(frozen=True)
class Identity:
    """The schema a payload maps to, or a ``reason`` it can't be read (→ UNREADABLE)."""

    schema_id: str | None
    reason: str | None

    @property
    def ok(self) -> bool:
        return self.schema_id is not None


def identify(payload: Any) -> Identity:
    """Map a payload to its schema id, fail-closed on anything unrecognized/unsupported."""
    if not isinstance(payload, dict):
        return Identity(None, "payload is not a JSON object")
    if "schema_version" in payload and "kind" in payload:
        kind = payload["kind"]
        if kind not in DOC_KINDS:
            return Identity(None, f"unknown kind {kind!r}")
        version = payload.get("schema_version")
        if version != SUPPORTED_SCHEMA_VERSION:
            return Identity(None, f"unsupported schema_version {version!r}")
        return Identity(_schema_id_for_kind(kind), None)
    if "v" in payload:
        version = payload.get("v")
        if version != SUPPORTED_WIRE_VERSION:
            return Identity(None, f"unsupported wire version {version!r}")
        if "session" in payload:
            return Identity(FED_EVENT_ID, None)
        if "run_id" in payload:
            return Identity(TEST_EVENT_ID, None)
        return Identity(None, "ambiguous live event (missing session/run_id discriminator)")
    return Identity(None, "unrecognized payload (expected schema_version+kind, or v)")


def validate_payload(payload: Any) -> list[str]:
    """Validate one payload against its schema. Empty list == valid; fail-closed otherwise."""
    ident = identify(payload)
    if not ident.ok:
        return [ident.reason or "unidentifiable payload"]
    validator = _validator(ident.schema_id)
    errors = sorted(validator.iter_errors(payload), key=lambda e: list(e.path))
    return [_format_error(e) for e in errors]


def _format_error(error: Any) -> str:
    location = "/".join(str(part) for part in error.path) or "<root>"
    return f"{location}: {error.message}"
