from math import ceil
from typing import Any, Generic, TypeVar
from collections.abc import Sequence

from fastapi import Query, Response
from fastapi_pagination import Page
from fastapi_pagination.bases import AbstractPage, AbstractParams, RawParams
from pydantic import BaseModel, Field, ConfigDict

DataType = TypeVar("DataType")
T = TypeVar("T")


class XMLResponse(Response):
    media_type = "application/xml"


# =========================
# PAGINATION PARAMS
# =========================
class PageParams(BaseModel, AbstractParams):
    page: int = Query(1, ge=1)
    size: int = Query(10, ge=1, le=10000)

    def to_raw_params(self) -> RawParams:
        return RawParams(
            limit=self.size,
            offset=(self.page - 1) * self.size,
        )


# =========================
# PAGE BASE
# =========================
class PageBase(Page[T], Generic[T]):
    previous_page: int | None = Field(
        default=None,
        description="Page number of the previous page",
    )
    next_page: int | None = Field(
        default=None,
        description="Page number of the next page",
    )


# =========================
# BASE RESPONSES
# =========================
class IResponseList(BaseModel, Generic[T]):
    model_config = ConfigDict(from_attributes=True)

    message: str = ""
    meta: dict[str, Any] = Field(default_factory=dict)
    data: list[T] | None = None


class IResponseBase(BaseModel, Generic[T]):
    model_config = ConfigDict(from_attributes=True)

    message: str = ""
    meta: dict[str, Any] = Field(default_factory=dict)
    data: T | None = None


# =========================
# PAGINATED RESPONSE
# =========================
class IGetResponsePaginated(AbstractPage[T], Generic[T]):
    model_config = ConfigDict(from_attributes=True)

    message: str = ""
    meta: dict[str, Any] = Field(default_factory=dict)
    data: PageBase[T]

    __params_type__ = PageParams  # 👈 fondamentale

    @classmethod
    def create(
        cls,
        items: Sequence[T],
        total: int,
        params: AbstractParams,
    ) -> "IGetResponsePaginated[T]":
        raw_params = params.to_raw_params()

        size = raw_params.limit or 10
        offset = raw_params.offset or 0

        page = (offset // size) + 1 if size > 0 else 1
        pages = ceil(total / size) if size > 0 else 0

        return cls(
            message="Data paginated correctly",
            meta={},
            data=PageBase[T](
                items=list(items),
                page=page,
                size=size,
                total=total,
                pages=pages,
                next_page=page + 1 if page < pages else None,
                previous_page=page - 1 if page > 1 else None,
            ),
        )


# =========================
# SPECIALIZED RESPONSES
# =========================
class IGetResponseBase(IResponseBase[DataType], Generic[DataType]):
    message: str = "Data got correctly"


class IPostResponseBase(IResponseBase[DataType], Generic[DataType]):
    message: str = "Data created correctly"


class IPutResponseBase(IResponseBase[DataType], Generic[DataType]):
    message: str = "Data updated correctly"


class IPatchResponseBase(IResponseBase[DataType], Generic[DataType]):
    message: str = "Data updated correctly"


class IDeleteResponseBase(IResponseBase[DataType], Generic[DataType]):
    message: str = "Data deleted correctly"


# =========================
# RESPONSE FACTORY
# =========================
def create_response(
    data: DataType,
    message: str | None = None,
    meta: dict | Any | None = {},
) -> (
    IResponseBase[DataType]
    | IGetResponsePaginated[DataType]
    | IGetResponseBase[DataType]
    | IPutResponseBase[DataType]
    | IDeleteResponseBase[DataType]
    | IPostResponseBase[DataType]
):
    if isinstance(data, IGetResponsePaginated):
        data.message = "Data paginated correctly" if message is None else message
        data.meta = meta
        return data
    if message is None:
        return {"data": data, "meta": meta}
    return {"data": data, "message": message, "meta": meta}


def create_list_response(
    data: list[DataType],
    message: str | None = None,
    meta: dict[str, Any] | None = None,
) -> IResponseList[DataType]:
    payload = IResponseList[DataType](
        data=data,
        meta=meta or {},
    )
    if message is not None:
        payload.message = message
    return payload