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

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

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

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_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,
    Operation,
    PaginationConfig,
    SchemaConfig,
    SoftDeleteConfig,
)
from auen.policy import AllowAll, CrudPolicy
from auen.repository import AsyncCrudRepository, AsyncSqlModelRepository
from auen.schemas import derive_schemas
from auen.types import PKType, SessionDep


class RouterKwargs(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
    soft_delete: SoftDeleteConfig | None
    include_in_schema: set[Operation] | None
    operation_id_prefix: str


_OP_BUILDERS = {
    Operation.CREATE: add_async_create,
    Operation.LIST: add_async_list,
    Operation.READ: add_async_read,
    Operation.UPDATE: add_async_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 crud_router(
    *,
    model: type[SQLModel],
    session_dep: Callable[..., SessionDep],
    schemas: SchemaConfig | None = None,
    prefix: str = "",
    tags: Sequence[str | Enum] = (),
    operations: Collection[Operation] = ALL_OPERATIONS,
    auth: AuthConfig | None = None,
    policy: CrudPolicy | None = None,
    id_field: str = "",
    pagination: PaginationConfig | None = None,
    filters: FilterConfig | None = None,
    repository_factory: Callable[
        [type[SQLModel]], AsyncCrudRepository
    ] = AsyncSqlModelRepository,
    hooks: HooksConfig | None = None,
    soft_delete: SoftDeleteConfig | None = None,
    include_in_schema: set[Operation] | None = None,
    operation_id_prefix: str = "",
) -> APIRouter:
    """Generate a FastAPI APIRouter with CRUD endpoints.

    Args:
        model: SQLModel table class.
        session_dep: FastAPI dependency yielding an AsyncSession.
        schemas: Create/Read/Update schema config.
            If None, schemas are derived automatically.
        prefix: URL prefix (default: lowercase model name + "s").
        tags: OpenAPI tags.
        operations: Which CRUD operations to generate.
        auth: Authentication config.
        policy: Row-level authorization policy.
        id_field: Name of the primary key field.
        pagination: Pagination settings.
        filters: Filtering/sorting config.
        repository_factory: Factory for CRUD repository instances.
        hooks: Lifecycle hooks for before/after CRUD operations.
        soft_delete: Soft delete config. When set, DELETE sets a
            timestamp column instead of removing the row.
        include_in_schema: Operations to include in OpenAPI schema.
            Defaults to all enabled operations.
        operation_id_prefix: Prefix for OpenAPI operation IDs.

    Returns:
        A configured FastAPI APIRouter.
    """
    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_schemas(model)
    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(),
        soft_delete=soft_delete,
        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 _BuilderMixin:
    """Shared builder methods for CRUD router configuration."""

    _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
    _soft_delete: SoftDeleteConfig | None
    _include_in_schema: set[Operation] | None
    _operation_id_prefix: str

    def _init_defaults(self) -> None:
        self._schemas = None
        self._prefix = ""
        self._tags = []
        self._operations = ALL_OPERATIONS
        self._auth = None
        self._policy = None
        self._id_field = ""
        self._pagination = None
        self._filters = None
        self._repository_factory = AsyncSqlModelRepository
        self._hooks = None
        self._soft_delete = None
        self._include_in_schema = None
        self._operation_id_prefix = ""

    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_soft_delete(self, soft_delete: SoftDeleteConfig | None = None) -> Self:
        self._soft_delete = soft_delete or SoftDeleteConfig()
        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

    def _build_kwargs(self) -> RouterKwargs:
        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,
            "soft_delete": self._soft_delete,
            "include_in_schema": self._include_in_schema,
            "operation_id_prefix": self._operation_id_prefix,
        }


class CrudRouterBuilder(_BuilderMixin):
    """Fluent builder for CRUD routers.

    Accepts a single model or a list of models. When a single model
    is passed, ``build()`` returns a single ``APIRouter``. When a list
    is passed, ``build()`` returns a ``list[APIRouter]``.
    """

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

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

    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 crud_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 [
            crud_router(
                model=model,
                session_dep=self._session_dep,
                **kwargs,
            )
            for model in self._models
        ]
