"""CRUD router generation from SQLModel models."""

from collections.abc import Callable, Collection, Sequence
from enum import Enum
from typing import cast

from fastapi import APIRouter
from sqlmodel import SQLModel
from typing_extensions import Self, TypedDict

from auen._async_endpoints import (
    add_async_bulk_create,
    add_async_bulk_delete,
    add_async_bulk_update,
    add_async_create,
    add_async_delete,
    add_async_list,
    add_async_nested_create,
    add_async_nested_update,
    add_async_read,
    add_async_update,
)
from auen._endpoints import RouterContext
from auen._query import resolve_pk_field
from auen.config import (
    ALL_OPERATIONS,
    AuthConfig,
    FilterConfig,
    HooksConfig,
    NestedWriteConfig,
    Operation,
    PaginationConfig,
    SchemaConfig,
)
from auen.policy import AllowAll, CrudPolicy
from auen.repository import AsyncCrudRepository, AsyncSqlModelRepository
from auen.schemas import derive_schema_container
from auen.types import PKType, SessionDep


class _BuildKwargs(TypedDict):
    schemas: SchemaConfig | None
    prefix: str
    tags: list[str | Enum]
    operations: Collection[Operation]
    auth: AuthConfig | None
    policy: CrudPolicy | None
    id_field: str
    pagination: PaginationConfig | None
    filters: FilterConfig | None
    repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository]
    hooks: HooksConfig | None
    nested_writes: NestedWriteConfig | None
    nested_policy_registry: dict[type[SQLModel], CrudPolicy]
    include_in_schema: set[Operation] | None
    operation_id_prefix: str


_OP_BUILDERS = {
    Operation.CREATE: add_async_create,
    Operation.NESTED_CREATE: add_async_nested_create,
    Operation.LIST: add_async_list,
    Operation.READ: add_async_read,
    Operation.UPDATE: add_async_update,
    Operation.NESTED_UPDATE: add_async_nested_update,
    Operation.DELETE: add_async_delete,
    Operation.BULK_CREATE: add_async_bulk_create,
    Operation.BULK_UPDATE: add_async_bulk_update,
    Operation.BULK_DELETE: add_async_bulk_delete,
}


def _build_one_router(
    *,
    model: type[SQLModel],
    session_dep: Callable[..., SessionDep],
    schemas: SchemaConfig | None,
    prefix: str,
    tags: list[str | Enum],
    operations: Collection[Operation],
    auth: AuthConfig | None,
    policy: CrudPolicy | None,
    id_field: str,
    pagination: PaginationConfig | None,
    filters: FilterConfig | None,
    repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository],
    hooks: HooksConfig | None,
    nested_writes: NestedWriteConfig | None,
    nested_policy_registry: dict[type[SQLModel], CrudPolicy],
    include_in_schema: set[Operation] | None,
    operation_id_prefix: str,
) -> APIRouter:
    """Build a single APIRouter with CRUD endpoints for one model."""
    model_name = model.__name__
    pk_name, pk_type = resolve_pk_field(model, id_field or None)
    pk_type = cast("PKType", pk_type)

    if schemas is None:
        schemas = derive_schema_container(model).to_config()
    if not prefix:
        prefix = f"/{model_name.lower()}s"
    elif not prefix.startswith("/"):
        prefix = f"/{prefix}"
    tags_list: list[str | Enum] = [model_name] if not tags else list(tags)

    router = APIRouter(prefix=prefix, tags=tags_list)

    effective_filters = (
        filters if filters and (filters.fields or filters.sort_fields) else None
    )

    ctx = RouterContext(
        router=router,
        model=model,
        model_name=model_name,
        schemas=schemas,
        policy=policy or AllowAll(),
        session_dep=session_dep,
        auth=auth,
        pk_name=pk_name,
        pk_type=pk_type,
        pagination=pagination or PaginationConfig(),
        filters=effective_filters,
        repository_factory=repository_factory,
        hooks=hooks or HooksConfig(),
        nested_writes=nested_writes,
        nested_policy_registry=nested_policy_registry,
        include_in_schema=include_in_schema or set(operations),
        operation_id_prefix=operation_id_prefix or model_name.lower(),
    )

    for op, builder in _OP_BUILDERS.items():
        if op in operations:
            builder(ctx)

    return router


