"""Tests that pagination works end-to-end with real generated models.

The existing test_pagination.py tests the Paginator mechanism with FakePage
models.  These tests plug in the actual generated pydantic response models
to verify that:
  1. The models preserve nextToken through model_validate() round-trip
  2. The Paginator correctly iterates multiple pages with real models
  3. The CRUDNamespace.list() passes nextToken and kwargs as query params

Only spaces and memories support server-side pagination today.  Embedders,
LLMs, and rerankers return all results in a single response (no nextToken
field in their proto definitions or generated models).
"""

import httpx

from goodmem._generated.models.list_spaces_response import ListSpacesResponse
from goodmem._generated.models.memory_list_response import MemoryListResponse
from goodmem.pagination import Paginator

from tests.conftest import MEMORY_RESPONSE, SPACE_RESPONSE


# ---------------------------------------------------------------------------
# nextToken preserved through model_validate round-trip
# ---------------------------------------------------------------------------


class TestNextTokenPreservedOnDeserialize:
    """Verify nextToken survives: server JSON → model_validate → getattr."""

    def test_spaces_next_token_round_trip(self):
        data = {"spaces": [SPACE_RESPONSE], "nextToken": "page2"}
        page = ListSpacesResponse.model_validate(data)
        assert getattr(page, "next_token", None) == "page2"

    def test_memories_next_token_round_trip(self):
        data = {"memories": [MEMORY_RESPONSE], "nextToken": "page2"}
        page = MemoryListResponse.model_validate(data)
        assert getattr(page, "next_token", None) == "page2"


# ---------------------------------------------------------------------------
# Multi-page pagination with real generated models
# ---------------------------------------------------------------------------


class TestPaginationWithRealModels:
    """Run the Paginator with actual generated models, not FakePage."""

    def test_spaces_multi_page(self):
        space_a = {**SPACE_RESPONSE, "spaceId": "space-a", "name": "A"}
        space_b = {**SPACE_RESPONSE, "spaceId": "space-b", "name": "B"}

        pages = [
            {"spaces": [space_a], "nextToken": "page2"},
            {"spaces": [space_b]},
        ]
        call_idx = [0]

        def fetch(token):
            idx = call_idx[0]
            call_idx[0] += 1
            return httpx.Response(200, json=pages[idx])

        paginator = Paginator(
            fetch_fn=fetch,
            response_model=ListSpacesResponse,
            items_field="spaces",
        )

        items = list(paginator)
        assert len(items) == 2
        assert items[0].space_id == "space-a"
        assert items[1].space_id == "space-b"
        assert call_idx[0] == 2

    def test_memories_multi_page(self):
        mem_a = {**MEMORY_RESPONSE, "memoryId": "mem-a"}
        mem_b = {**MEMORY_RESPONSE, "memoryId": "mem-b"}

        pages = [
            {"memories": [mem_a], "nextToken": "tok2"},
            {"memories": [mem_b]},
        ]
        call_idx = [0]

        def fetch(token):
            idx = call_idx[0]
            call_idx[0] += 1
            return httpx.Response(200, json=pages[idx])

        paginator = Paginator(
            fetch_fn=fetch,
            response_model=MemoryListResponse,
            items_field="memories",
        )

        items = list(paginator)
        assert len(items) == 2
        assert items[0].memory_id == "mem-a"
        assert items[1].memory_id == "mem-b"
        assert call_idx[0] == 2


# ---------------------------------------------------------------------------
# CRUDNamespace.list() passes nextToken and kwargs as query params
# ---------------------------------------------------------------------------


class TestListPassesNextTokenParam:

    def test_first_call_has_no_next_token(self, mock_client_factory):
        """First page request should not include nextToken param."""
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["params"] = dict(request.url.params)
            return httpx.Response(200, json={"spaces": [SPACE_RESPONSE]})

        client = mock_client_factory(handler)
        list(client.spaces.list())

        assert "nextToken" not in captured["params"]

    def test_second_call_includes_next_token(self, mock_client_factory):
        """When server returns nextToken, the SDK must pass it on the next request."""
        calls = []

        def handler(request: httpx.Request) -> httpx.Response:
            calls.append(dict(request.url.params))
            if len(calls) == 1:
                return httpx.Response(200, json={
                    "spaces": [SPACE_RESPONSE],
                    "nextToken": "cursor-abc",
                })
            return httpx.Response(200, json={
                "spaces": [{**SPACE_RESPONSE, "spaceId": "space-2"}],
            })

        client = mock_client_factory(handler)
        items = list(client.spaces.list())

        assert len(calls) == 2
        assert "nextToken" not in calls[0]
        assert calls[1]["nextToken"] == "cursor-abc"
        assert len(items) == 2

    def test_list_kwargs_passed_as_query_params(self, mock_client_factory):
        """Extra kwargs to list() should become query parameters."""
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["params"] = dict(request.url.params)
            return httpx.Response(200, json={"spaces": []})

        client = mock_client_factory(handler)
        list(client.spaces.list(maxResults=10, ownerId="user-x"))

        assert captured["params"]["maxResults"] == "10"
        assert captured["params"]["ownerId"] == "user-x"

    def test_list_kwargs_preserved_on_subsequent_pages(self, mock_client_factory):
        """Extra kwargs should be included on every page request, not just the first."""
        calls = []

        def handler(request: httpx.Request) -> httpx.Response:
            calls.append(dict(request.url.params))
            if len(calls) == 1:
                return httpx.Response(200, json={
                    "spaces": [SPACE_RESPONSE],
                    "nextToken": "pg2",
                })
            return httpx.Response(200, json={"spaces": []})

        client = mock_client_factory(handler)
        list(client.spaces.list(maxResults=5))

        assert len(calls) == 2
        assert calls[0]["maxResults"] == "5"
        assert calls[1]["maxResults"] == "5"
        assert calls[1]["nextToken"] == "pg2"

    def test_memories_list_uses_space_path_with_pagination(self, mock_client_factory):
        """Memories list should use /v1/spaces/{id}/memories and pass nextToken."""
        calls = []

        def handler(request: httpx.Request) -> httpx.Response:
            calls.append({
                "url": str(request.url),
                "params": dict(request.url.params),
            })
            if len(calls) == 1:
                return httpx.Response(200, json={
                    "memories": [MEMORY_RESPONSE],
                    "nextToken": "mem-pg2",
                })
            return httpx.Response(200, json={
                "memories": [{**MEMORY_RESPONSE, "memoryId": "mem-2"}],
            })

        client = mock_client_factory(handler)
        items = list(client.memories.list(space_id="sp-1"))

        assert len(calls) == 2
        assert "/v1/spaces/sp-1/memories" in calls[0]["url"]
        assert "/v1/spaces/sp-1/memories" in calls[1]["url"]
        assert "nextToken" not in calls[0]["params"]
        assert calls[1]["params"]["nextToken"] == "mem-pg2"
        assert len(items) == 2
        assert items[0].memory_id == "mem-123"
        assert items[1].memory_id == "mem-2"
