"""Error hierarchy for the generic client pattern."""

from __future__ import annotations

from fatum.client.types import RequestID, ResponseBody, StatusCode


class ClientError(Exception):
    """Base exception for all client-related errors.

    All client exceptions inherit from this class, making it easy to catch
    all client errors with a single except block when needed.

    Parameters
    ----------
    message : str
        Human-readable error message.
    status_code : int | None, optional
        HTTP status code if available (default: None).
    response_body : Any | None, optional
        Raw response body for debugging (default: None).
    request_id : str | None, optional
        Request ID for tracing and support (default: None).

    Attributes
    ----------
    status_code : int | None
        The HTTP status code associated with the error.
    response_body : Any | None
        The raw response body from the server.
    request_id : str | None
        Unique request identifier for debugging.

    Examples
    --------
    >>> try:
    ...     # API call that might fail
    ...     response = await client.call_api()
    ... except ClientError as e:
    ...     print(f"Error: {e}")
    ...     if e.request_id:
    ...         print(f"Request ID for support: {e.request_id}")
    """

    def __init__(
        self,
        message: str,
        *,
        status_code: StatusCode | None = None,
        response_body: ResponseBody = None,
        request_id: RequestID = None,
    ) -> None:
        super().__init__(message)
        self.status_code = status_code
        self.response_body = response_body
        self.request_id = request_id

    def __str__(self) -> str:
        """Return string representation of the error.

        Returns
        -------
        str
            Formatted error message including status code and request ID if available.

        Examples
        --------
        >>> error = ClientError("API failure", status_code=500, request_id="abc-123")
        >>> str(error)
        'API failure | Status: 500 | Request ID: abc-123'
        """
        parts = [super().__str__()]
        if self.status_code is not None:
            parts.append(f"Status: {self.status_code}")
        if self.request_id is not None:
            parts.append(f"Request ID: {self.request_id}")
        return " | ".join(parts)


class RetryableError(ClientError):
    """Errors that should trigger automatic retry with backoff.

    This exception indicates a transient failure that may succeed if retried.
    The client framework will automatically retry these errors using exponential
    backoff with jitter.

    Covered Status Codes
    --------------------
    - 408: Request Timeout
    - 429: Too Many Requests (rate limiting)
    - 502: Bad Gateway
    - 503: Service Unavailable
    - 504: Gateway Timeout

    Also Covers
    -----------
    - Network errors (connection failures, DNS resolution)
    - Timeout errors (read/write timeouts)
    - Temporary server issues

    Examples
    --------
    >>> # This error will be automatically retried
    >>> raise RetryableError(
    ...     "Service temporarily unavailable",
    ...     status_code=503,
    ...     request_id="xyz-789"
    ... )

    Notes
    -----
    The retry behavior is configured in the BaseClient._request method
    using the tenacity library with exponential backoff.
    """

    pass


class PermanentError(ClientError):
    """Errors that should not be retried.

    This exception indicates a permanent failure that will not succeed even if
    retried. The client should handle these errors appropriately, such as by
    prompting for new credentials or fixing request parameters.

    Covered Status Codes
    --------------------
    - 400: Bad Request
    - 401: Unauthorized (authentication required)
    - 403: Forbidden (insufficient permissions)
    - 404: Not Found
    - 405: Method Not Allowed
    - 406: Not Acceptable
    - 409: Conflict
    - 410: Gone
    - 422: Unprocessable Entity (validation error)

    Examples
    --------
    >>> # This error will not be retried
    >>> raise PermanentError(
    ...     "Invalid API key",
    ...     status_code=401,
    ...     response_body={"error": "Invalid authentication credentials"}
    ... )

    Notes
    -----
    These errors typically require user intervention to resolve, such as
    updating authentication credentials or fixing request parameters.
    """

    pass


class ConfigurationError(ClientError):
    """Errors related to client configuration.

    This exception is raised when the client is misconfigured or when
    configuration validation fails. These errors should be fixed before
    the client can be used.

    Common Causes
    -------------
    - Missing required configuration fields
    - Invalid base URL format
    - Invalid timeout values (negative or zero)
    - Incompatible configuration combinations
    - Missing API credentials

    Examples
    --------
    >>> # Raised during client initialization
    >>> raise ConfigurationError(
    ...     "Missing required API key in configuration"
    ... )
    >>>
    >>> # Raised for invalid values
    >>> if config.timeout <= 0:
    ...     raise ConfigurationError(
    ...         f"Invalid timeout value: {config.timeout}"
    ...     )

    Notes
    -----
    Configuration errors are typically caught during client initialization,
    preventing runtime failures due to misconfiguration.
    """

    pass
