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

from __future__ import annotations

from pathlib import Path
from typing import Any, Mapping

import httpx

from goodmem.errors import raise_for_status


def _op_meta(namespace: Any, op_name: str) -> dict[str, Any]:
    """Look up generated operation metadata for a CRUD method."""
    return (getattr(namespace, "_rest_metadata", {}) or {}).get(op_name, {})


def _supports_label_wildcard(op: dict[str, Any]) -> bool:
    return bool(op.get("supports_label_wildcard")) or "label.*" in (op.get("query_params") or [])


def _expand_label_filters(
    *,
    op: dict[str, Any],
    labels: Mapping[str, str] | None,
    params: dict[str, Any],
) -> dict[str, Any]:
    """Expand labels mapping to literal label.<key> query params when supported."""
    if labels is None:
        return params
    if not _supports_label_wildcard(op):
        raise ValueError("This endpoint does not support label.* query filters.")
    params.update({f"label.{k}": v for k, v in labels.items()})
    return params


def _normalize_multipart_file(file_value: Any) -> Any:
    """Normalize supported file inputs into an httpx files= payload tuple."""
    if isinstance(file_value, tuple):
        return file_value
    if isinstance(file_value, (bytes, bytearray)):
        return ("upload.bin", bytes(file_value), "application/octet-stream")
    if hasattr(file_value, "read"):
        raw_name = getattr(file_value, "name", "upload.bin")
        filename = Path(str(raw_name)).name
        return (filename or "upload.bin", file_value, "application/octet-stream")
    raise TypeError(
        "Multipart file must be a tuple, bytes, bytearray, or a file-like object with read()."
    )


def _prepare_create_payload(
    *,
    create_model: type | None,
    op: dict[str, Any],
    kwargs: dict[str, Any],
) -> tuple[dict[str, Any] | None, dict[str, Any] | None, dict[str, Any] | None]:
    """Build json/data/files payloads for create(), including multipart when declared."""
    supports_multipart = bool(op.get("supports_multipart")) or "multipart/form-data" in (
        op.get("body_kinds") or []
    )
    file_field = op.get("multipart_file_field") or "file"
    request_field = op.get("multipart_request_field") or "request"

    if supports_multipart and file_field in kwargs:
        payload = dict(kwargs)
        raw_file = payload.pop(file_field)
        raw_request = payload.pop(request_field, None)
        if raw_request is None:
            request_kwargs = payload
        elif isinstance(raw_request, dict):
            request_kwargs = raw_request
        elif hasattr(raw_request, "model_dump"):
            request_kwargs = raw_request.model_dump(by_alias=False, exclude_unset=False)
        else:
            raise TypeError(
                f"Multipart '{request_field}' must be a dict or pydantic model, got {type(raw_request)!r}."
            )

        body = create_model.model_validate(request_kwargs) if create_model is not None else None
        request_json = (
            body.model_dump_json(by_alias=True, exclude_unset=True)
            if body is not None
            else "{}"
        )
        data = {request_field: request_json}
        files = {file_field: _normalize_multipart_file(raw_file)}
        return None, data, files

    body = create_model.model_validate(kwargs) if create_model is not None else None
    json_payload = (
        body.model_dump(by_alias=True, exclude_unset=True)
        if body is not None
        else None
    )
    return json_payload, None, None


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

    Provides ``__init__`` and ``_handle_response``.  The actual CRUD methods
    (``create``, ``get``, ``list``, ``update``, ``delete``) are emitted by the
    generator only when the OpenAPI spec declares them — no phantom methods.

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

        class EmbeddersAPIBase(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
    _create_response_model: type | None = None
    _list_response_model: type | None = None
    _list_items_field: str = "items"
    _rest_metadata: dict[str, dict[str, Any]] = {}

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

    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())

    def _handle_list_response(self, resp: httpx.Response) -> list:
        """Parse a non-paginated list response into typed items."""
        raise_for_status(resp)
        wrapper = self._list_response_model.model_validate(resp.json())
        return getattr(wrapper, self._list_items_field, [])


class AsyncCRUDNamespace:
    """Async base class for CRUD API namespaces. See ``CRUDNamespace``."""

    _base_path: str
    _create_model: type | None = None
    _update_model: type | None = None
    _response_model: type | None = None
    _create_response_model: type | None = None
    _list_response_model: type | None = None
    _list_items_field: str = "items"
    _rest_metadata: dict[str, dict[str, Any]] = {}

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

    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())

    def _handle_list_response(self, resp: httpx.Response) -> list:
        """Parse a non-paginated list response into typed items."""
        raise_for_status(resp)
        wrapper = self._list_response_model.model_validate(resp.json())
        return getattr(wrapper, self._list_items_field, [])
