"""HTTP transport layer for the Danube SDK."""

import asyncio
import logging
from typing import Any, Dict, Optional

import httpx

from danube.config import DanubeConfig
from danube.exceptions import (
    AuthenticationError,
    AuthorizationError,
    DanubeConnectionError,
    DanubeError,
    DanubeTimeoutError,
    NotFoundError,
    RateLimitError,
    ValidationError,
)

logger = logging.getLogger("danube")


class HTTPClient:
    """Low-level async HTTP client with authentication and error handling.

    This client handles:
    - Authentication header injection
    - Response parsing and error handling
    - Retry logic with exponential backoff
    - Connection management
    """

    def __init__(self, config: DanubeConfig):
        """Initialize the HTTP client.

        Args:
            config: Danube configuration instance.
        """
        self.config = config
        self._client: Optional[httpx.AsyncClient] = None

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create the httpx async client.

        Returns:
            Configured httpx.AsyncClient instance.
        """
        if self._client is None or self._client.is_closed:
            self._client = httpx.AsyncClient(
                base_url=self.config.base_url,
                timeout=self.config.timeout,
                verify=self.config.verify_ssl,
                headers={
                    "danube-api-key": self.config.api_key,
                    "Content-Type": "application/json",
                    "User-Agent": self.config.user_agent,
                },
            )
        return self._client

    async def request(
        self,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        retry_count: int = 0,
    ) -> Dict[str, Any]:
        """Make an authenticated HTTP request.

        Args:
            method: HTTP method (GET, POST, etc.).
            path: API path (will be appended to base_url).
            params: Query parameters.
            json: JSON body for POST/PUT requests.
            retry_count: Current retry attempt (used internally).

        Returns:
            Parsed JSON response as dictionary.

        Raises:
            AuthenticationError: If API key is invalid.
            AuthorizationError: If access is denied.
            NotFoundError: If resource doesn't exist.
            RateLimitError: If rate limit is exceeded.
            DanubeConnectionError: If connection fails.
            DanubeTimeoutError: If request times out.
            DanubeError: For other API errors.
        """
        client = await self._get_client()

        # Filter out None values from params
        if params:
            params = {k: v for k, v in params.items() if v is not None}

        try:
            logger.debug(f"Request: {method} {path} params={params}")
            response = await client.request(
                method=method,
                url=path,
                params=params,
                json=json,
            )
            logger.debug(f"Response: {response.status_code}")

        except httpx.ConnectError as e:
            logger.error(f"Connection error: {e}")
            if retry_count < self.config.max_retries:
                await self._backoff(retry_count)
                return await self.request(method, path, params, json, retry_count + 1)
            raise DanubeConnectionError(f"Failed to connect to Danube API: {e}")

        except httpx.TimeoutException as e:
            logger.error(f"Timeout error: {e}")
            if retry_count < self.config.max_retries:
                await self._backoff(retry_count)
                return await self.request(method, path, params, json, retry_count + 1)
            raise DanubeTimeoutError(f"Request timed out: {e}")

        # Handle response
        return self._handle_response(response, retry_count, method, path, params, json)

    def _handle_response(
        self,
        response: httpx.Response,
        retry_count: int,
        method: str,
        path: str,
        params: Optional[Dict[str, Any]],
        json: Optional[Dict[str, Any]],
    ) -> Dict[str, Any]:
        """Handle HTTP response and raise appropriate exceptions.

        Args:
            response: The httpx response object.
            retry_count: Current retry count.
            method: Original request method.
            path: Original request path.
            params: Original request params.
            json: Original request json body.

        Returns:
            Parsed JSON response.

        Raises:
            Various DanubeError subclasses based on status code.
        """
        if response.status_code == 200:
            try:
                return response.json()
            except Exception:
                # Return empty dict for empty responses
                return {}

        # Parse error detail if available
        try:
            error_body = response.json()
            detail = error_body.get("detail", response.text[:500])
        except Exception:
            detail = response.text[:500] if response.text else "Unknown error"

        logger.warning(f"API error {response.status_code}: {detail}")

        # Map status codes to exceptions
        if response.status_code == 400:
            raise ValidationError(str(detail))
        elif response.status_code == 401:
            raise AuthenticationError(str(detail))
        elif response.status_code == 403:
            raise AuthorizationError(str(detail))
        elif response.status_code == 404:
            raise NotFoundError(message=str(detail))
        elif response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            raise RateLimitError(
                retry_after=int(retry_after) if retry_after else None
            )
        elif response.status_code >= 500:
            # Server errors might be transient, retry
            if retry_count < self.config.max_retries:
                # Note: This is sync context, we can't await here
                # Caller will need to handle retry for 5xx
                pass
            raise DanubeError(str(detail), status_code=response.status_code)
        else:
            raise DanubeError(str(detail), status_code=response.status_code)

    async def _backoff(self, retry_count: int) -> None:
        """Sleep with exponential backoff.

        Args:
            retry_count: Current retry attempt number.
        """
        delay = min(2**retry_count, 30)  # Max 30 seconds
        logger.debug(f"Backing off for {delay}s (retry {retry_count + 1})")
        await asyncio.sleep(delay)

    async def get(
        self, path: str, params: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Make a GET request.

        Args:
            path: API path.
            params: Query parameters.

        Returns:
            Parsed JSON response.
        """
        return await self.request("GET", path, params=params)

    async def post(
        self,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make a POST request.

        Args:
            path: API path.
            json: JSON body.
            params: Query parameters.

        Returns:
            Parsed JSON response.
        """
        return await self.request("POST", path, params=params, json=json)

    async def put(
        self,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make a PUT request.

        Args:
            path: API path.
            json: JSON body.
            params: Query parameters.

        Returns:
            Parsed JSON response.
        """
        return await self.request("PUT", path, params=params, json=json)

    async def patch(
        self,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make a PATCH request.

        Args:
            path: API path.
            json: JSON body.
            params: Query parameters.

        Returns:
            Parsed JSON response.
        """
        return await self.request("PATCH", path, params=params, json=json)

    async def delete(
        self,
        path: str,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make a DELETE request.

        Args:
            path: API path.
            params: Query parameters.

        Returns:
            Parsed JSON response.
        """
        return await self.request("DELETE", path, params=params)

    async def public_post(
        self,
        path: str,
        json: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make a POST request without the API key header.

        Used for public endpoints like agent registration that don't
        require authentication.

        Args:
            path: API path.
            json: JSON body.

        Returns:
            Parsed JSON response.
        """
        async with httpx.AsyncClient(
            base_url=self.config.base_url,
            timeout=self.config.timeout,
            verify=self.config.verify_ssl,
            headers={
                "Content-Type": "application/json",
                "User-Agent": self.config.user_agent,
            },
        ) as client:
            try:
                response = await client.post(url=path, json=json)
            except httpx.ConnectError as e:
                raise DanubeConnectionError(f"Failed to connect to Danube API: {e}")
            except httpx.TimeoutException as e:
                raise DanubeTimeoutError(f"Request timed out: {e}")

            return self._handle_response(response, 0, "POST", path, None, json)

    async def close(self) -> None:
        """Close the HTTP client and release resources."""
        if self._client and not self._client.is_closed:
            await self._client.aclose()
            self._client = None

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

    async def __aexit__(self, *args: Any) -> None:
        """Async context manager exit."""
        await self.close()
