"""JSON Schema validation with actionable error messages."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import jsonschema
import jsonschema.validators

from abi_control_core.contracts import CONTRACTS_DIR

# Build a validator class with format checking enabled
_VALIDATOR_CLS = jsonschema.validators.validator_for({"$schema": "https://json-schema.org/draft/2020-12/schema"})


class SchemaValidationError(Exception):
    """Raised when JSON instance fails schema validation.

    Attributes:
        errors: List of (json_pointer, message) tuples.
    """

    def __init__(self, errors: list[tuple[str, str]]) -> None:
        self.errors = errors
        summary = "; ".join(f"{ptr}: {msg}" for ptr, msg in errors[:5])
        if len(errors) > 5:
            summary += f" ... and {len(errors) - 5} more"
        super().__init__(f"Schema validation failed ({len(errors)} error(s)): {summary}")


def _json_pointer(path: tuple[str | int, ...]) -> str:
    """Convert a jsonschema path deque to a JSON Pointer (RFC 6901)."""
    if not path:
        return "/"
    parts = []
    for segment in path:
        s = str(segment)
        s = s.replace("~", "~0").replace("/", "~1")
        parts.append(s)
    return "/" + "/".join(parts)


def _load_schema(schema_ref: str | Path) -> dict[str, Any]:
    """Load a schema from a filename or absolute path."""
    path = Path(schema_ref)
    if not path.is_absolute():
        path = CONTRACTS_DIR / path
    with open(path, encoding="utf-8") as f:
        return json.load(f)  # type: ignore[no-any-return]


def validate_json(instance: Any, schema_ref: str | Path) -> None:
    """Validate a JSON instance against a schema.

    Args:
        instance: The Python object to validate (dict, list, etc.).
        schema_ref: Schema filename (looked up in contracts/) or absolute path.

    Raises:
        SchemaValidationError: If validation fails, with JSON pointer paths
            and actionable messages for each error.
        FileNotFoundError: If the schema file does not exist.
    """
    schema = _load_schema(schema_ref)
    validator = _VALIDATOR_CLS(schema)
    raw_errors = sorted(validator.iter_errors(instance), key=lambda e: list(e.absolute_path))

    if not raw_errors:
        return

    errors: list[tuple[str, str]] = []
    for err in raw_errors:
        pointer = _json_pointer(tuple(err.absolute_path))
        msg = err.message
        if err.context:
            sub = "; ".join(sub_err.message for sub_err in err.context[:3])
            msg = f"{msg} ({sub})"
        errors.append((pointer, msg))

    raise SchemaValidationError(errors)
