"""
Error classes for the ARP SDK.

Provides a hierarchy of typed exceptions that map to HTTP status codes
returned by the ARP API.
"""

from __future__ import annotations

from typing import Any


class ARPError(Exception):
    """Base error class for all ARP SDK errors."""

    def __init__(
        self,
        code: str,
        message: str,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message)
        self.code = code
        self.message = message
        self.details = details or {}

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(code={self.code!r}, message={self.message!r})"


class AuthError(ARPError):
    """Raised when authentication or authorization fails (401/403)."""

    def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
        super().__init__("AUTH_ERROR", message, details)


class NotFoundError(ARPError):
    """Raised when the requested resource is not found (404)."""

    def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
        super().__init__("NOT_FOUND", message, details)


class ValidationError(ARPError):
    """Raised when request validation fails (400/422)."""

    def __init__(
        self,
        message: str,
        details: dict[str, Any] | None = None,
        field_errors: dict[str, list[str]] | None = None,
    ) -> None:
        super().__init__("VALIDATION_ERROR", message, details)
        self.field_errors = field_errors or {}


class RateLimitError(ARPError):
    """Raised when the rate limit is exceeded (429)."""

    def __init__(
        self,
        message: str,
        retry_after: float | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__("RATE_LIMITED", message, details)
        self.retry_after = retry_after


class ConflictError(ARPError):
    """Raised when a resource conflict occurs (409)."""

    def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
        super().__init__("CONFLICT", message, details)


class ServerError(ARPError):
    """Raised when the server encounters an internal error (500+)."""

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


class TimeoutError(ARPError):
    """Raised when a request times out."""

    def __init__(self, message: str = "Request timed out") -> None:
        super().__init__("TIMEOUT", message)


def from_response(status: int, body: dict[str, Any] | str | None) -> ARPError:
    """
    Factory function to create the appropriate error from an HTTP response.
    """
    parsed: dict[str, Any] | None
    if isinstance(body, str):
        parsed = _try_parse_json(body)
    elif isinstance(body, dict):
        parsed = body
    else:
        parsed = None

    message = _extract_message(parsed, status)
    details = parsed if parsed is not None else {}

    if status in (400, 422):
        field_errors = parsed.get("fieldErrors") if parsed else None
        return ValidationError(message, details, field_errors)
    elif status in (401, 403):
        return AuthError(message, details)
    elif status == 404:
        return NotFoundError(message, details)
    elif status == 409:
        return ConflictError(message, details)
    elif status == 429:
        retry_after = parsed.get("retryAfter") if parsed else None
        return RateLimitError(message, retry_after, details)
    elif status >= 500:
        return ServerError(message, status, details)
    else:
        return ARPError("UNKNOWN_ERROR", message, details)


def _try_parse_json(text: str) -> dict[str, Any] | None:
    import json

    try:
        result = json.loads(text)
        if isinstance(result, dict):
            return result
        return None
    except (json.JSONDecodeError, ValueError):
        return None


def _extract_message(body: dict[str, Any] | None, status: int) -> str:
    if body:
        msg = body.get("message")
        if isinstance(msg, str):
            return msg
        err = body.get("error")
        if isinstance(err, str):
            return err
    return f"HTTP Error {status}"
