"""Convexity SDK error hierarchy.

All SDK exceptions inherit from ``ConvexityError`` so callers can catch
the full tree with a single ``except`` clause when convenient.
"""

from __future__ import annotations


class ConvexityError(Exception):
    """Base exception for all Convexity SDK errors."""

    def __init__(
        self,
        message: str,
        *,
        status_code: int | None = None,
        method: str | None = None,
        endpoint: str | None = None,
    ) -> None:
        self.message = message
        self.status_code = status_code
        self.method = method
        self.endpoint = endpoint
        super().__init__(message)

    def __repr__(self) -> str:
        parts = [f"message={self.message!r}"]
        if self.status_code is not None:
            parts.append(f"status_code={self.status_code}")
        if self.method is not None:
            parts.append(f"method={self.method!r}")
        if self.endpoint is not None:
            parts.append(f"endpoint={self.endpoint!r}")
        return f"{type(self).__name__}({', '.join(parts)})"


class AuthenticationError(ConvexityError):
    """401 — invalid or missing API key."""


class PermissionError(ConvexityError):  # noqa: A001 — intentional shadow of builtin
    """403 — insufficient API key permissions."""


class NotFoundError(ConvexityError):
    """404 — requested resource not found."""


class ConflictError(ConvexityError):
    """409 — resource conflict (e.g. duplicate slug)."""


class ValidationError(ConvexityError):
    """422 — invalid request parameters."""


class RateLimitError(ConvexityError):
    """429 — rate limit exceeded."""

    def __init__(
        self,
        message: str,
        *,
        retry_after: float | None = None,
        status_code: int | None = 429,
        method: str | None = None,
        endpoint: str | None = None,
    ) -> None:
        super().__init__(message, status_code=status_code, method=method, endpoint=endpoint)
        self.retry_after = retry_after


class ServerError(ConvexityError):
    """5xx — server-side failure."""


# Mapping from HTTP status codes to exception classes used by the transport layer.
STATUS_CODE_TO_EXCEPTION: dict[int, type[ConvexityError]] = {
    401: AuthenticationError,
    403: PermissionError,
    404: NotFoundError,
    409: ConflictError,
    422: ValidationError,
    429: RateLimitError,
}

#: Status codes that should be automatically retried.
RETRYABLE_STATUS_CODES: frozenset[int] = frozenset({429, 502, 503, 504})
