"""Goodmem and AsyncGoodmem — the main entry points."""

from __future__ import annotations

from typing import Any

import httpx

from goodmem.api.admin import AdminAPI, AsyncAdminAPI
from goodmem.api.apikeys import ApikeysAPI, AsyncApikeysAPI
from goodmem.api.embedders import AsyncEmbeddersAPI, EmbeddersAPI
from goodmem.api.llms import AsyncLlmsAPI, LlmsAPI
from goodmem.api.memories import AsyncMemoriesAPI, MemoriesAPI
from goodmem.api.ocr import AsyncOcrAPI, OcrAPI
from goodmem.api.rerankers import AsyncRerankersAPI, RerankersAPI
from goodmem.api.spaces import AsyncSpacesAPI, SpacesAPI
from goodmem.api.system import AsyncSystemAPI, SystemAPI
from goodmem.api.users import AsyncUsersAPI, UsersAPI

# Sentinel to distinguish "not passed" from explicit values.
_UNSET: Any = object()


def _auth_headers(api_key: str) -> dict[str, str]:
    """Build auth headers from a GoodMem API key."""
    return {"x-api-key": api_key}


class Goodmem:
    """Synchronous GoodMem API client.

    Args:
        base_url: Base URL of the GoodMem server (e.g. ``"http://localhost:8080"``).
            Required unless *http_client* is provided.
        api_key: GoodMem API key (prefix ``gm_``), sent as ``x-api-key`` header.
        timeout: HTTP request timeout in seconds. Defaults to ``30.0``.
            Cannot be used with *http_client*.
        verify: SSL certificate verification.  Pass ``True`` (default) to use
            system CAs, ``False`` to skip verification, or a path string to a
            CA bundle / certificate file.  Cannot be used with *http_client*.
        http_client: A pre-configured :class:`httpx.Client` to use instead of
            creating one internally.  When provided, *base_url*, *timeout*, and
            *verify* must **not** be set — configure them on the client directly.

    Usage::

        client = Goodmem(base_url="http://localhost:8080", api_key="gm_...")

        # Or as context manager (recommended — ensures the connection pool is closed)
        with Goodmem(base_url="...", api_key="gm_...") as client:
            embedder = client.embedders.create(
                display_name="My Embedder",
                model_identifier="text-embedding-3-large",
                api_key="sk-...",
            )

    Attributes:
        embedders: :class:`~goodmem.api.embedders.EmbeddersAPI`
        rerankers: :class:`~goodmem.api.rerankers.RerankersAPI`
        llms: :class:`~goodmem.api.llms.LlmsAPI`
        spaces: :class:`~goodmem.api.spaces.SpacesAPI`
        memories: :class:`~goodmem.api.memories.MemoriesAPI`
        apikeys: :class:`~goodmem.api.apikeys.ApikeysAPI`
        users: :class:`~goodmem.api.users.UsersAPI`
        ocr: :class:`~goodmem.api.ocr.OcrAPI`
        system: :class:`~goodmem.api.system.SystemAPI`
        admin: :class:`~goodmem.api.admin.AdminAPI`
    """

    def __init__(
        self,
        base_url: str | None = None,
        api_key: str | None = None,
        *,
        timeout: float = _UNSET,
        verify: bool | str = _UNSET,
        http_client: httpx.Client | None = None,
    ) -> None:
        if http_client is not None:
            # Full-control mode: user owns transport + auth.
            # All simple-mode params are mutually exclusive with http_client.
            for name, val, sentinel in [
                ("base_url", base_url, None),
                ("api_key", api_key, None),
                ("timeout", timeout, _UNSET),
                ("verify", verify, _UNSET),
            ]:
                if val is not sentinel:
                    raise ValueError(
                        f"{name} cannot be used together with http_client — "
                        f"configure it on the httpx.Client directly"
                    )
            self._http = http_client
            self._owns_http = False
            self._headers: dict[str, str] = {}
        else:
            # Simple mode: SDK builds the httpx client.
            if base_url is None:
                raise ValueError(
                    "base_url is required (or provide http_client)"
                )
            if api_key is None:
                raise ValueError(
                    "api_key is required (or provide http_client with auth headers)"
                )
            self._http = httpx.Client(
                base_url=base_url,
                timeout=30.0 if timeout is _UNSET else timeout,
                verify=True if verify is _UNSET else verify,
            )
            self._owns_http = True
            self._headers = _auth_headers(api_key)

        # Namespace APIs
        self.embedders = EmbeddersAPI(self._http, self._headers)
        self.rerankers = RerankersAPI(self._http, self._headers)
        self.llms = LlmsAPI(self._http, self._headers)
        self.spaces = SpacesAPI(self._http, self._headers)
        self.memories = MemoriesAPI(self._http, self._headers)
        self.apikeys = ApikeysAPI(self._http, self._headers)
        self.users = UsersAPI(self._http, self._headers)
        self.ocr = OcrAPI(self._http, self._headers)
        self.system = SystemAPI(self._http, self._headers)
        self.admin = AdminAPI(self._http, self._headers)

    def close(self) -> None:
        """Close the underlying HTTP client.

        When a custom *http_client* was provided, closing is a no-op — the
        caller owns that client's lifecycle.
        """
        if self._owns_http:
            self._http.close()

    def __enter__(self) -> Goodmem:
        return self

    def __exit__(self, *args) -> None:
        self.close()


