"""Async endpoint builders for CRUD operations."""

from types import GenericAlias
from typing import Annotated

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

from auen._async_endpoints_bulk_ops import (
    add_async_bulk_create,
    add_async_bulk_delete,
    add_async_bulk_update,
)
from auen._async_endpoints_common import (
    _build_nested_create_schema,
    _build_nested_update_schema,
    _op_kwargs,
    _resolve_relation_fk_field,
)
from auen._async_endpoints_nested_ops import (
    add_async_nested_create,
    add_async_nested_update,
)
from auen._endpoints import (
    RouterContext,
    _build_filter_model,
    _build_page_model,
    _make_user_dep,
)
from auen._query import apply_filters, apply_sorting
from auen.body_parsers import make_model_body_parser, make_path_value_parser
from auen.config import FilterConfig, Operation
from auen.exceptions import ForbiddenError, NotFoundError
from auen.types import ListResponse, PKValue, ResponseModel, User

__all__ = [
    "_build_nested_create_schema",
    "_build_nested_update_schema",
    "_resolve_relation_fk_field",
    "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",
]


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

    @ctx.router.post(
        "/",
        response_model=read_schema,
        status_code=status.HTTP_201_CREATED,
        **_op_kwargs(ctx, Operation.CREATE),
    )
    async def create_resource(
        obj_in: Annotated[BaseModel, body_dep],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> SQLModel:
        pol = ctx.policy
        if not pol.can_create(user, obj_in):
            raise ForbiddenError()
        hooks = ctx.hooks
        if hooks.before_create:
            await hooks.before_create(session, obj_in, user)
        repo = repo_factory(model)
        db_obj = await repo.create(session, obj_in=obj_in)
        if hooks.after_create:
            await hooks.after_create(session, db_obj, user)
        return db_obj


async def _list_core(
    *,
    ctx: RouterContext,
    session: AsyncSession,
    user: User,
    offset: int,
    limit: int,
    sort: str | None,
    filter_params: BaseModel | None,
) -> ListResponse:
    """Shared LIST logic for all pagination styles."""
    model = ctx.model
    filters = ctx.filters
    pol = ctx.policy
    pg = ctx.pagination

    query = select(model)
    query = pol.filter_list_query(user, query)
    if filters and filter_params is not None:
        query = apply_filters(
            query,
            model,
            filters,
            filter_params.model_dump(exclude_none=True),
        )
    query = apply_sorting(query, model, filters, sort)
    repo = ctx.repository_factory(model)
    items = list(await repo.list(session, query=query, offset=offset, limit=limit))
    if pg.include_total:
        total = await repo.count(session, query=query)
        return {"items": items, "total": total, "limit": limit, "offset": offset}
    return items


def add_async_list(ctx: RouterContext) -> None:
    """Register the async LIST endpoint."""
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.LIST)
    filters = ctx.filters
    pg = ctx.pagination

    response_model: ResponseModel = GenericAlias(list, read_schema)
    if pg.include_total:
        response_model = _build_page_model(read_schema)

    _sort_query = Query(default=None)

    # Build filter model: always present (empty if no filters configured)
    effective_filters = filters or FilterConfig()
    filter_model = _build_filter_model(ctx.model, effective_filters)
    filter_dep = Depends(filter_model)

    route_kwargs = _op_kwargs(ctx, Operation.LIST)

    if pg.style == "page":
        _page_query = Query(default=1, ge=1)
        _size_query = Query(default=pg.default_limit, ge=1, le=pg.max_limit)

        @ctx.router.get("/", response_model=response_model, **route_kwargs)
        async def list_resources_page(
            session: Annotated[AsyncSession, Depends(ctx.session_dep)],
            user: Annotated[User, user_dep],
            filter_params: Annotated[BaseModel, filter_dep],
            page: int = _page_query,
            size: int = _size_query,
            sort: str | None = _sort_query,
        ) -> ListResponse:
            offset = (page - 1) * size
            return await _list_core(
                ctx=ctx,
                session=session,
                user=user,
                offset=offset,
                limit=size,
                sort=sort,
                filter_params=filter_params if filters else None,
            )

    else:
        _offset_query = Query(default=0, ge=0)
        _limit_query = Query(default=pg.default_limit, ge=1, le=pg.max_limit)

        @ctx.router.get("/", response_model=response_model, **route_kwargs)
        async def list_resources_limit_offset(
            session: Annotated[AsyncSession, Depends(ctx.session_dep)],
            user: Annotated[User, user_dep],
            filter_params: Annotated[BaseModel, filter_dep],
            offset: int = _offset_query,
            limit: int = _limit_query,
            sort: str | None = _sort_query,
        ) -> ListResponse:
            return await _list_core(
                ctx=ctx,
                session=session,
                user=user,
                offset=offset,
                limit=limit,
                sort=sort,
                filter_params=filter_params if filters else None,
            )


