"""HTTP client used by the Yutori SDK."""

from __future__ import annotations

from typing import Any, Dict, Mapping, MutableMapping, Optional, Union

import httpx

from .config import DEFAULT_BASE_URL, DEFAULT_TIMEOUT_SECONDS, sanitize_base_url
from .exceptions import APIError, AuthenticationError

JsonPayload = Union[Mapping[str, Any], MutableMapping[str, Any]]
ModelPayload = JsonPayload


class YutoriClient:

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT_SECONDS,
    ) -> None:
        if not api_key:
            raise AuthenticationError("An API key is required to initialize YutoriClient.")

        self.api_key = api_key
        self.base_url = sanitize_base_url(base_url)
        self.timeout = timeout
        self._client = httpx.Client(timeout=timeout)

    # --------------------------------------------------------------------- #
    # Public API methods
    # --------------------------------------------------------------------- #
    def chat_completions(self, request: ModelPayload) -> Dict[str, Any]:
        """Call POST /v1/chat/completions using Bearer authentication."""

        payload = self._prepare_payload(request)
        return self._request("POST", "/chat/completions", json=payload, auth_type="bearer")

    def agent_run(self, request: ModelPayload) -> Dict[str, Any]:
        """Call POST /v1/browsing/tasks using API-key authentication."""

        payload = self._prepare_payload(request)
        return self._request("POST", "/browsing/tasks", json=payload, auth_type="api_key")

    def create_scout(self, request: ModelPayload) -> Dict[str, Any]:
        """Call POST /v1/scouting/tasks to create a monitoring scout."""

        payload = self._prepare_payload(request)
        return self._request("POST", "/scouting/tasks", json=payload, auth_type="api_key")

    def list_scouts(self) -> Dict[str, Any]:
        """Return the authenticated user's scouts."""

        return self._request("GET", "/scouting/tasks", auth_type="api_key")

    def get_usage(self) -> Dict[str, Any]:
        """Return usage metadata for the authenticated API key."""

        return self._request("GET", "/usage", auth_type="api_key")

    # --------------------------------------------------------------------- #
    # Lifecycle helpers
    # --------------------------------------------------------------------- #
    def close(self) -> None:
        """Release the underlying httpx client."""

        self._client.close()

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

    def __exit__(self, exc_type, exc, traceback) -> None:  # noqa: ANN001
        self.close()

    # --------------------------------------------------------------------- #
    # Internal helpers
    # --------------------------------------------------------------------- #
    def _request(
        self,
        method: str,
        path: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        auth_type: str = "api_key",
    ) -> Dict[str, Any]:
        url = f"{self.base_url}{path}"
        response = self._client.request(
            method.upper(),
            url,
            headers=self._build_headers(auth_type),
            json=json,
        )

        if response.status_code >= 400:
            raise APIError(
                message=response.text or "Yutori API call failed",
                status_code=response.status_code,
                response=response,
            )

        if response.content:
            return response.json()
        return {}

    def _build_headers(self, auth_type: str) -> Dict[str, str]:
        headers = {"Content-Type": "application/json"}

        if auth_type == "api_key":
            headers["x-api-key"] = self.api_key
        elif auth_type == "bearer":
            headers["Authorization"] = f"Bearer {self.api_key}"
        else:  # pragma: no cover - defensive guard
            raise ValueError(f"Unsupported auth type: {auth_type}")

        return headers

    @staticmethod
    def _prepare_payload(payload: ModelPayload) -> Dict[str, Any]:
        if hasattr(payload, "model_dump"):
            return payload.model_dump(exclude_none=True)  # type: ignore[union-attr]
        if isinstance(payload, Mapping):
            return dict(payload)
        raise TypeError("Payloads must be mappings or objects with model_dump().")