class AsyncGoodmem:
    """Asynchronous GoodMem API client.

    Args:
        base_url: Base URL of the GoodMem server.
            Required unless *http_client* is provided.
        api_key: GoodMem API key (prefix ``gm_``), sent as ``x-api-key`` header.
        timeout: HTTP request timeout in seconds. Defaults to ``30.0``.
            Cannot be used with *http_client*.
        verify: SSL certificate verification.  Pass ``True`` (default) to use
            system CAs, ``False`` to skip verification, or a path string to a
            CA bundle / certificate file.  Cannot be used with *http_client*.
        http_client: A pre-configured :class:`httpx.AsyncClient` to use instead
            of creating one internally.  When provided, *base_url*, *timeout*,
            and *verify* must **not** be set.

    Usage::

        async with AsyncGoodmem(base_url="...", api_key="gm_...") as client:
            embedder = await client.embedders.create(
                display_name="My Embedder",
                model_identifier="text-embedding-3-large",
                api_key="sk-...",
            )

    Attributes:
        embedders: :class:`~goodmem.api.embedders.AsyncEmbeddersAPI`
        rerankers: :class:`~goodmem.api.rerankers.AsyncRerankersAPI`
        llms: :class:`~goodmem.api.llms.AsyncLlmsAPI`
        spaces: :class:`~goodmem.api.spaces.AsyncSpacesAPI`
        memories: :class:`~goodmem.api.memories.AsyncMemoriesAPI`
        apikeys: :class:`~goodmem.api.apikeys.AsyncApikeysAPI`
        users: :class:`~goodmem.api.users.AsyncUsersAPI`
        ocr: :class:`~goodmem.api.ocr.AsyncOcrAPI`
        system: :class:`~goodmem.api.system.AsyncSystemAPI`
        admin: :class:`~goodmem.api.admin.AsyncAdminAPI`
    """

    def __init__(
        self,
        base_url: str | None = None,
        api_key: str | None = None,
        *,
        timeout: float = _UNSET,
        verify: bool | str = _UNSET,
        http_client: httpx.AsyncClient | None = None,
    ) -> None:
        if http_client is not None:
            for name, val, sentinel in [
                ("base_url", base_url, None),
                ("api_key", api_key, None),
                ("timeout", timeout, _UNSET),
                ("verify", verify, _UNSET),
            ]:
                if val is not sentinel:
                    raise ValueError(
                        f"{name} cannot be used together with http_client — "
                        f"configure it on the httpx.AsyncClient directly"
                    )
            self._http = http_client
            self._owns_http = False
            self._headers: dict[str, str] = {}
        else:
            if base_url is None:
                raise ValueError(
                    "base_url is required (or provide http_client)"
                )
            if api_key is None:
                raise ValueError(
                    "api_key is required (or provide http_client with auth headers)"
                )
            self._http = httpx.AsyncClient(
                base_url=base_url,
                timeout=30.0 if timeout is _UNSET else timeout,
                verify=True if verify is _UNSET else verify,
            )
            self._owns_http = True
            self._headers = _auth_headers(api_key)

        self.embedders = AsyncEmbeddersAPI(self._http, self._headers)
        self.rerankers = AsyncRerankersAPI(self._http, self._headers)
        self.llms = AsyncLlmsAPI(self._http, self._headers)
        self.spaces = AsyncSpacesAPI(self._http, self._headers)
        self.memories = AsyncMemoriesAPI(self._http, self._headers)
        self.apikeys = AsyncApikeysAPI(self._http, self._headers)
        self.users = AsyncUsersAPI(self._http, self._headers)
        self.ocr = AsyncOcrAPI(self._http, self._headers)
        self.system = AsyncSystemAPI(self._http, self._headers)
        self.admin = AsyncAdminAPI(self._http, self._headers)

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

        When a custom *http_client* was provided, closing is a no-op.
        """
        if self._owns_http:
            await self._http.aclose()

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

    async def __aexit__(self, *args) -> None:
        await self.close()
