import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from sqlalchemy_relay_pagination import paginate
from sqlalchemy_relay_pagination.types import RequestedFields

# Import User and seeded_users from the parent conftest
from tests.conftest import User


@pytest.mark.asyncio
async def test_forward_pagination(async_session: AsyncSession, seeded_users):
    query = select(User)
    conn = await paginate(async_session, query, order_by=User.id, first=5)
    assert len(conn.edges) == 5
    assert conn.edges[0].node.id == 1
    assert conn.pageInfo.hasNextPage is True
    assert conn.pageInfo.hasPreviousPage is False


@pytest.mark.asyncio
async def test_forward_pagination_with_cursor(async_session: AsyncSession, seeded_users):
    query = select(User)
    page1 = await paginate(async_session, query, order_by=User.id, first=5)
    cursor = page1.pageInfo.endCursor
    page2 = await paginate(async_session, query, order_by=User.id, first=5, after=cursor)
    assert len(page2.edges) == 5
    assert page2.edges[0].node.id == 6


@pytest.mark.asyncio
async def test_forward_last_page(async_session: AsyncSession, seeded_users):
    query = select(User)
    page1 = await paginate(async_session, query, order_by=User.id, first=15)
    cursor = page1.pageInfo.endCursor
    page2 = await paginate(async_session, query, order_by=User.id, first=10, after=cursor)
    assert page2.pageInfo.hasNextPage is False


@pytest.mark.asyncio
async def test_backward_pagination(async_session: AsyncSession, seeded_users):
    query = select(User)
    forward = await paginate(async_session, query, order_by=User.id, first=15)
    cursor = forward.pageInfo.endCursor
    back = await paginate(async_session, query, order_by=User.id, last=5, before=cursor)
    assert len(back.edges) == 5
    assert back.pageInfo.hasPreviousPage is True
    assert back.pageInfo.hasNextPage is False
    ids = [e.node.id for e in back.edges]
    assert ids == sorted(ids)


@pytest.mark.asyncio
async def test_total_count(async_session: AsyncSession, seeded_users):
    query = select(User)
    fields = RequestedFields(
        total_count=True,
        edges=False,
        has_next_page=False,
        has_previous_page=False,
        start_cursor=False,
        end_cursor=False,
    )
    conn = await paginate(async_session, query, order_by=User.id, requested_fields=fields)
    assert conn.pageInfo.totalCount == 20


@pytest.mark.asyncio
async def test_skipped_page_query(async_session: AsyncSession, seeded_users):
    query = select(User)
    fields = RequestedFields(
        edges=False,
        has_next_page=False,
        has_previous_page=False,
        start_cursor=False,
        end_cursor=False,
        total_count=True,
    )
    conn = await paginate(async_session, query, order_by=User.id, requested_fields=fields)
    assert conn.edges == []
    assert conn.pageInfo.totalCount == 20


@pytest.mark.asyncio
async def test_desc_forward_first_page(async_session: AsyncSession, seeded_users):
    # ORDER BY id DESC, first: 5 → ids 20, 19, 18, 17, 16
    query = select(User)
    conn = await paginate(async_session, query, order_by=User.id.desc(), first=5)
    ids = [e.node.id for e in conn.edges]
    assert ids == [20, 19, 18, 17, 16]
    assert conn.pageInfo.hasNextPage is True
    assert conn.pageInfo.hasPreviousPage is False


@pytest.mark.asyncio
async def test_desc_forward_with_cursor(async_session: AsyncSession, seeded_users):
    # Page through all 20 rows two pages of 5 in descending id order.
    query = select(User)
    page1 = await paginate(async_session, query, order_by=User.id.desc(), first=5)
    assert [e.node.id for e in page1.edges] == [20, 19, 18, 17, 16]

    cursor = page1.pageInfo.endCursor
    page2 = await paginate(async_session, query, order_by=User.id.desc(), first=5, after=cursor)
    assert [e.node.id for e in page2.edges] == [15, 14, 13, 12, 11]
    assert page2.pageInfo.hasNextPage is True


@pytest.mark.asyncio
async def test_desc_forward_last_page(async_session: AsyncSession, seeded_users):
    # After fetching 15 rows descending the remaining 5 should be the last page.
    query = select(User)
    page1 = await paginate(async_session, query, order_by=User.id.desc(), first=15)
    cursor = page1.pageInfo.endCursor
    page2 = await paginate(async_session, query, order_by=User.id.desc(), first=10, after=cursor)
    assert [e.node.id for e in page2.edges] == [5, 4, 3, 2, 1]
    assert page2.pageInfo.hasNextPage is False


@pytest.mark.asyncio
async def test_desc_backward_with_cursor(async_session: AsyncSession, seeded_users):
    # last: 5, before cursor at id=6 → rows immediately before id=6 descending.
    query = select(User)
    page1 = await paginate(async_session, query, order_by=User.id.desc(), first=15)
    cursor = page1.pageInfo.endCursor  # cursor at id=6

    back = await paginate(async_session, query, order_by=User.id.desc(), last=5, before=cursor)
    ids = [e.node.id for e in back.edges]
    assert ids == sorted(ids, reverse=True)
    assert back.pageInfo.hasPreviousPage is True
    assert back.pageInfo.hasNextPage is False


@pytest.mark.asyncio
async def test_desc_full_traversal_no_duplicates(async_session: AsyncSession, seeded_users):
    # Paginate through all 20 rows in descending id order and verify completeness.
    query = select(User)
    all_ids: list[int] = []
    cursor = None
    while True:
        conn = await paginate(async_session, query, order_by=User.id.desc(), first=7, after=cursor)
        all_ids.extend(e.node.id for e in conn.edges)
        cursor = conn.pageInfo.endCursor
        if not conn.pageInfo.hasNextPage:
            break

    assert len(all_ids) == 20
    assert len(set(all_ids)) == 20
    assert all_ids == sorted(all_ids, reverse=True)
