"""HTTP transport layer for the MLD API client.

Wraps httpx.Client with auth header injection, automatic /api path
prefixing, status-code-to-exception mapping, and token refresh on 401.
"""

from __future__ import annotations

from typing import Any

try:
    import httpx
except ImportError:
    httpx = None  # type: ignore[assignment]

from mld_sdk.client._exceptions import (
    AuthenticationError,
    MLDConnectionError,
    raise_for_status,
)


def _require_httpx() -> None:
    if httpx is None:
        raise ImportError("httpx is required for the MLD API client. Install it with: pip install mld-sdk[client]")


class HTTPClient:
    """Thin HTTP client wrapping httpx with auth and error handling."""

    def __init__(
        self,
        base_url: str,
        token: str | None = None,
        timeout: float = 30.0,
    ) -> None:
        _require_httpx()
        self._base_url = base_url.rstrip("/")
        self._token = token
        self._timeout = timeout
        self._refresh_callback: Any = None
        self._client = httpx.Client(
            base_url=self._base_url,
            timeout=timeout,
        )

    def set_token(self, token: str | None) -> None:
        self._token = token

    def set_refresh_callback(self, callback: Any) -> None:
        """Set a callable that attempts token refresh. Returns new token or None."""
        self._refresh_callback = callback

    @property
    def base_url(self) -> str:
        return self._base_url

    def _headers(self) -> dict[str, str]:
        headers: dict[str, str] = {"Accept": "application/json"}
        if self._token:
            headers["Authorization"] = f"Bearer {self._token}"
        return headers

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
        _is_retry: bool = False,
    ) -> Any:
        """Execute an HTTP request with error handling and optional token refresh."""
        # Ensure /api prefix
        if not path.startswith("/api"):
            path = f"/api{path}" if path.startswith("/") else f"/api/{path}"

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

        try:
            response = self._client.request(
                method,
                path,
                json=json,
                params=params,
                headers=self._headers(),
            )
        except httpx.ConnectError as e:
            raise MLDConnectionError(f"Cannot connect to {self._base_url}: {e}") from e
        except httpx.TimeoutException as e:
            raise MLDConnectionError(f"Request timed out: {e}") from e

        # Attempt token refresh on 401 (once)
        if response.status_code == 401 and not _is_retry and self._refresh_callback:
            new_token = self._refresh_callback()
            if new_token:
                self._token = new_token
                return self._request(method, path, json=json, params=params, _is_retry=True)

        # Parse body
        body = None
        if response.content:
            try:
                body = response.json()
            except (ValueError, TypeError):
                body = None

        raise_for_status(response.status_code, body)
        return body

    def get(self, path: str, **params: Any) -> Any:
        return self._request("GET", path, params=params if params else None)

    def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
        return self._request("POST", path, json=json)

    def put(self, path: str, json: dict[str, Any] | None = None) -> Any:
        return self._request("PUT", path, json=json)

    def patch(self, path: str, json: dict[str, Any] | None = None) -> Any:
        return self._request("PATCH", path, json=json)

    def delete(self, path: str) -> Any:
        return self._request("DELETE", path)

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