"""HTTP client implementation with retry logic."""

from __future__ import annotations

import json
import time
from typing import Any, Mapping

import httpx

from ._constants import (
    DEFAULT_BASE_URL,
    DEFAULT_MAX_RETRIES,
    DEFAULT_RETRY_DELAY,
    DEFAULT_TIMEOUT,
)
from ._exceptions import (
    APIConnectionError,
    APIError,
    APITimeoutError,
    AuthenticationError,
    InternalServerError,
    MethodNotSupportedError,
    NotFoundError,
    RateLimitError,
)


class SyncHTTPClient:
    """Synchronous HTTP client for OctaMem API."""

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
        retry_delay: float = DEFAULT_RETRY_DELAY,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._max_retries = max_retries
        self._retry_delay = retry_delay
        self._client: httpx.Client | None = None

    @property
    def client(self) -> httpx.Client:
        if self._client is None or self._client.is_closed:
            headers: dict[str, str] = {"Content-Type": "application/json"}
            if self._api_key:
                headers["Authorization"] = f"Bearer {self._api_key}"
            self._client = httpx.Client(
                base_url=self._base_url,
                timeout=self._timeout,
                headers=headers,
            )
        return self._client

    def close(self) -> None:
        """Close the HTTP client."""
        if self._client is not None and not self._client.is_closed:
            self._client.close()
            self._client = None

    def __enter__(self) -> "SyncHTTPClient":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def request(
        self,
        method: str,
        endpoint: str,
        *,
        body: Mapping[str, Any] | None = None,
    ) -> Any:
        """Make an HTTP request with retry logic."""
        last_error: Exception | None = None

        for attempt in range(self._max_retries + 1):
            try:
                return self._execute(method, endpoint, body=body)
            except (APIConnectionError, APITimeoutError, RateLimitError, InternalServerError) as e:
                last_error = e
                if attempt >= self._max_retries:
                    raise
                if isinstance(e, RateLimitError) and e.retry_after:
                    time.sleep(e.retry_after)
                else:
                    time.sleep(self._retry_delay * (2**attempt))

        raise last_error or APIConnectionError("Request failed")

    def _execute(
        self,
        method: str,
        endpoint: str,
        *,
        body: Mapping[str, Any] | None = None,
    ) -> Any:
        """Execute a single HTTP request."""
        try:
            response = self.client.request(
                method,
                endpoint,
                json=body,
            )
            return self._handle_response(response)
        except httpx.TimeoutException as e:
            raise APITimeoutError(self._timeout) from e
        except httpx.RequestError as e:
            raise APIConnectionError(str(e)) from e

    def _handle_response(self, response: httpx.Response) -> Any:
        """Handle HTTP response and raise appropriate errors."""
        if response.status_code == 200:
            try:
                return response.json()
            except json.JSONDecodeError:
                return {}

        self._raise_for_status(response)

    def _raise_for_status(self, response: httpx.Response) -> None:
        """Raise appropriate exception based on status code."""
        try:
            error_data = response.json()
            message = error_data.get("error", f"HTTP {response.status_code}")
            code = error_data.get("code")
        except json.JSONDecodeError:
            message = f"HTTP {response.status_code}"
            code = None

        status = response.status_code

        if status == 401:
            raise AuthenticationError(message)
        elif status == 403:
            raise MethodNotSupportedError(message)
        elif status == 404:
            raise NotFoundError(message)
        elif status == 429:
            retry_after = response.headers.get("Retry-After")
            raise RateLimitError(float(retry_after) if retry_after else None)
        elif status >= 500:
            raise InternalServerError(message, status)
        else:
            raise APIError(message, status, code)


class AsyncHTTPClient:
    """Asynchronous HTTP client for OctaMem API."""

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
        retry_delay: float = DEFAULT_RETRY_DELAY,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._max_retries = max_retries
        self._retry_delay = retry_delay
        self._client: httpx.AsyncClient | None = None

    @property
    def client(self) -> httpx.AsyncClient:
        if self._client is None or self._client.is_closed:
            headers: dict[str, str] = {"Content-Type": "application/json"}
            if self._api_key:
                headers["Authorization"] = f"Bearer {self._api_key}"
            self._client = httpx.AsyncClient(
                base_url=self._base_url,
                timeout=self._timeout,
                headers=headers,
            )
        return self._client

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

    async def __aenter__(self) -> "AsyncHTTPClient":
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    async def request(
        self,
        method: str,
        endpoint: str,
        *,
        body: Mapping[str, Any] | None = None,
    ) -> Any:
        """Make an HTTP request with retry logic."""
        import asyncio

        last_error: Exception | None = None

        for attempt in range(self._max_retries + 1):
            try:
                return await self._execute(method, endpoint, body=body)
            except (APIConnectionError, APITimeoutError, RateLimitError, InternalServerError) as e:
                last_error = e
                if attempt >= self._max_retries:
                    raise
                if isinstance(e, RateLimitError) and e.retry_after:
                    await asyncio.sleep(e.retry_after)
                else:
                    await asyncio.sleep(self._retry_delay * (2**attempt))

        raise last_error or APIConnectionError("Request failed")

    async def _execute(
        self,
        method: str,
        endpoint: str,
        *,
        body: Mapping[str, Any] | None = None,
    ) -> Any:
        """Execute a single HTTP request."""
        try:
            response = await self.client.request(
                method,
                endpoint,
                json=body,
            )
            return self._handle_response(response)
        except httpx.TimeoutException as e:
            raise APITimeoutError(self._timeout) from e
        except httpx.RequestError as e:
            raise APIConnectionError(str(e)) from e

    def _handle_response(self, response: httpx.Response) -> Any:
        """Handle HTTP response and raise appropriate errors."""
        if response.status_code == 200:
            try:
                return response.json()
            except json.JSONDecodeError:
                return {}

        self._raise_for_status(response)

    def _raise_for_status(self, response: httpx.Response) -> None:
        """Raise appropriate exception based on status code."""
        try:
            error_data = response.json()
            message = error_data.get("error", f"HTTP {response.status_code}")
            code = error_data.get("code")
        except json.JSONDecodeError:
            message = f"HTTP {response.status_code}"
            code = None

        status = response.status_code

        if status == 401:
            raise AuthenticationError(message)
        elif status == 403:
            raise MethodNotSupportedError(message)
        elif status == 404:
            raise NotFoundError(message)
        elif status == 429:
            retry_after = response.headers.get("Retry-After")
            raise RateLimitError(float(retry_after) if retry_after else None)
        elif status >= 500:
            raise InternalServerError(message, status)
        else:
            raise APIError(message, status, code)
