"""CRUDNamespace base class — eliminates repetition across CRUD API namespaces."""

from __future__ import annotations

from typing import Any, Generic, TypeVar

import httpx

from goodmem.errors import raise_for_status
from goodmem.pagination import AsyncPaginator, Paginator

T = TypeVar("T")


class CRUDNamespace:
    """Base class for standard CRUD API namespaces.

    Subclasses set class-level attributes to define the resource type:

        class EmbeddersAPI(CRUDNamespace):
            _base_path = "/v1/embedders"
            _create_model = EmbedderCreationRequest
            _update_model = UpdateEmbedderRequest
            _response_model = EmbedderResponse
            _list_response_model = ListEmbeddersResponse
    """

    _base_path: str
    _create_model: type | None = None
    _update_model: type | None = None
    _response_model: type | None = None
    _list_response_model: type | None = None
    _list_items_field: str = "items"

    def __init__(self, http: httpx.Client, headers: dict[str, str]) -> None:
        self._http = http
        self._headers = headers

    def create(self, **kwargs: Any) -> Any:
        """Create a resource. Accepts model fields as kwargs."""
        body = self._create_model.model_validate(kwargs)
        resp = self._http.post(
            self._base_path,
            json=body.model_dump(by_alias=True, exclude_unset=True),
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    def get(self, id: str) -> Any:
        """Get a resource by ID."""
        resp = self._http.get(
            f"{self._base_path}/{id}",
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    def list(self, **kwargs: Any) -> Paginator:
        """List resources. Returns an auto-paginating iterator."""
        return Paginator(
            fetch_fn=lambda token: self._http.get(
                self._base_path,
                params={**kwargs, "nextToken": token} if token else kwargs or None,
                headers=self._headers,
            ),
            response_model=self._list_response_model,
            items_field=self._list_items_field,
        )

    def update(self, id: str, **kwargs: Any) -> Any:
        """Update a resource by ID."""
        body = self._update_model.model_validate(kwargs)
        resp = self._http.put(
            f"{self._base_path}/{id}",
            json=body.model_dump(by_alias=True, exclude_unset=True),
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    def delete(self, id: str) -> None:
        """Delete a resource by ID."""
        resp = self._http.delete(
            f"{self._base_path}/{id}",
            headers=self._headers,
        )
        raise_for_status(resp)

    def _handle_response(self, resp: httpx.Response, model_cls: type | None) -> Any:
        """Check status, raise typed exceptions, parse response."""
        raise_for_status(resp)
        if model_cls is None:
            return None
        return model_cls.model_validate(resp.json())


class AsyncCRUDNamespace:
    """Async base class for standard CRUD API namespaces."""

    _base_path: str
    _create_model: type | None = None
    _update_model: type | None = None
    _response_model: type | None = None
    _list_response_model: type | None = None
    _list_items_field: str = "items"

    def __init__(self, http: httpx.AsyncClient, headers: dict[str, str]) -> None:
        self._http = http
        self._headers = headers

    async def create(self, **kwargs: Any) -> Any:
        body = self._create_model.model_validate(kwargs)
        resp = await self._http.post(
            self._base_path,
            json=body.model_dump(by_alias=True, exclude_unset=True),
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    async def get(self, id: str) -> Any:
        resp = await self._http.get(
            f"{self._base_path}/{id}",
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    def list(self, **kwargs: Any) -> AsyncPaginator:
        return AsyncPaginator(
            fetch_fn=lambda token: self._http.get(
                self._base_path,
                params={**kwargs, "nextToken": token} if token else kwargs or None,
                headers=self._headers,
            ),
            response_model=self._list_response_model,
            items_field=self._list_items_field,
        )

    async def update(self, id: str, **kwargs: Any) -> Any:
        body = self._update_model.model_validate(kwargs)
        resp = await self._http.put(
            f"{self._base_path}/{id}",
            json=body.model_dump(by_alias=True, exclude_unset=True),
            headers=self._headers,
        )
        return self._handle_response(resp, self._response_model)

    async def delete(self, id: str) -> None:
        resp = await self._http.delete(
            f"{self._base_path}/{id}",
            headers=self._headers,
        )
        raise_for_status(resp)

    def _handle_response(self, resp: httpx.Response, model_cls: type | None) -> Any:
        raise_for_status(resp)
        if model_cls is None:
            return None
        return model_cls.model_validate(resp.json())