class CrudRouterBuilder:
    """Fluent builder for CRUD routers.

    Accepts a single model or a list of models.
    Use ``build()`` for a single model, ``build_all()`` for multiple.
    """

    def __init__(
        self,
        models: type[SQLModel] | list[type[SQLModel]],
        session_dep: Callable[..., SessionDep],
    ) -> None:
        if isinstance(models, list):
            self._models = models
            self._single = False
        else:
            self._models = [models]
            self._single = True
        self._session_dep = session_dep
        self._schemas: SchemaConfig | None = None
        self._prefix: str = ""
        self._tags: list[str | Enum] = []
        self._operations: Collection[Operation] = ALL_OPERATIONS
        self._auth: AuthConfig | None = None
        self._policy: CrudPolicy | None = None
        self._id_field: str = ""
        self._pagination: PaginationConfig | None = None
        self._filters: FilterConfig | None = None
        self._repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository] = (
            AsyncSqlModelRepository
        )
        self._hooks: HooksConfig | None = None
        self._nested_writes: NestedWriteConfig | None = None
        self._nested_policy_registry: dict[type[SQLModel], CrudPolicy] = {}
        self._include_in_schema: set[Operation] | None = None
        self._operation_id_prefix: str = ""

    @classmethod
    def for_model(
        cls,
        model: type[SQLModel] | list[type[SQLModel]],
        session_dep: Callable[..., SessionDep],
    ) -> Self:
        return cls(model, session_dep)

    # --- Fluent setters ---

    def with_schemas(self, schemas: SchemaConfig) -> Self:
        self._schemas = schemas
        return self

    def with_prefix(self, prefix: str) -> Self:
        self._prefix = prefix
        return self

    def with_tags(self, tags: Sequence[str | Enum]) -> Self:
        self._tags = list(tags)
        return self

    def with_operations(self, operations: Collection[Operation]) -> Self:
        self._operations = operations
        return self

    def with_auth(self, auth: AuthConfig) -> Self:
        self._auth = auth
        return self

    def with_policy(self, policy: CrudPolicy) -> Self:
        self._policy = policy
        return self

    def with_id_field(self, id_field: str) -> Self:
        self._id_field = id_field
        return self

    def with_pagination(self, pagination: PaginationConfig) -> Self:
        self._pagination = pagination
        return self

    def with_filters(self, filters: FilterConfig) -> Self:
        self._filters = filters
        return self

    def with_repository_factory(
        self,
        repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository],
    ) -> Self:
        self._repository_factory = repository_factory
        return self

    def with_hooks(self, hooks: HooksConfig) -> Self:
        self._hooks = hooks
        return self

    def with_nested_writes(self, nested_writes: NestedWriteConfig) -> Self:
        self._nested_writes = nested_writes
        return self

    def with_nested_policy_registry(
        self, registry: dict[type[SQLModel], CrudPolicy]
    ) -> Self:
        self._nested_policy_registry = registry
        return self

    def with_include_in_schema(self, ops: set[Operation]) -> Self:
        self._include_in_schema = ops
        return self

    def with_operation_id_prefix(self, prefix: str) -> Self:
        self._operation_id_prefix = prefix
        return self

    # --- Build ---

    def _build_kwargs(self) -> _BuildKwargs:
        return {
            "schemas": self._schemas,
            "prefix": self._prefix,
            "tags": self._tags,
            "operations": self._operations,
            "auth": self._auth,
            "policy": self._policy,
            "id_field": self._id_field,
            "pagination": self._pagination,
            "filters": self._filters,
            "repository_factory": self._repository_factory,
            "hooks": self._hooks,
            "nested_writes": self._nested_writes,
            "nested_policy_registry": self._nested_policy_registry,
            "include_in_schema": self._include_in_schema,
            "operation_id_prefix": self._operation_id_prefix,
        }

    def build(self) -> APIRouter:
        """Build a single APIRouter. Raises if multiple models were given."""
        if not self._single:
            msg = "build() requires a single model; use build_all() for multiple models"
            raise ValueError(msg)
        return _build_one_router(
            model=self._models[0],
            session_dep=self._session_dep,
            **self._build_kwargs(),
        )

    def build_all(self) -> list[APIRouter]:
        """Build one APIRouter per model."""
        kwargs = self._build_kwargs()
        return [
            _build_one_router(
                model=model,
                session_dep=self._session_dep,
                **kwargs,
            )
            for model in self._models
        ]
