from httpx import AsyncClient


async def gql(client: AsyncClient, query: str) -> dict:
    resp = await client.post("/graphql", json={"query": query})
    return resp.json()


# 1. first: 5, no cursor
async def test_first_5_no_cursor(client):
    result = await gql(
        client,
        """
        { users(first: 5) {
            edges { node { id name } cursor }
            pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
        }}
    """,
    )
    data = result["data"]["users"]
    assert len(data["edges"]) == 5
    assert data["pageInfo"]["hasNextPage"] is True
    assert data["pageInfo"]["hasPreviousPage"] is False
    assert data["edges"][0]["node"]["id"] == 1


# 2. first: 5, after cursor
async def test_first_5_with_cursor(client):
    r1 = await gql(client, "{ users(first: 5) { pageInfo { endCursor } } }")
    cursor = r1["data"]["users"]["pageInfo"]["endCursor"]
    result = await gql(
        client,
        f"""
        {{ users(first: 5, after: "{cursor}") {{
            edges {{ node {{ id }} }}
        }} }}
    """,
    )
    ids = [e["node"]["id"] for e in result["data"]["users"]["edges"]]
    assert ids == [6, 7, 8, 9, 10]


# 3. first: 5 on last page
async def test_last_page_no_next(client):
    r1 = await gql(client, "{ users(first: 15) { pageInfo { endCursor } } }")
    cursor = r1["data"]["users"]["pageInfo"]["endCursor"]
    result = await gql(
        client,
        f"""
        {{ users(first: 10, after: "{cursor}") {{
            pageInfo {{ hasNextPage }}
        }} }}
    """,
    )
    assert result["data"]["users"]["pageInfo"]["hasNextPage"] is False


# 4. last: 5, before cursor
async def test_last_5_before_cursor(client):
    r1 = await gql(client, "{ users(first: 15) { pageInfo { endCursor } } }")
    cursor = r1["data"]["users"]["pageInfo"]["endCursor"]
    result = await gql(
        client,
        f"""
        {{ users(last: 5, before: "{cursor}") {{
            edges {{ node {{ id }} }}
            pageInfo {{ hasPreviousPage hasNextPage }}
        }} }}
    """,
    )
    data = result["data"]["users"]
    assert len(data["edges"]) == 5
    assert data["pageInfo"]["hasPreviousPage"] is True
    assert data["pageInfo"]["hasNextPage"] is False


# 5. last: 5 with no cursor → last 5 items of 20; hasPreviousPage=True (items 1-15 come before)
async def test_last_5_first_page(client):
    result = await gql(
        client,
        """
        { users(last: 5) {
            pageInfo { hasPreviousPage }
        }}
    """,
    )
    # With 20 seeded users and last: 5, we get users 16-20; users 1-15 come before → True
    assert result["data"]["users"]["pageInfo"]["hasPreviousPage"] is True


# 6. Only totalCount → 1 query
async def test_only_total_count(client, query_counter):
    result = await gql(client, "{ users { pageInfo { totalCount } } }")
    assert result["data"]["users"]["pageInfo"]["totalCount"] == 20
    assert query_counter["n"] == 1


# 7. Only pageInfo { hasNextPage } → 1 query with LIMIT n+1
async def test_only_has_next_page(client, query_counter):
    result = await gql(
        client,
        """
        { users(first: 5) { pageInfo { hasNextPage } } }
    """,
    )
    assert result["data"]["users"]["pageInfo"]["hasNextPage"] is True
    assert query_counter["n"] == 1


# 8. Only startCursor/endCursor → page query, no extra row
async def test_only_cursors(client, query_counter):
    result = await gql(
        client,
        """
        { users(first: 5) { pageInfo { startCursor endCursor } } }
    """,
    )
    data = result["data"]["users"]["pageInfo"]
    assert data["startCursor"] is not None
    assert data["endCursor"] is not None
    assert query_counter["n"] == 1


# 9. edges + totalCount → 2 queries (concurrent)
async def test_edges_and_total_count(client, query_counter):
    result = await gql(
        client,
        """
        { users(first: 5) {
            edges { node { id } }
            pageInfo { totalCount }
        }}
    """,
    )
    assert len(result["data"]["users"]["edges"]) == 5
    assert result["data"]["users"]["pageInfo"]["totalCount"] == 20
    assert query_counter["n"] == 2


# 10. Nothing requested → 0 queries
async def test_nothing_requested(client, query_counter):
    # Request only __typename which needs no DB query
    await gql(client, "{ users { __typename } }")
    assert query_counter["n"] == 0


# 11. first + last together → error
async def test_first_and_last_error(client):
    result = await gql(client, "{ users(first: 5, last: 5) { edges { node { id } } } }")
    assert "errors" in result


# 12. Negative first → error
async def test_negative_first_error(client):
    result = await gql(client, "{ users(first: -1) { edges { node { id } } } }")
    assert "errors" in result


# 13. Invalid/tampered cursor → error
async def test_invalid_cursor_error(client):
    result = await gql(
        client,
        """
        { users(first: 5, after: "totally-invalid-cursor!!!") {
            edges { node { id } }
        }}
    """,
    )
    assert "errors" in result


# 14. Multi-column ORDER BY consistency
async def test_multi_column_order(client):
    # This uses single-column in the integration resolver; verify no duplicates/losses
    all_ids = []
    cursor = None
    for _ in range(4):
        if cursor:
            q = f'{{ users(first: 5, after: "{cursor}") {{ edges {{ node {{ id }} }} pageInfo {{ endCursor hasNextPage }} }} }}'
        else:
            q = "{ users(first: 5) { edges { node { id } } pageInfo { endCursor hasNextPage } } }"
        r = await gql(client, q)
        data = r["data"]["users"]
        all_ids.extend(e["node"]["id"] for e in data["edges"])
        cursor = data["pageInfo"]["endCursor"]
        if not data["pageInfo"]["hasNextPage"]:
            break
    assert len(set(all_ids)) == len(all_ids)
    assert sorted(all_ids) == all_ids


# 15. No first/last → uses default_page_size (100), returns all 20
async def test_default_page_size(client):
    result = await gql(client, "{ users { edges { node { id } } } }")
    assert len(result["data"]["users"]["edges"]) == 20


# 16. first > max_page_size → error
async def test_exceeds_max_page_size(client):
    from sqlalchemy_relay_pagination import PaginationConfig, configure

    configure(PaginationConfig(default_page_size=10, max_page_size=50))
    try:
        result = await gql(client, "{ users(first: 51) { edges { node { id } } } }")
        assert "errors" in result
    finally:
        configure(PaginationConfig())  # reset to defaults
