"""Tests for OpenAI-style Page pagination."""

import httpx

from goodmem.pagination import Page
from goodmem._transforms import _transform_list_kwargs


class FakeItem:
    def __init__(self, name: str):
        self.name = name


class FakePage:
    def __init__(self, items: list | None, next_token: str | None = None):
        self.items = items
        self.next_token = next_token

    @classmethod
    def model_validate(cls, data: dict) -> "FakePage":
        raw_items = data.get("items")
        items = [FakeItem(i) for i in raw_items] if raw_items else raw_items
        return cls(items=items, next_token=data.get("nextToken"))


def _make_fetch_fn(pages: list[dict]):
    """Create a fetch function that returns pages in sequence."""
    call_count = [0]

    def fetch(token: str | None) -> httpx.Response:
        idx = call_count[0]
        call_count[0] += 1
        return httpx.Response(200, json=pages[idx])

    return fetch, call_count


def _make_page(pages: list[dict], max_items: int | None = None) -> Page:
    """Build a Page by eagerly parsing the first page (simulating tier-2)."""
    fetch_fn, call_count = _make_fetch_fn(pages[1:])  # remaining pages
    first = FakePage.model_validate(pages[0])
    return Page(
        data=first.items if first.items is not None else [],
        next_token=first.next_token,
        fetch_fn=fetch_fn,
        response_model=FakePage,
        items_field="items",
        max_items=max_items,
    ), call_count


# ---------------------------------------------------------------------------
# .data and .next_token (assignment)
# ---------------------------------------------------------------------------


def test_page_data_populated():
    page, _ = _make_page([{"items": ["a", "b", "c"]}])
    assert [item.name for item in page.data] == ["a", "b", "c"]
    assert page.next_token is None


def test_page_next_token_present():
    page, _ = _make_page([{"items": ["a"], "nextToken": "page2"}])
    assert page.next_token == "page2"


def test_page_empty_data():
    page, _ = _make_page([{"items": []}])
    assert page.data == []
    assert page.next_token is None


def test_page_none_items():
    page, _ = _make_page([{"items": None}])
    assert page.data == []


# ---------------------------------------------------------------------------
# __iter__ (for loop — auto-paginate)
# ---------------------------------------------------------------------------


def test_iter_single_page():
    page, _ = _make_page([{"items": ["a", "b", "c"]}])
    items = [item.name for item in page]
    assert items == ["a", "b", "c"]


def test_iter_multi_page():
    page, _ = _make_page([
        {"items": ["a", "b"], "nextToken": "page2"},
        {"items": ["c", "d"], "nextToken": "page3"},
        {"items": ["e"]},
    ])
    items = [item.name for item in page]
    assert items == ["a", "b", "c", "d", "e"]


def test_iter_stops_at_no_next_token():
    call_count_outer = [0]

    def fetch(token: str | None) -> httpx.Response:
        call_count_outer[0] += 1
        if call_count_outer[0] == 1:
            return httpx.Response(200, json={"items": ["b"]})
        raise AssertionError("Should not fetch a third page")

    first = FakePage.model_validate({"items": ["a"], "nextToken": "t2"})
    page = Page(
        data=first.items,
        next_token=first.next_token,
        fetch_fn=fetch,
        response_model=FakePage,
        items_field="items",
    )
    items = [item.name for item in page]
    assert items == ["a", "b"]
    assert call_count_outer[0] == 1


def test_iter_empty_page():
    page, _ = _make_page([{"items": []}])
    items = list(page)
    assert items == []


# ---------------------------------------------------------------------------
# max_items
# ---------------------------------------------------------------------------


def test_max_items_truncates_data():
    """max_items truncates .data on construction."""
    page, _ = _make_page([{"items": ["a", "b", "c", "d", "e"]}], max_items=2)
    assert [item.name for item in page.data] == ["a", "b"]


def test_max_items_caps_iteration_single_page():
    page, _ = _make_page([{"items": ["a", "b", "c", "d", "e"]}], max_items=3)
    items = [item.name for item in page]
    assert items == ["a", "b", "c"]


def test_max_items_caps_across_pages():
    page, _ = _make_page([
        {"items": ["a", "b"], "nextToken": "page2"},
        {"items": ["c", "d"], "nextToken": "page3"},
        {"items": ["e"]},
    ], max_items=3)
    items = [item.name for item in page]
    assert items == ["a", "b", "c"]