def add_async_read(ctx: RouterContext) -> None:
    """Register the async READ endpoint."""
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.READ)
    model = ctx.model
    model_name = ctx.model_name
    repo_factory = ctx.repository_factory
    pol = ctx.policy
    pk_name = ctx.pk_name
    pk_dep = Depends(make_path_value_parser(pk_name=pk_name, pk_type=ctx.pk_type))

    @ctx.router.get(
        "/{" + pk_name + "}",
        response_model=read_schema,
        **_op_kwargs(ctx, Operation.READ),
    )
    async def read_resource(
        id: Annotated[PKValue, pk_dep],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> SQLModel:
        pk_value = id
        repo = repo_factory(model)
        db_obj = await repo.get(session, pk_value)
        if db_obj is None:
            raise NotFoundError(model_name, pk_name, pk_value)
        if not pol.can_read(user, db_obj):
            raise ForbiddenError()
        return db_obj


def add_async_update(ctx: RouterContext) -> None:
    """Register the async UPDATE endpoint."""
    update_schema = ctx.schemas.update
    read_schema = ctx.schemas.read
    user_dep = _make_user_dep(ctx.auth, Operation.UPDATE)
    body_dep = Depends(make_model_body_parser(update_schema))
    model = ctx.model
    model_name = ctx.model_name
    repo_factory = ctx.repository_factory
    pol = ctx.policy
    pk_name = ctx.pk_name
    pk_dep = Depends(make_path_value_parser(pk_name=pk_name, pk_type=ctx.pk_type))

    @ctx.router.patch(
        "/{" + pk_name + "}",
        response_model=read_schema,
        **_op_kwargs(ctx, Operation.UPDATE),
    )
    async def update_resource(
        id: Annotated[PKValue, pk_dep],
        obj_in: Annotated[BaseModel, body_dep],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> SQLModel:
        pk_value = id
        repo = repo_factory(model)
        db_obj = await repo.get(session, pk_value)
        if db_obj is None:
            raise NotFoundError(model_name, pk_name, pk_value)
        if not pol.can_update(user, db_obj, obj_in):
            raise ForbiddenError()
        hooks = ctx.hooks
        if hooks.before_update:
            await hooks.before_update(session, db_obj, user)
        result = await repo.update(session, db_obj=db_obj, obj_in=obj_in)
        if hooks.after_update:
            await hooks.after_update(session, result, user)
        return result


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

    @ctx.router.delete(
        "/{" + pk_name + "}",
        response_model=read_schema,
        **_op_kwargs(ctx, Operation.DELETE),
    )
    async def delete_resource(
        id: Annotated[PKValue, pk_dep],
        session: Annotated[AsyncSession, Depends(ctx.session_dep)],
        user: Annotated[User, user_dep],
    ) -> SQLModel:
        pk_value = id
        repo = repo_factory(model)
        db_obj = await repo.get(session, pk_value)
        if db_obj is None:
            raise NotFoundError(model_name, pk_name, pk_value)
        if not pol.can_delete(user, db_obj):
            raise ForbiddenError()
        hooks = ctx.hooks
        if hooks.before_delete:
            await hooks.before_delete(session, db_obj, user)
        await repo.delete(session, db_obj=db_obj)
        if hooks.after_delete:
            await hooks.after_delete(session, db_obj, user)
        return db_obj
