from abc import ABC, abstractmethod
from typing import Any, Callable, Type, Optional

from fastapi import Depends, FastAPI, Request
from pydantic import BaseModel, create_model


class SelectEngine(ABC):

    @abstractmethod
    def select(self, query: str, *params: Any) -> list[Any]:
        pass

    @abstractmethod
    def select_count(self, query: str, *params: Any) -> int:
        pass


class QueryContext(BaseModel):
    """
    Context object passed to middlewares, containing all query-related data
    that can be modified by middleware.
    """
    select_query: str
    select_params: list[Any]
    count_query: Optional[str] = None
    count_params: list[Any]
    filters: BaseModel
    limit: int
    offset: int
    dependency_results: dict[str, Any]


# Middleware type that receives QueryContext and next middleware callable
# and returns the ResponseModel (data_model)
Middleware = Callable[[QueryContext, Callable[[QueryContext], Any]], Any]


def serve_select(
    app: FastAPI,
    query_engine: SelectEngine,
    select_query: str,
    select_param: Callable[[BaseModel, int, int], list[Any]] | None = None,
    count_query: str | None = None,
    count_param: Callable[[BaseModel], list[Any]] | None = None,
    path: str = "/get",
    filter_model: Type[BaseModel] | None = None,
    data_model: Type[BaseModel] | None = None,
    dependencies: list[Any] | None = None,
    middlewares: list[Middleware] | None = None,
):
    """
    Adding fastapi route to app with proper annotation:
    - taking filter_model as parameter
    - return pydantic model with two property:
        - data (data_model)
        - count (int)
    - supports FastAPI dependencies
    - supports middleware system
    """
    if filter_model is None:
        filter_model = create_model("FilterModel")
    if data_model is None:
        data_model = create_model("DataModel")
    if dependencies is None:
        dependencies = []
    if middlewares is None:
        middlewares = []

    class ResponseModel(BaseModel):
        data: list[data_model] | list[Any]
        count: int | None = None

    def create_middleware_chain():
        """Create the middleware chain with FastAPI-style middleware pattern."""

        def final_handler(context: QueryContext) -> ResponseModel:
            """The final handler that executes the queries using the processed context."""
            data = query_engine.select(context.select_query, *context.select_params)
            
            if context.count_query:
                total = query_engine.select_count(context.count_query, *context.count_params)
                return ResponseModel(data=data, count=total)
            return ResponseModel(data=data)

        # Build the middleware chain from right to left (like FastAPI)
        handler = final_handler
        for middleware in reversed(middlewares):
            # Create a closure-safe wrapper
            def make_wrapper(current_middleware, next_handler):
                def wrapper(context: QueryContext):
                    return current_middleware(context, lambda: next_handler(context))
                return wrapper

            handler = make_wrapper(middleware, handler)

        return handler

    # Create dependency wrappers to capture results
    wrapped_dependencies = []
    for dep in dependencies:
        if hasattr(dep, "dependency"):
            # Create a closure-safe wrapper for each dependency
            def create_wrapped_dependency(original_dep):
                async def wrapped_dependency(request: Request):
                    result = await original_dep.dependency()
                    # Store the result in request state
                    if not hasattr(request.state, "dependency_results"):
                        request.state.dependency_results = {}
                    # Use the dependency function name as key
                    dep_name = original_dep.dependency.__name__
                    request.state.dependency_results[dep_name] = result
                    return result

                return wrapped_dependency

            wrapped_dependencies.append(Depends(create_wrapped_dependency(dep)))
        else:
            wrapped_dependencies.append(dep)

    # Create the route with dependencies
    @app.get(path, response_model=ResponseModel, dependencies=wrapped_dependencies)
    def get(
        filters: filter_model = Depends(),
        limit: int = 100,
        offset: int = 0,
        request: Request = None,
    ):
        """
        This route is automatically generated by dbanu
        """
        # Extract dependency results from request state
        dependency_results = {}
        if request and hasattr(request.state, "dependency_results"):
            dependency_results = request.state.dependency_results

        # Build initial select parameters
        select_args = (
            select_param(filters, limit, offset)
            if select_param is not None
            else [limit, offset]
        )
        
        # Build initial count parameters
        count_args = count_param(filters) if count_param is not None else []

        # Create initial QueryContext
        initial_context = QueryContext(
            select_query=select_query,
            select_params=select_args,
            count_query=count_query,
            count_params=count_args,
            filters=filters,
            limit=limit,
            offset=offset,
            dependency_results=dependency_results,
        )

        handler = create_middleware_chain()
        return handler(initial_context)