"""Shared endpoint utilities and context for CRUD operations."""

from collections.abc import Callable
from typing import Any, cast

from fastapi import APIRouter, Depends, Security
from fastapi.params import Depends as DependsParam
from fastapi.params import Security as SecurityParam
from pydantic import BaseModel, ConfigDict, create_model
from pydantic.dataclasses import dataclass
from sqlmodel import SQLModel

from auen.config import (
    AuthConfig,
    FilterConfig,
    FilterOp,
    HooksConfig,
    NestedWriteConfig,
    Operation,
    PaginationConfig,
    SchemaConfig,
)
from auen.policy import CrudPolicy
from auen.repository import AsyncCrudRepository
from auen.types import ModelFieldDefinition, PKType, SessionDep


@dataclass(config=ConfigDict(arbitrary_types_allowed=True))
class RouterContext:
    """Shared state for endpoint builders."""

    router: APIRouter
    model: type[SQLModel]
    model_name: str
    schemas: SchemaConfig
    policy: CrudPolicy
    session_dep: Callable[..., SessionDep]
    auth: AuthConfig | None
    pk_name: str
    pk_type: PKType
    pagination: PaginationConfig
    filters: FilterConfig | None
    repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository]
    hooks: HooksConfig
    nested_writes: NestedWriteConfig | None
    nested_policy_registry: dict[type[SQLModel], CrudPolicy]
    include_in_schema: set[Operation]
    operation_id_prefix: str


def _make_user_dep(
    auth: AuthConfig | None, op: Operation
) -> DependsParam | SecurityParam:
    """Build a unified user dependency for an operation."""
    if auth is None or op in auth.allow_anonymous:

        def _anon() -> None:
            return None

        return Depends(_anon)
    if auth.mode == "security":
        scopes = auth.scopes_by_operation.get(op, [])
        return Security(auth.dependency, scopes=scopes)
    return Depends(auth.dependency)


def _build_filter_model(
    model: type[SQLModel], filter_config: FilterConfig
) -> type[BaseModel]:
    fields: dict[str, ModelFieldDefinition] = {}
    for name, field_cfg in filter_config.fields.items():
        field_info = model.model_fields.get(name)
        if field_info is None:
            continue
        annotation = field_info.annotation or type(None)
        for op in sorted(field_cfg.ops, key=lambda o: o.value):
            param_name = name if op == FilterOp.EQ else f"{name}__{op.value}"
            if op in {FilterOp.IN, FilterOp.CONTAINS, FilterOp.ICONTAINS}:
                param_type = str | None
            else:
                param_type = annotation | None
            fields[param_name] = (cast(type, param_type), None)
    return create_model(
        f"{model.__name__}FilterParams",
        __base__=BaseModel,
        **cast(dict[str, Any], fields),
    )


def _build_page_model(read_schema: type[BaseModel]) -> type[BaseModel]:
    items_type = list.__class_getitem__(read_schema)
    return create_model(
        f"{read_schema.__name__}Page",
        __base__=BaseModel,
        items=(items_type, ...),
        total=(int, ...),
        limit=(int, ...),
        offset=(int, ...),
    )


def _build_identifier_schema(
    model_name: str,
    pk_name: str,
    pk_type: type,
) -> type[BaseModel]:
    """Build a minimal schema containing only the PK field."""
    _create_model = cast(Callable[..., type[BaseModel]], create_model)
    return _create_model(
        f"{model_name}Identifier",
        __base__=BaseModel,
        **{pk_name: (pk_type, ...)},
    )
