"""Asynchronous Influxion client."""

from __future__ import annotations

import asyncio
from typing import Any
from uuid import UUID

import httpx

from ._base import (
    MAX_RETRIES,
    InfluxionConfig,
    _logger,
    auth_headers,
    backoff_seconds,
    build_config,
    map_http_error,
    should_retry_status,
)
from ._exceptions import AuthenticationError, InfluxionError, NetworkError


class AsyncInfluxionClient:
    """Asynchronous Influxion SDK client.

    Example::

        async with AsyncInfluxionClient(api_key="sk-...") as client:
            session_id = await client.create_session(
                name="weekly-report-run",
                project_id="<uuid>",
                agent_id="report-agent",
            )
            await client.create_session_artifact(
                session_id=session_id,
                artifact_name="report",
                artifact="The quarterly results show...",
            )
            await client.end_session(session_id, success=True)
    """

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
        fail_silently: bool = True,
    ) -> None:
        """
        Args:
            api_key: API key for authentication. Falls back to the
                ``INFLUXION_API_KEY`` environment variable. Raises
                :exc:`~influxion.AuthenticationError` immediately if neither
                is set.
            base_url: Base URL for the Influxion API. Falls back to the
                ``INFLUXION_BASE_URL`` environment variable, then
                ``https://api.influxion.io/v1``.
            fail_silently: If ``True`` (default), call-time errors are logged
                and methods return ``None`` instead of raising. Set to
                ``False`` during development to surface errors immediately.
        """
        self._config: InfluxionConfig = build_config(api_key, base_url, fail_silently)
        self._http = httpx.AsyncClient(
            headers=auth_headers(self._config.api_key),
            timeout=30.0,
        )

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def aclose(self) -> None:
        """Close the underlying async HTTP client."""
        await self._http.aclose()

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

    async def __aexit__(self, *args: Any) -> bool:
        await self.aclose()
        return False

    # ------------------------------------------------------------------
    # Internal request machinery
    # ------------------------------------------------------------------

    async def _request(self, method: str, path: str, **kwargs: Any) -> Any:
        url = f"{self._config.base_url}{path}"
        last_exc: Exception | None = None

        for attempt in range(MAX_RETRIES):
            try:
                response = await self._http.request(method, url, **kwargs)

                if response.is_success:
                    return response.json() if response.content else None

                try:
                    detail = response.json().get("detail", response.text)
                except Exception:
                    detail = response.text or str(response.status_code)

                if response.status_code == 401:
                    raise AuthenticationError(f"Authentication failed: {detail}")

                error = map_http_error(response.status_code, str(detail))
                if should_retry_status(response.status_code) and attempt < MAX_RETRIES - 1:
                    last_exc = error
                    await asyncio.sleep(backoff_seconds(attempt))
                    continue
                raise error

            except (AuthenticationError, InfluxionError):
                raise
            except httpx.TransportError as exc:
                last_exc = NetworkError(f"Network error: {exc}")
                if attempt < MAX_RETRIES - 1:
                    await asyncio.sleep(backoff_seconds(attempt))
                    continue
                raise last_exc from exc

        raise last_exc  # type: ignore[misc]

    async def _call(self, method: str, path: str, **kwargs: Any) -> Any:
        try:
            return await self._request(method, path, **kwargs)
        except AuthenticationError:
            raise
        except Exception as exc:
            if not self._config.fail_silently:
                raise
            _logger.error("Influxion SDK error (%s %s): %s", method, path, exc)
            return None

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    async def create_session(
        self,
        name: str,
        project_id: str | UUID,
        agent_id: str,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> str | None:
        """Create a new agent session.

        Returns the session ID string, or ``None`` if an error occurs and
        ``fail_silently=True``.
        """
        body: dict[str, Any] = {
            "name": name,
            "project_id": str(project_id),
            "agent_id": agent_id,
        }
        if tags is not None:
            body["tags"] = tags
        if metadata is not None:
            body["metadata"] = metadata
        result = await self._call("POST", "/agent_sessions", json=body)
        return result["session_id"] if result else None

    async def create_session_artifact(
        self,
        session_id: str,
        artifact_name: str,
        artifact: str,
        artifact_type: str = "text",
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> str | None:
        """Upload an artifact to a session.

        Returns the artifact ID string, or ``None`` if an error occurs and
        ``fail_silently=True``.
        """
        body: dict[str, Any] = {
            "artifact_name": artifact_name,
            "artifact": artifact,
            "artifact_type": artifact_type,
        }
        if tags is not None:
            body["tags"] = tags
        if metadata is not None:
            body["metadata"] = metadata
        result = await self._call(
            "POST", f"/agent_sessions/{session_id}/artifacts", json=body
        )
        return result["artifact_id"] if result else None

    async def update_session(
        self,
        session_id: str,
        *,
        success: bool | None = None,
        failure_message: str | None = None,
        tags: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """Update mutable fields on an existing session.

        All parameters are optional. Providing ``success`` causes the server
        to record the session's end time. If no fields are provided, no
        request is made.
        """
        body: dict[str, Any] = {}
        if success is not None:
            body["success"] = success
        if failure_message is not None:
            body["failure_message"] = failure_message
        if tags is not None:
            body["tags"] = tags
        if metadata is not None:
            body["metadata"] = metadata
        if body:
            await self._call("PATCH", f"/agent_sessions/{session_id}", json=body)

    async def end_session(
        self,
        session_id: str,
        *,
        success: bool,
        failure_message: str | None = None,
    ) -> None:
        """Record the outcome of a session and mark it as ended.

        ``success`` is required. The server records the end time when a
        ``success`` value is present.
        """
        await self.update_session(
            session_id,
            success=success,
            failure_message=failure_message,
        )
