"""Async HTTP client for Crustdata API."""

from typing import Any

import httpx

from .config import config


class CrustdataAPIError(Exception):
    """Base exception for Crustdata API errors."""

    def __init__(self, message: str, status_code: int | None = None, response_data: Any = None):
        super().__init__(message)
        self.status_code = status_code
        self.response_data = response_data


class CrustdataAuthError(CrustdataAPIError):
    """Authentication error (401)."""

    pass


class CrustdataNotFoundError(CrustdataAPIError):
    """Resource not found error (404)."""

    pass


class CrustdataRateLimitError(CrustdataAPIError):
    """Rate limit exceeded error (429)."""

    pass


class CrustdataServerError(CrustdataAPIError):
    """Server error (5xx)."""

    pass


class CrustdataClient:
    """Async HTTP client for Crustdata API.

    Handles authentication, request/response processing, and error handling
    for all Crustdata API endpoints.
    """

    def __init__(self, api_token: str, base_url: str | None = None, timeout: float | None = None):
        """Initialize the Crustdata API client.

        Args:
            api_token: The API token for authentication.
            base_url: Optional base URL override (defaults to config value).
            timeout: Optional request timeout override (defaults to config value).
        """
        self.api_token = api_token
        self.base_url = (base_url or config.api_url).rstrip("/")
        self.timeout = timeout or config.request_timeout
        self._client: httpx.AsyncClient | None = None

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create the async HTTP client."""
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                headers={
                    "Authorization": f"Token {self.api_token}",
                    "Content-Type": "application/json",
                },
                timeout=self.timeout,
            )
        return self._client

    async def close(self) -> None:
        """Close the HTTP client connection."""
        if self._client is not None:
            await self._client.aclose()
            self._client = None

    def _handle_error(self, response: httpx.Response) -> None:
        """Handle error responses from the API.

        Args:
            response: The HTTP response object.

        Raises:
            CrustdataAuthError: For 401 responses.
            CrustdataNotFoundError: For 404 responses.
            CrustdataRateLimitError: For 429 responses.
            CrustdataServerError: For 5xx responses.
            CrustdataAPIError: For other error responses.
        """
        if response.is_success:
            return

        try:
            response_data = response.json()
        except Exception:
            response_data = response.text

        status_code = response.status_code
        error_message = self._extract_error_message(response_data, status_code)

        if status_code == 401:
            raise CrustdataAuthError(error_message, status_code, response_data)
        elif status_code == 404:
            raise CrustdataNotFoundError(error_message, status_code, response_data)
        elif status_code == 429:
            raise CrustdataRateLimitError(error_message, status_code, response_data)
        elif status_code >= 500:
            raise CrustdataServerError(error_message, status_code, response_data)
        else:
            raise CrustdataAPIError(error_message, status_code, response_data)

    def _extract_error_message(self, response_data: Any, status_code: int) -> str:
        """Extract error message from response data.

        Args:
            response_data: The response data (dict, str, or other).
            status_code: The HTTP status code.

        Returns:
            A human-readable error message.
        """
        if isinstance(response_data, dict):
            if "error" in response_data:
                return str(response_data["error"])
            if "detail" in response_data:
                return str(response_data["detail"])
            if "message" in response_data:
                return str(response_data["message"])
        if isinstance(response_data, str) and response_data:
            return response_data
        return f"API error (status {status_code})"

    async def get(self, endpoint: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
        """Make a GET request to the API.

        Args:
            endpoint: The API endpoint (e.g., "/user/credits").
            params: Optional query parameters.

        Returns:
            The JSON response data.

        Raises:
            CrustdataAPIError: For API errors.
        """
        client = await self._get_client()
        response = await client.get(endpoint, params=params)
        self._handle_error(response)
        return response.json()

    async def post(
        self, endpoint: str, data: dict[str, Any] | None = None, params: dict[str, Any] | None = None
    ) -> dict[str, Any]:
        """Make a POST request to the API.

        Args:
            endpoint: The API endpoint.
            data: Optional JSON body data.
            params: Optional query parameters.

        Returns:
            The JSON response data.

        Raises:
            CrustdataAPIError: For API errors.
        """
        client = await self._get_client()
        response = await client.post(endpoint, json=data, params=params)
        self._handle_error(response)
        return response.json()

    async def __aenter__(self) -> "CrustdataClient":
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self.close()
