"""Tests for cursor-based pagination utilities."""

import pytest

from app.utils.pagination import decode_cursor, encode_cursor


class TestEncodeCursor:
    """Test encode_cursor() function."""

    def test_encodes_valid_row_id(self) -> None:
        """encode_cursor encodes a valid positive row ID."""
        cursor = encode_cursor(42)
        assert isinstance(cursor, str)
        assert len(cursor) > 0

    def test_encoded_cursor_is_decodable(self) -> None:
        """Encoded cursor can be decoded back to original ID."""
        original_id = 12345
        cursor = encode_cursor(original_id)
        decoded_id = decode_cursor(cursor)
        assert decoded_id == original_id

    def test_raises_for_zero_id(self) -> None:
        """encode_cursor raises ValueError for row_id < 1."""
        with pytest.raises(ValueError, match="row_id must be >= 1"):
            encode_cursor(0)

    def test_raises_for_negative_id(self) -> None:
        """encode_cursor raises ValueError for negative row_id."""
        with pytest.raises(ValueError, match="row_id must be >= 1"):
            encode_cursor(-5)

    def test_different_ids_produce_different_cursors(self) -> None:
        """Different row IDs produce different cursor strings."""
        cursor1 = encode_cursor(1)
        cursor2 = encode_cursor(2)
        assert cursor1 != cursor2

    def test_encoding_is_deterministic(self) -> None:
        """encode_cursor produces the same output for the same input."""
        cursor1 = encode_cursor(999)
        cursor2 = encode_cursor(999)
        assert cursor1 == cursor2


class TestDecodeCursor:
    """Test decode_cursor() function."""

    def test_decodes_valid_cursor(self) -> None:
        """decode_cursor correctly decodes a valid cursor."""
        original_id = 555
        cursor = encode_cursor(original_id)
        decoded_id = decode_cursor(cursor)
        assert decoded_id == 555

    def test_raises_for_invalid_base64(self) -> None:
        """decode_cursor raises ValueError for invalid base64."""
        with pytest.raises(ValueError, match="Invalid cursor format"):
            decode_cursor("not-valid-base64!!!")

    def test_raises_for_invalid_json(self) -> None:
        """decode_cursor raises ValueError when JSON is invalid."""
        import base64
        invalid_json = base64.b64encode(b"not json").decode("ascii")
        with pytest.raises(ValueError, match="Invalid cursor format"):
            decode_cursor(invalid_json)

    def test_raises_for_missing_id_field(self) -> None:
        """decode_cursor raises ValueError when 'id' field is missing."""
        import base64
        import json
        cursor_data = {"other_field": 42}
        invalid_cursor = base64.b64encode(json.dumps(cursor_data).encode()).decode("ascii")
        with pytest.raises(ValueError, match="Invalid cursor format"):
            decode_cursor(invalid_cursor)

    def test_raises_for_non_integer_id(self) -> None:
        """decode_cursor raises ValueError when 'id' is not an integer."""
        import base64
        import json
        cursor_data = {"id": "not-an-int"}
        invalid_cursor = base64.b64encode(json.dumps(cursor_data).encode()).decode("ascii")
        with pytest.raises(ValueError, match="Invalid cursor format"):
            decode_cursor(invalid_cursor)

    def test_raises_for_invalid_id_value(self) -> None:
        """decode_cursor raises ValueError when 'id' is < 1."""
        import base64
        import json
        cursor_data = {"id": 0}
        invalid_cursor = base64.b64encode(json.dumps(cursor_data).encode()).decode("ascii")
        with pytest.raises(ValueError, match="Invalid cursor format"):
            decode_cursor(invalid_cursor)

    def test_roundtrip_large_id(self) -> None:
        """Roundtrip encoding/decoding works for large row IDs."""
        large_id = 999999999
        cursor = encode_cursor(large_id)
        decoded_id = decode_cursor(cursor)
        assert decoded_id == large_id


class TestCursorPaginationIntegration:
    """Integration tests for cursor pagination workflow."""

    def test_pagination_workflow_first_page(self) -> None:
        """Simulate pagination workflow: start with no cursor."""
        page_size = 10
        # First page: no cursor
        cursor = None
        # ... fetch items and get last_id = 100
        cursor = encode_cursor(100)
        assert isinstance(cursor, str)

    def test_pagination_workflow_subsequent_pages(self) -> None:
        """Simulate pagination workflow: decode cursor for next page."""
        # Previous page ended at ID 100
        cursor = encode_cursor(100)
        # Decode to get WHERE clause: WHERE id < 100
        last_id = decode_cursor(cursor)
        assert last_id == 100
        # Fetch next page with WHERE id < 100
        # ... mock fetch returns items ending at ID 50
        next_cursor = encode_cursor(50)
        assert decode_cursor(next_cursor) == 50
