"""Ticksupply API exceptions.

This module defines the exception hierarchy for the Ticksupply client.
All exceptions inherit from TicksupplyError.
"""

from __future__ import annotations


class TicksupplyError(Exception):
    """Base exception for all Ticksupply client errors."""

    pass


class APIError(TicksupplyError):
    """Server returned an error response.

    Attributes:
        code: Error code from the API (e.g., "not_found", "rate_limited").
        message: Human-readable error message.
        http_status: HTTP status code.
        trace_id: Optional trace ID for debugging.
    """

    def __init__(
        self,
        code: str,
        message: str,
        http_status: int,
        trace_id: str | None = None,
    ) -> None:
        self.code = code
        self.message = message
        self.http_status = http_status
        self.trace_id = trace_id
        super().__init__(self._format_message())

    def _format_message(self) -> str:
        msg = f"[{self.code}] {self.message}"
        if self.trace_id:
            msg += f" (trace_id: {self.trace_id})"
        return msg


class AuthenticationError(APIError):
    """401 Unauthenticated - Invalid or missing API key."""

    pass


class PermissionDeniedError(APIError):
    """403 Permission Denied - No access to the requested resource."""

    pass


class NotFoundError(APIError):
    """404 Not Found - The requested resource does not exist."""

    pass


class ConflictError(APIError):
    """409 Conflict - Resource already exists or state conflict."""

    pass


class RateLimitError(APIError):
    """429 Rate Limited - Too many requests.

    Attributes:
        retry_after: Seconds to wait before retrying (from Retry-After header).
    """

    def __init__(
        self,
        code: str,
        message: str,
        http_status: int,
        trace_id: str | None = None,
        retry_after: int | None = None,
    ) -> None:
        self.retry_after = retry_after
        super().__init__(code, message, http_status, trace_id)

    def _format_message(self) -> str:
        msg = super()._format_message()
        if self.retry_after:
            msg += f" (retry after {self.retry_after}s)"
        return msg


class PaymentRequiredError(APIError):
    """402 Payment Required - Billing action denied."""

    pass


class InvalidArgumentError(APIError):
    """400 Bad Request - Invalid request parameters."""

    pass


class ServerError(APIError):
    """5xx Server Error - Server-side error."""

    pass


class NetworkError(TicksupplyError):
    """Network-related errors (connection failures, timeouts)."""

    def __init__(self, message: str, cause: Exception | None = None) -> None:
        self.cause = cause
        super().__init__(message)


# Mapping from API error codes to exception classes
ERROR_CODE_MAP: dict[str, type[APIError]] = {
    "invalid_argument": InvalidArgumentError,
    "unauthenticated": AuthenticationError,
    "permission_denied": PermissionDeniedError,
    "not_found": NotFoundError,
    "already_exists": ConflictError,
    "rate_limited": RateLimitError,
    "payment_required": PaymentRequiredError,
    "internal": ServerError,
    "unavailable": ServerError,
}


def raise_for_error_response(
    http_status: int,
    code: str,
    message: str,
    trace_id: str | None = None,
    retry_after: int | None = None,
) -> None:
    """Raise the appropriate exception for an API error response.

    Args:
        http_status: HTTP status code.
        code: Error code from the API.
        message: Error message from the API.
        trace_id: Optional trace ID.
        retry_after: Optional retry-after seconds (for rate limiting).

    Raises:
        APIError: The appropriate subclass based on the error code.
    """
    exc_class = ERROR_CODE_MAP.get(code, APIError)

    if exc_class is RateLimitError:
        raise RateLimitError(
            code=code,
            message=message,
            http_status=http_status,
            trace_id=trace_id,
            retry_after=retry_after,
        )
    else:
        raise exc_class(
            code=code,
            message=message,
            http_status=http_status,
            trace_id=trace_id,
        )
