"""Main HTTP client for vikdata SDK."""

import asyncio
from typing import Any, Literal

import httpx
from pydantic import BaseModel, ValidationError as PydanticValidationError

from vikdata.config import VikdataConfig
from vikdata.exceptions import (
    APIError,
    AuthenticationError,
    AuthorizationError,
    ConflictError,
    NetworkError,
    RateLimitError,
    ResourceNotFoundError,
    ServerError,
    TimeoutError,
    ValidationError,
)


class VikdataClient:
    """Async HTTP client for vikdata API.

    Supports authentication via Bearer token or API key.
    Includes automatic retry logic with exponential backoff.

    Usage:
        async with VikdataClient(base_url="https://api.vikdata.com", api_key="vk_...") as client:
            user = await client.auth.me()
    """

    def __init__(
        self,
        base_url: str | None = None,
        api_key: str | None = None,
        access_token: str | None = None,
        timeout: float | None = None,
        max_retries: int | None = None,
        retry_backoff_factor: float | None = None,
        verify_ssl: bool | None = None,
    ) -> None:
        """Initialize the vikdata client.

        Args:
            base_url: Base URL for the API (default: http://localhost:8000)
            api_key: API key for authentication (X-API-Key header)
            access_token: JWT access token for authentication (Bearer token)
            timeout: Request timeout in seconds (default: 30.0)
            max_retries: Maximum number of retries (default: 3)
            retry_backoff_factor: Backoff factor for retries (default: 0.5)
            verify_ssl: Whether to verify SSL certificates (default: True)
        """
        config = VikdataConfig()

        self.base_url = (base_url or config.base_url).rstrip("/")
        self.api_key = api_key or config.api_key
        self.access_token = access_token
        self.timeout = timeout if timeout is not None else config.timeout
        self.max_retries = max_retries if max_retries is not None else config.max_retries
        self.retry_backoff_factor = (
            retry_backoff_factor if retry_backoff_factor is not None else config.retry_backoff_factor
        )
        self.verify_ssl = verify_ssl if verify_ssl is not None else config.verify_ssl

        self._client: httpx.AsyncClient | None = None
        self._auth_headers: dict[str, str] = {}
        self._update_auth_headers()

        # Lazy-loaded API endpoints
        from vikdata.api.admin import AdminAPI
        from vikdata.api.api_keys import ApiKeysAPI
        from vikdata.api.auth import AuthAPI
        from vikdata.api.billing import BillingAPI
        from vikdata.api.ingress import IngressAPI
        from vikdata.api.lookups import LookupsAPI
        from vikdata.api.permissions import PermissionsAPI
        from vikdata.api.quotas import QuotasAPI
        from vikdata.api.roles import RolesAPI
        from vikdata.api.rules import RulesAPI
        from vikdata.api.services import ServicesEndpoint
        from vikdata.api.tenants import TenantsAPI
        from vikdata.api.users import UsersAPI
        from vikdata.api.vessels import VesselsAPI

        self.admin = AdminAPI(self)
        self.api_keys = ApiKeysAPI(self)
        self.auth = AuthAPI(self)
        self.billing = BillingAPI(self)
        self.ingress = IngressAPI(self)
        self.lookups = LookupsAPI(self)
        self.permissions = PermissionsAPI(self)
        self.quotas = QuotasAPI(self)
        self.roles = RolesAPI(self)
        self.rules = RulesAPI(self)
        self.services = ServicesEndpoint(self)
        self.tenants = TenantsAPI(self)
        self.users = UsersAPI(self)
        self.vessels = VesselsAPI(self)

    def _update_auth_headers(self) -> None:
        """Update authentication headers based on available credentials."""
        self._auth_headers = {}
        if self.access_token:
            self._auth_headers["Authorization"] = f"Bearer {self.access_token}"
        elif self.api_key:
            self._auth_headers["X-API-Key"] = self.api_key

    def set_access_token(self, token: str) -> None:
        """Set the access token for authentication.

        Args:
            token: JWT access token
        """
        self.access_token = token
        self._update_auth_headers()

    def clear_access_token(self) -> None:
        """Clear the access token."""
        self.access_token = None
        self._update_auth_headers()

    async def __aenter__(self) -> "VikdataClient":
        """Enter async context manager."""
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=httpx.Timeout(self.timeout),
            verify=self.verify_ssl,
        )
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Exit async context manager."""
        if self._client:
            await self._client.aclose()
            self._client = None

    async def request(
        self,
        method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
        path: str,
        *,
        params: dict[str, Any] | None = None,
        json: dict[str, Any] | BaseModel | None = None,
        headers: dict[str, str] | None = None,
        response_model: type[BaseModel] | None = None,
    ) -> Any:
        """Make an HTTP request to the API.

        Args:
            method: HTTP method
            path: API path (e.g., '/api/v1/auth/login')
            params: Query parameters
            json: Request body (dict or Pydantic model)
            headers: Additional headers
            response_model: Pydantic model to parse response

        Returns:
            Parsed response (Pydantic model, dict, list, or None)

        Raises:
            Various exceptions based on response status
        """
        if not self._client:
            raise RuntimeError("Client not initialized. Use async context manager.")

        # Build headers
        request_headers = {**self._auth_headers}
        if headers:
            request_headers.update(headers)

        # Convert Pydantic model to dict
        request_json = None
        if json is not None:
            if isinstance(json, BaseModel):
                request_json = json.model_dump(mode="json", exclude_none=True)
            else:
                request_json = json

        # Retry logic
        last_exception: Exception | None = None
        for attempt in range(self.max_retries + 1):
            try:
                response = await self._client.request(
                    method=method,
                    url=path,
                    params=params,
                    json=request_json,
                    headers=request_headers,
                )

                # Handle response
                return self._handle_response(response, response_model)

            except httpx.TimeoutException as e:
                last_exception = TimeoutError(f"Request timeout after {self.timeout}s")
                if attempt < self.max_retries:
                    await asyncio.sleep(self.retry_backoff_factor * (2**attempt))
                    continue
                raise last_exception from e

            except httpx.NetworkError as e:
                last_exception = NetworkError(f"Network error: {e}")
                if attempt < self.max_retries:
                    await asyncio.sleep(self.retry_backoff_factor * (2**attempt))
                    continue
                raise last_exception from e

            except (AuthenticationError, AuthorizationError, ValidationError, ResourceNotFoundError):
                # Don't retry on client errors
                raise

        if last_exception:
            raise last_exception

        raise RuntimeError("Unexpected error in request retry logic")

    def _handle_response(
        self, response: httpx.Response, response_model: type[BaseModel] | None
    ) -> Any:
        """Handle HTTP response and raise appropriate exceptions.

        Args:
            response: HTTP response
            response_model: Pydantic model to parse response

        Returns:
            Parsed response data

        Raises:
            Various exceptions based on status code
        """
        # Success responses
        if 200 <= response.status_code < 300:
            if response.status_code == 204:  # No content
                return None

            try:
                data = response.json()
            except Exception:
                return None

            if response_model:
                try:
                    return response_model.model_validate(data)
                except PydanticValidationError as e:
                    raise ValidationError(f"Failed to parse response: {e}") from e

            return data

        # Error responses
        try:
            error_data = response.json()
            error_message = error_data.get("message") or error_data.get("detail") or "Unknown error"
        except Exception:
            error_message = response.text or f"HTTP {response.status_code}"
            error_data = None

        # 4xx Client Errors
        if response.status_code == 400:
            raise ValidationError(error_message)
        elif response.status_code == 401:
            raise AuthenticationError(error_message)
        elif response.status_code == 403:
            raise AuthorizationError(error_message)
        elif response.status_code == 404:
            raise ResourceNotFoundError(error_message)
        elif response.status_code == 409:
            raise ConflictError(error_message)
        elif response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", "60"))
            raise RateLimitError(error_message, retry_after)
        elif 400 <= response.status_code < 500:
            raise APIError(error_message, response.status_code, error_data)

        # 5xx Server Errors
        elif 500 <= response.status_code < 600:
            raise ServerError(f"Server error: {error_message}")

        # Unknown status code
        raise APIError(error_message, response.status_code, error_data)
