import math
from typing import Dict, List, Type, TypeVar, Optional, Any

from pydantic import BaseModel
from sqlalchemy.orm import Query

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


class QueryPaginator:
    """
    DB collection paginator.

    :param query: SQLAlchemy Query object
    :param schema: Pydantic BaseModel schema for response data serialization
    :param url: Base URL for pagination
    :param offset: Number of records to skip
    :param limit: Number of records to show
    :param use_orm: Flag to indicate whether to use ORM
    """

    default_offset = 0
    default_limit = 10
    max_offset = None
    max_limit = 1000

    def __init__(
        self,
        query: Query,
        schema: Type[ResponseSchemaType],
        url: str,
        offset: int = default_offset,
        limit: int = default_limit,
        use_orm: bool = True,
    ):
        self.query = query
        self.schema = schema
        self.url = url
        self.offset = offset
        self.limit = limit
        self.count = None
        self.list: List[ResponseSchemaType] = []
        self.use_orm = use_orm

    def get_count(self) -> int:
        """Retrieve count of the records."""
        if self.count is None:
            self.count = self.query.count()
        return self.count

    def get_current_page(self) -> int:
        """Retrieve the current page number."""
        return math.floor(self.offset / self.limit) + 1

    def get_next_url(self) -> Optional[str]:
        """Construct URL for the next page of results."""
        if self.offset + self.limit >= self.get_count():
            return None
        return f"{self.url}?page={self.get_current_page() + 1}&per_page={self.limit}"

    def get_last_url(self) -> str:
        """Construct URL for the last page of results."""
        last_page = math.ceil(self.get_count() / self.limit)
        if last_page == 0:
            last_page += 1
        return f"{self.url}?page={last_page}&per_page={self.limit}"

    def get_first_url(self) -> str:
        """Construct URL for the first page of results."""
        return f"{self.url}?page=1&per_page={self.limit}"

    def get_previous_url(self) -> Optional[str]:
        """Construct URL for the previous page of results."""
        if self.offset <= 0:
            return None
        if self.offset - self.limit <= 0:
            return self.get_first_url()
        return f"{self.url}?page={self.get_current_page() - 1}&per_page={self.limit}"

    def get_list(self) -> List[ResponseSchemaType]:
        """Retrieve the list of records."""
        items = self.query.offset(self.offset).limit(self.limit).all()
        if self.use_orm:
            # self.list = [self.schema.model_validate(item) for item in items]
            # Tell Pydantic v2 to treat item as an ORM model
            self.list = [self.schema.model_validate(item, from_attributes=True) for item in items]
        else:
            # Using model_validate with a dict conversion for non-ORM objects
            # self.list = [self.schema.model_validate(item._asdict()) for item in items]
            # For raw tuples or dict-like results (e.g. from .values())
            self.list = [self.schema.model_validate(item._asdict()) for item in items]
        return self.list


    def paginate(self) -> Dict[str, Any]:
        """Perform pagination and return the paginated response."""
        results = self.get_list()
        return {
            "total": self.get_count(),
            "page": self.get_current_page(),
            "per_page": self.limit,
            "next": self.get_next_url(),
            "previous": self.get_previous_url(),
            "first": self.get_first_url(),
            "last": self.get_last_url(),
            "result": results,
        }