from __future__ import annotations

from typing import Any

import httpx

from ._errors import (
    A3ApiError,
    A3AuthenticationError,
    A3ConnectionError,
    A3RateLimitError,
    A3ValidationError,
)
from ._retry import (
    RetryConfig,
    is_retryable_status,
    parse_retry_after,
    with_retry_async,
    with_retry_sync,
)
from ._types import AssessAgeRequest, AssessAgeResponse
from ._version import __version__

DEFAULT_BASE_URL = "https://api.a3api.io"
DEFAULT_TIMEOUT = 30.0
DEFAULT_MAX_RETRIES = 2


def _build_headers(api_key: str) -> dict[str, str]:
    return {
        "Content-Type": "application/json",
        "x-api-key": api_key,
        "User-Agent": f"a3api-python/{__version__}",
    }


def _handle_error(response: httpx.Response) -> None:
    body: dict[str, Any] | None = None
    try:
        body = response.json()
    except Exception:
        pass

    status = response.status_code
    if status == 400:
        raise A3ValidationError(body)
    if status == 401:
        raise A3AuthenticationError(body)
    if status == 429:
        retry_after = parse_retry_after(response.headers.get("retry-after"))
        raise A3RateLimitError(retry_after, body)
    raise A3ApiError(
        body.get("error", f"HTTP {status}") if body else f"HTTP {status}",
        status,
        body,
    )


def _should_retry(exc: Exception) -> tuple[bool, float | None]:
    if isinstance(exc, A3RateLimitError):
        return True, exc.retry_after
    if isinstance(exc, A3ApiError) and is_retryable_status(exc.status_code):
        return True, None
    if isinstance(exc, A3ConnectionError):
        return True, None
    return False, None


class A3Client:
    """Synchronous A3 API client using httpx."""

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
    ) -> None:
        if not api_key:
            raise ValueError("api_key is required")
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._retry_config = RetryConfig(max_retries=max_retries)
        self._client = httpx.Client(
            base_url=self._base_url,
            headers=_build_headers(api_key),
            timeout=timeout,
        )

    def __enter__(self) -> A3Client:
        return self

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

    def close(self) -> None:
        self._client.close()

    def assess_age(self, request: AssessAgeRequest) -> AssessAgeResponse:
        """Assess age and return a parsed response model."""
        raw = self.assess_age_raw(request)
        return AssessAgeResponse.model_validate(raw)

    def assess_age_raw(self, request: AssessAgeRequest) -> dict[str, Any]:
        """Assess age and return the raw JSON dict."""
        return with_retry_sync(
            lambda: self._do_assess_age(request),
            self._retry_config,
            _should_retry,
        )

    def _do_assess_age(self, request: AssessAgeRequest) -> dict[str, Any]:
        try:
            response = self._client.post(
                "/v1/assurance/assess-age",
                json=request.model_dump(exclude_none=True),
            )
        except httpx.TimeoutException as exc:
            raise A3ConnectionError(f"Request timed out: {exc}", exc) from exc
        except httpx.NetworkError as exc:
            raise A3ConnectionError(f"Network error: {exc}", exc) from exc
        except httpx.ProtocolError as exc:
            raise A3ConnectionError(f"Protocol error: {exc}", exc) from exc

        if response.is_success:
            return response.json()  # type: ignore[no-any-return]
        _handle_error(response)
        raise AssertionError("unreachable")  # pragma: no cover


class AsyncA3Client:
    """Asynchronous A3 API client using httpx."""

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
    ) -> None:
        if not api_key:
            raise ValueError("api_key is required")
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._retry_config = RetryConfig(max_retries=max_retries)
        self._client = httpx.AsyncClient(
            base_url=self._base_url,
            headers=_build_headers(api_key),
            timeout=timeout,
        )

    async def __aenter__(self) -> AsyncA3Client:
        return self

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

    async def close(self) -> None:
        await self._client.aclose()

    async def assess_age(self, request: AssessAgeRequest) -> AssessAgeResponse:
        """Assess age and return a parsed response model."""
        raw = await self.assess_age_raw(request)
        return AssessAgeResponse.model_validate(raw)

    async def assess_age_raw(self, request: AssessAgeRequest) -> dict[str, Any]:
        """Assess age and return the raw JSON dict."""
        return await with_retry_async(
            lambda: self._do_assess_age(request),
            self._retry_config,
            _should_retry,
        )

    async def _do_assess_age(self, request: AssessAgeRequest) -> dict[str, Any]:
        try:
            response = await self._client.post(
                "/v1/assurance/assess-age",
                json=request.model_dump(exclude_none=True),
            )
        except httpx.TimeoutException as exc:
            raise A3ConnectionError(f"Request timed out: {exc}", exc) from exc
        except httpx.NetworkError as exc:
            raise A3ConnectionError(f"Network error: {exc}", exc) from exc
        except httpx.ProtocolError as exc:
            raise A3ConnectionError(f"Protocol error: {exc}", exc) from exc

        if response.is_success:
            return response.json()  # type: ignore[no-any-return]
        _handle_error(response)
        raise AssertionError("unreachable")  # pragma: no cover
