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

from collections.abc import Callable
from collections.abc import Callable as TypingCallable
from typing import TYPE_CHECKING, 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,
    HooksConfig,
    Operation,
    PaginationConfig,
    SchemaConfig,
    SoftDeleteConfig,
)
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
    soft_delete: SoftDeleteConfig | None
    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.

    Returns a Depends/Security marker that always resolves,
    or None if no auth is configured / operation is anonymous.
    """
    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]:
    if TYPE_CHECKING:
        return 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):
            param_name = name if op == "eq" else f"{name}__{op}"
            if op in {"in", "contains", "icontains"}:
                param_type = str | None
            else:
                param_type = annotation | None
            fields[param_name] = (cast(type, param_type), None)
    field_definitions = cast(dict[str, ModelFieldDefinition], dict(fields))
    _create_model = cast(TypingCallable[..., type[BaseModel]], create_model)
    return _create_model(
        f"{model.__name__}FilterParams", __base__=BaseModel, **field_definitions
    )


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