"""Tests for PowerPath pagination logic.

Tests the Paginator behavior with mock transports, matching the TS SDK's pagination tests.
"""

import pytest

from timeback_common import InputValidationError
from timeback_powerpath import TestAssignment
from timeback_powerpath.resources.test_assignments import TestAssignmentsResource


def make_assignment(id: str) -> dict:
    """Create a minimal test assignment dict for testing."""
    return {
        "sourcedId": id,
        "studentSourcedId": "student-1",
        "studentEmail": "student@example.com",
        "assignedByUserSourcedId": None,
        "subject": "Math",
        "grade": "3",
        "assignmentStatus": "assigned",
    }


class MockPaths:
    base = "/powerpath"


class MockTransport:
    """Mock transport that returns paginated test assignment responses."""

    def __init__(self, pages: list[dict]):
        self._pages = pages
        self._call_count = 0
        self.requests: list[dict] = []
        self.paths = MockPaths()
        self.base_url = "https://example.test"

    async def get(self, path: str, *, params: dict | None = None) -> dict:
        self.requests.append({"path": path, "params": params or {}})
        if self._call_count >= len(self._pages):
            return {"testAssignments": []}
        response = self._pages[self._call_count]
        self._call_count += 1
        return response

    async def post(self, _path, _data=None, *, headers=None):  # noqa: ARG002
        raise RuntimeError("post should not be called")

    async def put(self, _path, _data=None, *, headers=None):  # noqa: ARG002
        raise RuntimeError("put should not be called")

    async def delete(self, _path, *, headers=None):  # noqa: ARG002
        raise RuntimeError("delete should not be called")

    async def close(self):
        pass

    @property
    def call_count(self) -> int:
        return self._call_count


class TestStreamPagination:
    """Tests for test_assignments.stream() pagination."""

    @pytest.mark.asyncio
    async def test_stream_respects_max_items(self):
        """stream() should stop after max_items even if more available."""
        transport = MockTransport(
            [
                {
                    "testAssignments": [
                        make_assignment("a1"),
                        make_assignment("a2"),
                        make_assignment("a3"),
                    ],
                },
            ]
        )

        resource = TestAssignmentsResource(transport)
        items = await resource.stream(
            {"student": "student-1"},
            max_items=2,
        ).to_list()

        assert len(items) == 2
        assert all(isinstance(item, TestAssignment) for item in items)
        assert items[0].sourced_id == "a1"
        assert items[1].sourced_id == "a2"

    @pytest.mark.asyncio
    async def test_stream_advances_offsets(self):
        """stream() should advance offsets across pages."""
        transport = MockTransport(
            [
                {"testAssignments": [make_assignment("a1")]},
                {"testAssignments": [make_assignment("a2")]},
                {"testAssignments": []},
            ]
        )

        resource = TestAssignmentsResource(transport)
        items = await resource.stream(
            {"student": "student-1", "limit": 1},
        ).to_list()

        assert len(items) == 2

        offsets = [r["params"].get("offset", 0) for r in transport.requests]
        assert offsets[0] == 0
        assert offsets[1] == 1

    @pytest.mark.asyncio
    async def test_stream_validates_limit(self):
        """stream() should reject limit > 3000."""
        transport = MockTransport([])
        resource = TestAssignmentsResource(transport)

        with pytest.raises(InputValidationError):
            resource.stream({"student": "student-1", "limit": 3001})

    @pytest.mark.asyncio
    async def test_list_all_returns_all_items(self):
        """list_all() should collect all items across pages."""
        transport = MockTransport(
            [
                {"testAssignments": [make_assignment("a1"), make_assignment("a2")]},
                {"testAssignments": [make_assignment("a3")]},
                {"testAssignments": []},
            ]
        )

        resource = TestAssignmentsResource(transport)
        items = await resource.list_all({"student": "student-1", "limit": 2})

        assert len(items) == 3
        assert items[0].sourced_id == "a1"
        assert items[2].sourced_id == "a3"


class TestAdminStreamPagination:
    """Tests for test_assignments.admin_stream() pagination."""

    @pytest.mark.asyncio
    async def test_admin_stream_paginates(self):
        """admin_stream() should paginate correctly."""
        transport = MockTransport(
            [
                {"testAssignments": [make_assignment("a1")]},
                {"testAssignments": []},
            ]
        )

        resource = TestAssignmentsResource(transport)
        items = await resource.admin_stream(max_items=10).to_list()

        assert len(items) == 1
        assert items[0].sourced_id == "a1"
