"""Unit tests for pagination utilities."""

from __future__ import annotations

import pytest

from pinecone.models.pagination import AsyncPaginator, Page, Paginator


pytestmark = pytest.mark.unit


class TestPage:
    """Tests for Page struct."""

    def test_page_with_items(self) -> None:
        """Page should contain items list."""
        page: Page[str] = Page(items=["a", "b", "c"])
        assert page.items == ["a", "b", "c"]
        assert page.pagination_token is None

    def test_page_with_pagination_token(self) -> None:
        """Page should store pagination token."""
        page: Page[str] = Page(items=["a"], pagination_token="token123")
        assert page.pagination_token == "token123"

    def test_has_more_with_token(self) -> None:
        """has_more should be True when pagination_token exists."""
        page: Page[str] = Page(items=["a"], pagination_token="next")
        assert page.has_more is True

    def test_has_more_without_token(self) -> None:
        """has_more should be False when no pagination_token."""
        page: Page[str] = Page(items=["a"])
        assert page.has_more is False

    def test_empty_page(self) -> None:
        """Empty page should work correctly."""
        page: Page[str] = Page(items=[])
        assert page.items == []
        assert page.has_more is False


class TestPaginator:
    """Tests for sync Paginator."""

    def test_single_page_iteration(self) -> None:
        """Should iterate items from a single page."""
        pages_data = [
            Page(items=["a", "b", "c"]),
        ]
        call_count = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal call_count
            call_count += 1
            return pages_data[0]

        paginator: Paginator[str] = Paginator(fetch_page)
        result = list(paginator)

        assert result == ["a", "b", "c"]
        assert call_count == 1

    def test_multi_page_iteration(self) -> None:
        """Should iterate items across multiple pages."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="page2"),
            Page(items=["c", "d"], pagination_token="page3"),
            Page(items=["e"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page)
        result = list(paginator)

        assert result == ["a", "b", "c", "d", "e"]
        assert page_index == 3

    def test_pages_iteration(self) -> None:
        """Should iterate pages for batch processing."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="page2"),
            Page(items=["c"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page)
        pages = list(paginator.pages())

        assert len(pages) == 2
        assert pages[0].items == ["a", "b"]
        assert pages[0].has_more is True
        assert pages[1].items == ["c"]
        assert pages[1].has_more is False

    def test_to_list(self) -> None:
        """to_list should return all items."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="next"),
            Page(items=["c"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page)
        result = paginator.to_list()

        assert result == ["a", "b", "c"]

    def test_limit_caps_items(self) -> None:
        """limit should cap total items returned."""
        pages_data = [
            Page(items=["a", "b", "c"], pagination_token="page2"),
            Page(items=["d", "e", "f"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page, limit=4)
        result = list(paginator)

        assert result == ["a", "b", "c", "d"]

    def test_limit_stops_early(self) -> None:
        """limit should stop fetching when reached within a page."""
        pages_data = [
            Page(items=["a", "b", "c", "d", "e"], pagination_token="page2"),
            Page(items=["f", "g"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page, limit=2)
        result = list(paginator)

        assert result == ["a", "b"]
        # Should have only fetched one page
        assert page_index == 1

    def test_initial_token_resumes_pagination(self) -> None:
        """initial_token should resume from previous position."""
        tokens_received: list[str | None] = []

        def fetch_page(token: str | None) -> Page[str]:
            tokens_received.append(token)
            if token == "resume_token":
                return Page(items=["c", "d"])
            return Page(items=["a", "b"], pagination_token="next")

        paginator: Paginator[str] = Paginator(fetch_page, initial_token="resume_token")
        result = list(paginator)

        assert result == ["c", "d"]
        assert tokens_received == ["resume_token"]

    def test_pagination_token_property(self) -> None:
        """pagination_token should reflect current state."""
        pages_data = [
            Page(items=["a"], pagination_token="token1"),
            Page(items=["b"], pagination_token="token2"),
            Page(items=["c"]),
        ]
        page_index = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: Paginator[str] = Paginator(fetch_page)

        # Before iteration, token is None (no initial token)
        assert paginator.pagination_token is None

        # Iterate through pages and check token updates
        pages_iter = paginator.pages()
        next(pages_iter)
        assert paginator.pagination_token == "token1"

        next(pages_iter)
        assert paginator.pagination_token == "token2"

        next(pages_iter)
        assert paginator.pagination_token is None

    def test_empty_first_page(self) -> None:
        """Should handle empty result set."""

        def fetch_page(token: str | None) -> Page[str]:
            return Page(items=[])

        paginator: Paginator[str] = Paginator(fetch_page)
        result = list(paginator)

        assert result == []

    def test_iterator_is_single_use(self) -> None:
        """Iterator should be exhausted after first iteration."""
        call_count = 0

        def fetch_page(token: str | None) -> Page[str]:
            nonlocal call_count
            call_count += 1
            return Page(items=["a", "b"])

        paginator: Paginator[str] = Paginator(fetch_page)

        # First iteration
        result1 = list(paginator)
        assert result1 == ["a", "b"]

        # Second iteration should return empty (exhausted)
        result2 = list(paginator)
        assert result2 == []

        # Should have only fetched once
        assert call_count == 1


class TestAsyncPaginator:
    """Tests for async AsyncPaginator."""

    @pytest.mark.asyncio()
    async def test_single_page_iteration(self) -> None:
        """Should iterate items from a single page asynchronously."""

        async def fetch_page(token: str | None) -> Page[str]:
            return Page(items=["a", "b", "c"])

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page)
        result: list[str] = []
        async for item in paginator:
            result.append(item)

        assert result == ["a", "b", "c"]

    @pytest.mark.asyncio()
    async def test_multi_page_iteration(self) -> None:
        """Should iterate items across multiple pages asynchronously."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="page2"),
            Page(items=["c"]),
        ]
        page_index = 0

        async def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page)
        result: list[str] = []
        async for item in paginator:
            result.append(item)

        assert result == ["a", "b", "c"]

    @pytest.mark.asyncio()
    async def test_pages_iteration(self) -> None:
        """Should iterate pages asynchronously."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="page2"),
            Page(items=["c"]),
        ]
        page_index = 0

        async def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page)
        pages: list[Page[str]] = []
        async for page in paginator.pages():
            pages.append(page)

        assert len(pages) == 2
        assert pages[0].items == ["a", "b"]
        assert pages[1].items == ["c"]

    @pytest.mark.asyncio()
    async def test_to_list(self) -> None:
        """to_list should return all items asynchronously."""
        pages_data = [
            Page(items=["a", "b"], pagination_token="next"),
            Page(items=["c"]),
        ]
        page_index = 0

        async def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page)
        result = await paginator.to_list()

        assert result == ["a", "b", "c"]

    @pytest.mark.asyncio()
    async def test_limit_caps_items(self) -> None:
        """limit should cap total items returned asynchronously."""
        pages_data = [
            Page(items=["a", "b", "c"], pagination_token="page2"),
            Page(items=["d", "e"]),
        ]
        page_index = 0

        async def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page, limit=4)
        result = await paginator.to_list()

        assert result == ["a", "b", "c", "d"]

    @pytest.mark.asyncio()
    async def test_initial_token_resumes_pagination(self) -> None:
        """initial_token should resume from previous position."""
        tokens_received: list[str | None] = []

        async def fetch_page(token: str | None) -> Page[str]:
            tokens_received.append(token)
            return Page(items=["resumed"])

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page, initial_token="resume_token")
        result = await paginator.to_list()

        assert result == ["resumed"]
        assert tokens_received == ["resume_token"]

    @pytest.mark.asyncio()
    async def test_pagination_token_property(self) -> None:
        """pagination_token should reflect current state."""
        pages_data = [
            Page(items=["a"], pagination_token="token1"),
            Page(items=["b"]),
        ]
        page_index = 0

        async def fetch_page(token: str | None) -> Page[str]:
            nonlocal page_index
            page = pages_data[page_index]
            page_index += 1
            return page

        paginator: AsyncPaginator[str] = AsyncPaginator(fetch_page)

        assert paginator.pagination_token is None

        pages_iter = paginator.pages()
        await pages_iter.__anext__()
        assert paginator.pagination_token == "token1"

        await pages_iter.__anext__()
        assert paginator.pagination_token is None
