"""JSON schema loading and validation for roadmap and bits artifacts."""

from __future__ import annotations

import json
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Any, Iterable

from jsonschema import Draft202012Validator
from referencing import Registry, Resource

ROADMAP_SCHEMA_VERSION = "1.0.0"
BITS_SCHEMA_VERSION = "1.0.0"


class JsonSchemaLoadError(RuntimeError):
    """Raised when schema files are invalid or cannot be loaded."""


class JsonSchemaValidationError(ValueError):
    """Raised when a payload fails schema validation."""


@dataclass(frozen=True)
class LoadedSchemas:
    roadmap_validator: Draft202012Validator
    bits_validator: Draft202012Validator


SCHEMA_DIR = Path(__file__).resolve().parents[1] / "schemas"
COMMON_SCHEMA_PATH = SCHEMA_DIR / "common.schema.json"
ROADMAP_SCHEMA_PATH = SCHEMA_DIR / "roadmap.schema.json"
BITS_SCHEMA_PATH = SCHEMA_DIR / "bits.schema.json"


def _load_json(path: Path) -> dict[str, Any]:
    try:
        with path.open("r", encoding="utf-8") as fh:
            return json.load(fh)
    except Exception as exc:
        raise JsonSchemaLoadError(f"Failed to read schema '{path}': {exc}") from exc


def _pointer(path_parts: Iterable[Any]) -> str:
    parts = list(path_parts)
    if not parts:
        return "/"
    escaped = [str(p).replace("~", "~0").replace("/", "~1") for p in parts]
    return "/" + "/".join(escaped)


def _format_error(error: Any) -> str:
    pointer = _pointer(error.path)
    snippet = repr(error.instance)
    if len(snippet) > 140:
        snippet = snippet[:137] + "..."
    return f"- {pointer}: {error.message} | value={snippet}"


@lru_cache(maxsize=1)
def load_schemas() -> LoadedSchemas:
    common_schema = _load_json(COMMON_SCHEMA_PATH)
    roadmap_schema = _load_json(ROADMAP_SCHEMA_PATH)
    bits_schema = _load_json(BITS_SCHEMA_PATH)

    try:
        Draft202012Validator.check_schema(common_schema)
        Draft202012Validator.check_schema(roadmap_schema)
        Draft202012Validator.check_schema(bits_schema)
    except Exception as exc:
        raise JsonSchemaLoadError(f"Schema definition error: {exc}") from exc

    registry = Registry().with_resources(
        [
            (common_schema["$id"], Resource.from_contents(common_schema)),
            (roadmap_schema["$id"], Resource.from_contents(roadmap_schema)),
            (bits_schema["$id"], Resource.from_contents(bits_schema)),
        ]
    )

    return LoadedSchemas(
        roadmap_validator=Draft202012Validator(roadmap_schema, registry=registry),
        bits_validator=Draft202012Validator(bits_schema, registry=registry),
    )


def _validate(data: dict[str, Any], validator: Draft202012Validator, artifact_name: str) -> None:
    errors = sorted(validator.iter_errors(data), key=lambda e: (list(e.path), e.message))
    if not errors:
        return

    details = "\n".join(_format_error(error) for error in errors[:20])
    extra_count = max(0, len(errors) - 20)
    if extra_count:
        details += f"\n- ... and {extra_count} more validation errors"

    raise JsonSchemaValidationError(
        f"{artifact_name} failed JSON schema validation with {len(errors)} error(s):\n{details}"
    )


def validate_roadmap(data: dict[str, Any]) -> None:
    schemas = load_schemas()
    _validate(data, schemas.roadmap_validator, "roadmap")


def validate_bits(data: dict[str, Any]) -> None:
    schemas = load_schemas()
    _validate(data, schemas.bits_validator, "bits")
