"""Bulk endpoint builders."""

from types import GenericAlias
from typing import TYPE_CHECKING, Annotated

from fastapi import Depends, status
from pydantic import BaseModel
from sqlmodel import SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession

from auen._async_endpoints_common import _op_kwargs, _set_param_annotations
from auen._endpoints import RouterContext, _build_identifier_schema, _make_user_dep
from auen.config import Operation
from auen.exceptions import ForbiddenError, NotFoundError
from auen.types import User


def add_async_bulk_create(ctx: RouterContext) -> None:
    """Register the async BULK CREATE endpoint."""
    create_schema = ctx.schemas.create
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.BULK_CREATE)
    model = ctx.model
    repo_factory = ctx.repository_factory

    if TYPE_CHECKING:
        bulk_response_model = list[BaseModel]
    else:
        bulk_response_model = list[read_schema]

    @ctx.router.post(
        "/bulk",
        response_model=bulk_response_model,
        status_code=status.HTTP_201_CREATED,
        **_op_kwargs(ctx, Operation.BULK_CREATE),
    )
    async def bulk_create(
        objects_in: list[BaseModel],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> list[SQLModel]:
        pol = ctx.policy
        for obj in objects_in:
            if not pol.can_create(user, obj):
                raise ForbiddenError()
        repo = repo_factory(model)
        return list(await repo.bulk_create(session, objects_in=objects_in))

    _set_param_annotations(bulk_create, {"objects_in": GenericAlias(list, create_schema)})


def add_async_bulk_update(ctx: RouterContext) -> None:
    """Register the async BULK UPDATE endpoint."""
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.BULK_UPDATE)
    model = ctx.model
    model_name = ctx.model_name
    repo_factory = ctx.repository_factory
    pol = ctx.policy
    pk_name = ctx.pk_name

    if TYPE_CHECKING:
        bulk_update_response = list[BaseModel]
    else:
        bulk_update_response = list[read_schema]

    @ctx.router.patch(
        "/bulk",
        response_model=bulk_update_response,
        **_op_kwargs(ctx, Operation.BULK_UPDATE),
    )
    async def bulk_update(
        items_in: list[BaseModel],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> list[SQLModel]:
        repo = repo_factory(model)
        pairs: list[tuple[SQLModel, BaseModel]] = []
        for item in items_in:
            pk = getattr(item, pk_name)
            db_obj = await repo.get(session, pk)
            if db_obj is None:
                raise NotFoundError(model_name, pk_name, pk)
            if not pol.can_update(user, db_obj, item):
                raise ForbiddenError()
            update_data = item.model_dump(exclude={pk_name}, exclude_unset=True)
            update_obj = ctx.schemas.update.model_validate(update_data)
            pairs.append((db_obj, update_obj))
        return list(await repo.bulk_update(session, items=pairs))

    _set_param_annotations(
        bulk_update,
        {"items_in": GenericAlias(list, read_schema)},
    )


def add_async_bulk_delete(ctx: RouterContext) -> None:
    """Register the async BULK DELETE endpoint."""
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.BULK_DELETE)
    model = ctx.model
    model_name = ctx.model_name
    repo_factory = ctx.repository_factory
    pol = ctx.policy
    pk_name = ctx.pk_name
    pk_type = ctx.pk_type

    identifier_schema = _build_identifier_schema(model_name, pk_name, pk_type)

    if TYPE_CHECKING:
        bulk_delete_response = list[BaseModel]
    else:
        bulk_delete_response = list[read_schema]

    @ctx.router.delete(
        "/bulk",
        response_model=bulk_delete_response,
        **_op_kwargs(ctx, Operation.BULK_DELETE),
    )
    async def bulk_delete(
        items_in: list[BaseModel],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> list[SQLModel]:
        repo = repo_factory(model)
        db_objects: list[SQLModel] = []
        for item in items_in:
            pk = getattr(item, pk_name)
            db_obj = await repo.get(session, pk)
            if db_obj is None:
                raise NotFoundError(model_name, pk_name, pk)
            if not pol.can_delete(user, db_obj):
                raise ForbiddenError()
            db_objects.append(db_obj)
        await repo.bulk_delete(session, db_objects=db_objects)
        return db_objects

    _set_param_annotations(
        bulk_delete,
        {"items_in": GenericAlias(list, identifier_schema)},
    )
