"""Load and validate OpenAPI documents from JSON or YAML files."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import yaml
from openapi_spec_validator import validate

from lapis_spec.models.openapi import OpenAPIDict


class OpenAPILoadError(Exception):
    """Raised when an OpenAPI file cannot be loaded or is invalid."""


def load_openapi(path: str | Path, *, skip_validation: bool = False) -> OpenAPIDict:
    """Load an OpenAPI document from a JSON or YAML file.

    Parameters
    ----------
    path:
        Filesystem path to the OpenAPI file (.json, .yaml, .yml).
    skip_validation:
        If ``True``, skip schema validation against the OpenAPI spec.

    Returns
    -------
    OpenAPIDict
        The parsed OpenAPI document as a nested dict.

    Raises
    ------
    OpenAPILoadError
        If the file cannot be read, parsed, or fails validation.
    """
    filepath = Path(path)
    if not filepath.exists():
        raise OpenAPILoadError(f"File not found: {filepath}")

    text = filepath.read_text(encoding="utf-8")

    try:
        data = _parse(filepath.suffix.lower(), text)
    except Exception as exc:
        raise OpenAPILoadError(f"Failed to parse {filepath.name}: {exc}") from exc

    if not isinstance(data, dict):
        raise OpenAPILoadError(f"Expected a mapping at the root of {filepath.name}")

    _check_openapi_version(data)

    if not skip_validation:
        try:
            validate(data)
        except Exception as exc:
            raise OpenAPILoadError(f"OpenAPI validation failed: {exc}") from exc

    return data


def detect_openapi_version(doc: OpenAPIDict) -> str:
    """Return the OpenAPI version string (e.g. '3.0.3' or '3.1.0')."""
    version = doc.get("openapi", "")
    if not isinstance(version, str):
        version = str(version)
    return version


def _parse(suffix: str, text: str) -> Any:
    if suffix == ".json":
        return json.loads(text)
    if suffix in {".yaml", ".yml"}:
        return yaml.safe_load(text)
    raise OpenAPILoadError(f"Unsupported file extension: {suffix} (expected .json, .yaml, .yml)")


def _check_openapi_version(data: dict[str, Any]) -> None:
    version = data.get("openapi", "")
    if not isinstance(version, str):
        version = str(version)
    if not (version.startswith("3.0") or version.startswith("3.1")):
        raise OpenAPILoadError(
            f"Unsupported OpenAPI version: {version!r}. Only 3.0.x and 3.1.x are supported."
        )
