"""Async HTTP client for the Fable Assistant API."""
from __future__ import annotations

import os
from typing import Any, AsyncIterator

import httpx


class FableAPIError(Exception):
    """Raised when the Fable API returns a non-2xx response."""

    def __init__(self, status_code: int, message: str) -> None:
        self.status_code = status_code
        super().__init__(f"Fable API error {status_code}: {message}")


class FableClient:
    """Async wrapper around httpx for the Fable REST API."""

    def __init__(self) -> None:
        url = os.environ.get("FABLE_URL", "").rstrip("/")
        key = os.environ.get("FABLE_API_KEY", "")
        if not url:
            raise ValueError("FABLE_URL environment variable is required")
        if not key:
            raise ValueError("FABLE_API_KEY environment variable is required")
        self.base_url = url
        self._headers = {
            "Authorization": f"Bearer {key}",
            "Content-Type": "application/json",
        }
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> "FableClient":
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            headers=self._headers,
            timeout=httpx.Timeout(connect=10.0, read=300.0, write=30.0, pool=10.0),
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _http(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError("FableClient must be used as an async context manager")
        return self._client

    @staticmethod
    def _raise_for_status(response: httpx.Response) -> None:
        if response.is_error:
            try:
                message = response.json().get("error", response.text)
            except Exception:
                message = response.text
            raise FableAPIError(response.status_code, message)

    async def get(self, path: str, **kwargs: Any) -> Any:
        response = await self._http().get(path, **kwargs)
        self._raise_for_status(response)
        return response.json()

    async def post(self, path: str, **kwargs: Any) -> Any:
        response = await self._http().post(path, **kwargs)
        self._raise_for_status(response)
        return response.json()

    async def patch(self, path: str, **kwargs: Any) -> Any:
        response = await self._http().patch(path, **kwargs)
        self._raise_for_status(response)
        return response.json()

    async def put(self, path: str, **kwargs: Any) -> Any:
        response = await self._http().put(path, **kwargs)
        self._raise_for_status(response)
        return response.json()

    async def delete(self, path: str, **kwargs: Any) -> Any:
        response = await self._http().delete(path, **kwargs)
        if response.status_code == 204:
            return None
        self._raise_for_status(response)
        if response.content:
            return response.json()
        return None

    async def stream_get(self, path: str, **kwargs: Any) -> AsyncIterator[str]:
        """Yield non-empty lines from a streaming GET response (SSE)."""
        async with self._http().stream("GET", path, **kwargs) as response:
            if response.is_error:
                await response.aread()
                self._raise_for_status(response)
            async for line in response.aiter_lines():
                if line:
                    yield line


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

_client: FableClient | None = None


def init_client() -> FableClient:
    """Initialise the module-level FableClient singleton (reads env vars)."""
    global _client
    _client = FableClient()
    return _client


def get_client() -> FableClient:
    """Return the singleton client; raises RuntimeError if not initialised."""
    if _client is None:
        raise RuntimeError("Call init_client() before get_client()")
    return _client


def _reset_client() -> None:
    """Reset the singleton — used by tests only."""
    global _client
    _client = None
