"""
Pagination utilities for list endpoints.

Provides a FastAPI Depends-compatible query parameter parser and a helper
function that applies offset/limit to a SQLAlchemy async query and returns
a fully-populated PaginatedResponse.

Usage in route handler:
    @router.get("/patients")
    async def list_patients(
        params: PaginationParams = Depends(),
        db: AsyncSession = Depends(get_db),
    ) -> PaginatedResponse[PatientResponse]:
        return await paginate(db, select(Patient).where(...), params, PatientResponse)
"""

import math
from typing import Any, TypeVar

from fastapi import Query
from pydantic import BaseModel
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql import Select

from app.core.responses import PaginatedResponse, PaginationMeta

T = TypeVar("T", bound=BaseModel)

# Hard upper bound — prevents a client requesting 10 000 rows in one call.
MAX_PAGE_LIMIT = 200


class PaginationParams:
    """
    Query-parameter-based pagination for all list endpoints.

    Injected via FastAPI Depends:
        params: PaginationParams = Depends()

    Query string: ?page=1&limit=20
    """

    def __init__(
        self,
        page: int = Query(default=1, ge=1, description="Page number (1-indexed)"),
        limit: int = Query(
            default=20,
            ge=1,
            le=MAX_PAGE_LIMIT,
            description=f"Records per page (max {MAX_PAGE_LIMIT})",
        ),
    ) -> None:
        self.page = page
        self.limit = limit

    @property
    def offset(self) -> int:
        """Zero-based row offset for SQL OFFSET clause."""
        return (self.page - 1) * self.limit


async def paginate(
    db: AsyncSession,
    query: Select[Any],
    params: PaginationParams,
    schema: type[T],
) -> PaginatedResponse[T]:
    """
    Execute a SQLAlchemy SELECT query with pagination.

    Runs two queries:
      1. COUNT(*) for the total number of matching rows.
      2. The original query with LIMIT + OFFSET applied.

    Args:
        db:     Active async database session.
        query:  The base SELECT statement (no LIMIT/OFFSET applied yet).
        params: PaginationParams parsed from the request query string.
        schema: Pydantic response schema to validate each row against.

    Returns:
        PaginatedResponse containing the current page of data and pagination metadata.
    """
    # ── Count query ───────────────────────────────────────────────────────────
    count_query = select(func.count()).select_from(query.subquery())
    total_result = await db.execute(count_query)
    total: int = total_result.scalar_one()

    # ── Data query ────────────────────────────────────────────────────────────
    data_query = query.offset(params.offset).limit(params.limit)
    data_result = await db.execute(data_query)
    rows = data_result.scalars().all()

    items = [schema.model_validate(row, from_attributes=True) for row in rows]
    pages = math.ceil(total / params.limit) if total > 0 else 1

    return PaginatedResponse(
        data=items,
        meta=PaginationMeta(
            total=total,
            page=params.page,
            limit=params.limit,
            pages=pages,
        ),
    )