def test_max_items_prevents_unnecessary_fetches():
    """When max_items is satisfied within first page, no subsequent fetches."""
    page, call_count = _make_page([
        {"items": ["a", "b", "c"], "nextToken": "t2"},
        {"items": ["d", "e"]},
    ], max_items=2)
    items = [item.name for item in page]
    assert items == ["a", "b"]
    assert call_count[0] == 0  # Second page never fetched


def test_max_items_none_returns_all():
    page, _ = _make_page([
        {"items": ["a", "b"], "nextToken": "page2"},
        {"items": ["c"]},
    ], max_items=None)
    items = [item.name for item in page]
    assert items == ["a", "b", "c"]


def test_max_items_larger_than_total():
    page, _ = _make_page([{"items": ["a", "b"]}], max_items=100)
    items = [item.name for item in page]
    assert items == ["a", "b"]


def test_max_items_zero():
    page, call_count = _make_page([{"items": ["a", "b"]}], max_items=0)
    assert page.data == []
    items = list(page)
    assert items == []


def test_max_items_set_after_construction():
    """max_items can be set via _max_items after construction (used by Tier 3)."""
    page, _ = _make_page([{"items": ["a", "b", "c", "d", "e"]}])
    page._max_items = 3
    page.data = page.data[:3]
    items = [item.name for item in page]
    assert items == ["a", "b", "c"]


# ---------------------------------------------------------------------------
# iter_pages
# ---------------------------------------------------------------------------


def test_iter_pages_single():
    page, _ = _make_page([{"items": ["a", "b"]}])
    pages = list(page.iter_pages())
    assert len(pages) == 1
    assert [item.name for item in pages[0].data] == ["a", "b"]


def test_iter_pages_multi():
    page, _ = _make_page([
        {"items": ["a", "b"], "nextToken": "page2"},
        {"items": ["c", "d"], "nextToken": "page3"},
        {"items": ["e"]},
    ])
    pages = list(page.iter_pages())
    assert len(pages) == 3
    assert [item.name for item in pages[0].data] == ["a", "b"]
    assert [item.name for item in pages[1].data] == ["c", "d"]
    assert [item.name for item in pages[2].data] == ["e"]


def test_iter_pages_with_max_items():
    """iter_pages respects max_items budget across pages."""
    page, _ = _make_page([
        {"items": ["a", "b"], "nextToken": "page2"},
        {"items": ["c", "d", "e"], "nextToken": "page3"},
        {"items": ["f"]},
    ], max_items=4)
    pages = list(page.iter_pages())
    assert len(pages) == 2
    assert [item.name for item in pages[0].data] == ["a", "b"]
    # Second page gets remaining budget of 2
    assert [item.name for item in pages[1].data] == ["c", "d"]


def test_iter_pages_next_token_on_each():
    page, _ = _make_page([
        {"items": ["a"], "nextToken": "t2"},
        {"items": ["b"], "nextToken": "t3"},
        {"items": ["c"]},
    ])
    pages = list(page.iter_pages())
    assert pages[0].next_token == "t2"
    assert pages[1].next_token == "t3"
    assert pages[2].next_token is None


# ---------------------------------------------------------------------------
# _transform_list_kwargs tests
# ---------------------------------------------------------------------------


def test_transform_page_size_to_max_results():
    kwargs = {"page_size": 25, "sort_by": "name"}
    max_items = _transform_list_kwargs(kwargs)
    assert kwargs == {"max_results": 25, "sort_by": "name"}
    assert max_items is None


def test_transform_extracts_max_items():
    kwargs = {"max_items": 100, "sort_by": "name"}
    max_items = _transform_list_kwargs(kwargs)
    assert kwargs == {"sort_by": "name"}
    assert max_items == 100


def test_transform_leaves_next_token_in_kwargs():
    """next_token is NOT popped — it flows through to tier-2."""
    kwargs = {"next_token": "abc123", "sort_by": "name"}
    max_items = _transform_list_kwargs(kwargs)
    assert kwargs == {"next_token": "abc123", "sort_by": "name"}
    assert max_items is None


def test_transform_all_together():
    kwargs = {"page_size": 20, "max_items": 50, "next_token": "tok", "sort_by": "name"}
    max_items = _transform_list_kwargs(kwargs)
    assert kwargs == {"max_results": 20, "next_token": "tok", "sort_by": "name"}
    assert max_items == 50


def test_transform_no_convenience_params():
    kwargs = {"sort_by": "name", "max_results": 30}
    max_items = _transform_list_kwargs(kwargs)
    assert kwargs == {"sort_by": "name", "max_results": 30}
    assert max_items is None
