from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    Generic,
    Iterator,
    List,
    Optional,
    Type,
    TypeVar,
    AsyncIterator,
)

from pydantic import Field

from ._models import BaseModel, FinalRequestOptions

if TYPE_CHECKING:
    from ._base_client import AsyncAPIClient, SyncAPIClient

_T = TypeVar("_T")


class SyncPage(BaseModel, Generic[_T]):
    items: List[Any] = []
    page: int = Field(default=1)
    page_size: int = Field(default=20, alias="pageSize")
    total: int = 0

    _model: Type[_T] | None = None
    _client: SyncAPIClient | None = None
    _options: FinalRequestOptions | None = None

    model_config = BaseModel.model_config.copy()
    model_config["arbitrary_types_allowed"] = True

    @property
    def has_more(self) -> bool:
        return self.page * self.page_size < self.total

    def _set_private_attributes(
        self,
        client: SyncAPIClient,
        model: Type[_T],
        options: FinalRequestOptions,
    ) -> None:
        self._client = client
        self._model = model
        self._options = options

    def _parse_items(self) -> list[_T]:
        if self._model is None:
            return self.items  # type: ignore[return-value]
        return [
            self._model.model_validate(item) if isinstance(item, dict) else item
            for item in self.items
        ]

    def __iter__(self) -> Iterator[_T]:  # type: ignore[override]
        yield from self._parse_items()
        page = self
        while page.has_more and page._client and page._options:
            page = page._get_next_page()
            yield from page._parse_items()

    def _get_next_page(self) -> SyncPage[_T]:
        assert self._client is not None
        assert self._options is not None
        params = dict(self._options.params or {})
        params["page"] = self.page + 1
        params["pageSize"] = self.page_size
        opts = self._options.model_copy(update={"params": params})
        result = self._client.request(type(self), opts)
        result._set_private_attributes(
            client=self._client,
            model=self._model,  # type: ignore[arg-type]
            options=self._options,
        )
        return result


class AsyncPage(BaseModel, Generic[_T]):
    items: List[Any] = []
    page: int = Field(default=1)
    page_size: int = Field(default=20, alias="pageSize")
    total: int = 0

    _model: Type[_T] | None = None
    _client: AsyncAPIClient | None = None
    _options: FinalRequestOptions | None = None

    model_config = BaseModel.model_config.copy()
    model_config["arbitrary_types_allowed"] = True

    @property
    def has_more(self) -> bool:
        return self.page * self.page_size < self.total

    def _set_private_attributes(
        self,
        client: AsyncAPIClient,
        model: Type[_T],
        options: FinalRequestOptions,
    ) -> None:
        self._client = client
        self._model = model
        self._options = options

    def _parse_items(self) -> list[_T]:
        if self._model is None:
            return self.items  # type: ignore[return-value]
        return [
            self._model.model_validate(item) if isinstance(item, dict) else item
            for item in self.items
        ]

    async def __aiter__(self) -> AsyncIterator[_T]:
        for item in self._parse_items():
            yield item
        page = self
        while page.has_more and page._client and page._options:
            page = await page._get_next_page()
            for item in page._parse_items():
                yield item

    async def _get_next_page(self) -> AsyncPage[_T]:
        assert self._client is not None
        assert self._options is not None
        params = dict(self._options.params or {})
        params["page"] = self.page + 1
        params["pageSize"] = self.page_size
        opts = self._options.model_copy(update={"params": params})
        result = await self._client.request(type(self), opts)
        result._set_private_attributes(
            client=self._client,
            model=self._model,  # type: ignore[arg-type]
            options=self._options,
        )
        return result
