"""Exception hierarchy for the MLD API client.

Maps HTTP status codes to typed exceptions. Separate from the plugin
exception hierarchy in mld_sdk.exceptions — these are for REST client errors.

Exception Hierarchy:
    MLDAPIError
    ├── AuthenticationError     - 401 Unauthorized
    ├── MLDPermissionError      - 403 Forbidden
    ├── NotFoundError           - 404 Not Found
    ├── ConflictError           - 409 Conflict
    ├── MLDValidationError      - 422 Unprocessable Entity
    ├── RateLimitError          - 429 Too Many Requests
    ├── ServerError             - 5xx Server Error
    └── MLDConnectionError      - Network / timeout failures
"""

from __future__ import annotations

from typing import Any


class MLDAPIError(Exception):
    """Base exception for all MLD API client errors."""

    def __init__(
        self,
        message: str,
        *,
        status_code: int | None = None,
        body: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message)
        self.message = message
        self.status_code = status_code
        self.body = body

    def __str__(self) -> str:
        if self.status_code:
            return f"[{self.status_code}] {self.message}"
        return self.message


class AuthenticationError(MLDAPIError):
    """401 — Invalid, missing, or expired credentials."""

    def __init__(self, message: str = "Authentication required", **kwargs: Any) -> None:
        super().__init__(message, status_code=401, **kwargs)


class MLDPermissionError(MLDAPIError):
    """403 — Insufficient permissions for the requested operation."""

    def __init__(self, message: str = "Permission denied", **kwargs: Any) -> None:
        super().__init__(message, status_code=403, **kwargs)


class NotFoundError(MLDAPIError):
    """404 — Requested resource does not exist."""

    def __init__(self, message: str = "Resource not found", **kwargs: Any) -> None:
        super().__init__(message, status_code=404, **kwargs)


class ConflictError(MLDAPIError):
    """409 — Resource conflict (duplicate, state conflict, etc.)."""

    def __init__(self, message: str = "Resource conflict", **kwargs: Any) -> None:
        super().__init__(message, status_code=409, **kwargs)


class MLDValidationError(MLDAPIError):
    """422 — Request validation failed."""

    def __init__(self, message: str = "Validation error", **kwargs: Any) -> None:
        super().__init__(message, status_code=422, **kwargs)


class RateLimitError(MLDAPIError):
    """429 — Too many requests."""

    def __init__(self, message: str = "Rate limit exceeded", **kwargs: Any) -> None:
        super().__init__(message, status_code=429, **kwargs)


class ServerError(MLDAPIError):
    """5xx — Server-side error."""

    def __init__(self, message: str = "Server error", **kwargs: Any) -> None:
        super().__init__(message, status_code=kwargs.pop("status_code", 500), **kwargs)


class MLDConnectionError(MLDAPIError):
    """Network or timeout failure."""

    def __init__(self, message: str = "Connection failed", **kwargs: Any) -> None:
        super().__init__(message, status_code=None, **kwargs)


# Status code → exception class mapping
_STATUS_MAP: dict[int, type[MLDAPIError]] = {
    401: AuthenticationError,
    403: MLDPermissionError,
    404: NotFoundError,
    409: ConflictError,
    422: MLDValidationError,
    429: RateLimitError,
}


def raise_for_status(status_code: int, body: dict[str, Any] | None = None) -> None:
    """Raise the appropriate MLDAPIError for an HTTP status code.

    Does nothing for 2xx status codes.
    """
    if 200 <= status_code < 300:
        return

    detail = ""
    if body:
        detail = body.get("detail", body.get("message", ""))

    exc_cls = _STATUS_MAP.get(status_code)
    if exc_cls:
        raise exc_cls(detail or exc_cls.__doc__ or "", body=body)

    if 500 <= status_code < 600:
        raise ServerError(detail or "Server error", status_code=status_code, body=body)

    raise MLDAPIError(detail or f"HTTP {status_code}", status_code=status_code, body=body)
