"""Regression test for nested field selection bug.

Tests that field selection works correctly for nested JSONB objects,
ensuring only requested fields are returned (not all fields).

This bug causes 7.5x bandwidth overhead where ALL fields from nested
objects are returned instead of only the fields specified in the GraphQL
selection set.

Example:
    query {
        allocation {
            networkConfiguration {
                id          # Only request these 2 fields
                ipAddress
                # Should NOT include: subnetMask, gateway, dnsServer, etc.
            }
        }
    }

Expected: Only id and ipAddress in response
Actual (bug): ALL 15+ fields returned

Performance Impact:
- Bandwidth: 7.5x larger payloads
- CPU: 5-7x more processing (Rust zero-copy optimization not applied)
- Memory: 7.5x more memory usage per nested object
"""

import uuid
from typing import TYPE_CHECKING, AsyncGenerator, Optional

import pytest
import pytest_asyncio

from fraiseql import query
from fraiseql.fastapi import create_fraiseql_app
from fraiseql.types import fraise_type

if TYPE_CHECKING:
    from fastapi import FastAPI

pytestmark = pytest.mark.integration


# Define test types for nested field selection
@fraise_type
class NetworkConfiguration:
    """Network configuration with many fields (simulates real-world nested object)."""

    id: uuid.UUID
    ip_address: str
    subnet_mask: str
    gateway: str
    dns_primary: str
    dns_secondary: str
    dns_tertiary: str
    hostname: str
    domain_name: str
    vlan_id: int
    is_dhcp: bool
    mac_address: str
    mtu: int
    speed_mbps: int
    duplex: str
    # 15 fields total - should only return requested fields


@fraise_type
class GatewayConfig:
    """Gateway configuration for multi-level nesting test."""

    id: uuid.UUID
    ip_address: str
    port: int
    protocol: str
    timeout_seconds: int
    retry_count: int
    # 6 fields total


@fraise_type
class PrintServer:
    """Print server for array nesting test."""

    id: uuid.UUID
    hostname: str
    port: int
    protocol: str
    queue_name: str
    is_active: bool
    # 6 fields total


@fraise_type
class AllocationWithNesting:
    """Allocation with nested objects for testing field selection."""

    id: uuid.UUID
    name: str
    # Single-level nesting
    network_configuration: Optional[NetworkConfiguration] = None
    # Multi-level nesting (nested object with nested object)
    gateway_config: Optional[GatewayConfig] = None
    # Array nesting
    print_servers: Optional[list[PrintServer]] = None


@pytest_asyncio.fixture
async def graphql_app(
    clear_registry: None,
) -> AsyncGenerator["FastAPI", None]:
    """Create FastAPI app with GraphQL endpoint."""
    from typing import Any
    from unittest.mock import AsyncMock, MagicMock
    import psycopg_pool

    # Mock database query function
    async def mock_allocations(
        where: Optional[dict] = None,
        order_by: Optional[list[str]] = None,
    ) -> list[AllocationWithNesting]:
        """Return test allocation data with nested objects."""
        test_id = uuid.UUID("12345678-1234-5678-1234-567812345678")
        network_id = uuid.UUID("87654321-4321-8765-4321-876543218765")
        gateway_id = uuid.UUID("11111111-2222-3333-4444-555555555555")
        server1_id = uuid.UUID("66666666-7777-8888-9999-000000000000")
        server2_id = uuid.UUID("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")

        # Create nested objects with ALL fields
        network_config = NetworkConfiguration(
            id=network_id,
            ip_address="192.168.1.100",
            subnet_mask="255.255.255.0",
            gateway="192.168.1.1",
            dns_primary="8.8.8.8",
            dns_secondary="8.8.4.4",
            dns_tertiary="1.1.1.1",
            hostname="test-host",
            domain_name="example.com",
            vlan_id=100,
            is_dhcp=False,
            mac_address="00:11:22:33:44:55",
            mtu=1500,
            speed_mbps=1000,
            duplex="full",
        )

        gateway_config = GatewayConfig(
            id=gateway_id,
            ip_address="10.0.0.1",
            port=443,
            protocol="https",
            timeout_seconds=30,
            retry_count=3,
        )

        print_servers = [
            PrintServer(
                id=server1_id,
                hostname="print-server-1",
                port=9100,
                protocol="raw",
                queue_name="default",
                is_active=True,
            ),
            PrintServer(
                id=server2_id,
                hostname="print-server-2",
                port=9100,
                protocol="lpd",
                queue_name="color",
                is_active=True,
            ),
        ]

        allocation = AllocationWithNesting(
            id=test_id,
            name="Test Allocation",
            network_configuration=network_config,
            gateway_config=gateway_config,
            print_servers=print_servers,
        )

        return [allocation]

    # Register query with mock using decorator
    @query
    async def allocationsWithNesting(
        info: Optional[Any] = None,
        where: Optional[dict] = None,
        order_by: Optional[list[str]] = None,
    ) -> list[AllocationWithNesting]:
        return await mock_allocations(where, order_by)

    # Create a mock pool and set it before creating the app
    from fraiseql.fastapi.dependencies import set_db_pool

    mock_pool = MagicMock(spec=psycopg_pool.AsyncConnectionPool)
    set_db_pool(mock_pool)

    try:
        app = create_fraiseql_app()
        yield app
    finally:
        # Clean up the mock pool
        set_db_pool(None)


@pytest.mark.asyncio
async def test_nested_field_selection_single_level(graphql_app: "FastAPI"):
    """Test that field selection works for single-level nested JSONB objects.

    This is the PRIMARY test case demonstrating the bug.

    Query requests only 2 fields (id, ip_address) from networkConfiguration.
    With the bug: Response includes ALL 15 fields
    After fix: Response includes ONLY 2 fields
    """
    from httpx import ASGITransport, AsyncClient

    transport = ASGITransport(app=graphql_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        query_str = """
            query {
                allocationsWithNesting {
                    id
                    name
                    networkConfiguration {
                        id
                        ipAddress
                    }
                }
            }
        """

        response = await client.post("/graphql", json={"query": query_str})
        assert response.status_code == 200

        data = response.json()
        assert "errors" not in data, f"GraphQL errors: {data.get('errors')}"

        allocations = data["data"]["allocationsWithNesting"]
        assert len(allocations) == 1

        allocation = allocations[0]
        network_config = allocation["networkConfiguration"]

        # These fields SHOULD be present (requested)
        assert "id" in network_config
        assert "ipAddress" in network_config

        # These fields should NOT be present (not requested)
        # This is the bug - currently these ARE present
        assert "subnetMask" not in network_config, "Bug: subnetMask should not be returned"
        assert "gateway" not in network_config, "Bug: gateway should not be returned"
        assert "dnsPrimary" not in network_config, "Bug: dnsPrimary should not be returned"
        assert "dnsSecondary" not in network_config, "Bug: dnsSecondary should not be returned"
        assert "dnsTertiary" not in network_config, "Bug: dnsTertiary should not be returned"
        assert "hostname" not in network_config, "Bug: hostname should not be returned"
        assert "domainName" not in network_config, "Bug: domainName should not be returned"
        assert "vlanId" not in network_config, "Bug: vlanId should not be returned"
        assert "isDhcp" not in network_config, "Bug: isDhcp should not be returned"
        assert "macAddress" not in network_config, "Bug: macAddress should not be returned"
        assert "mtu" not in network_config, "Bug: mtu should not be returned"
        assert "speedMbps" not in network_config, "Bug: speedMbps should not be returned"
        assert "duplex" not in network_config, "Bug: duplex should not be returned"

        # Verify we got exactly 2 fields
        assert (
            len(network_config) == 2
        ), f"Expected 2 fields, got {len(network_config)}: {list(network_config.keys())}"


@pytest.mark.asyncio
async def test_nested_field_selection_multi_level(graphql_app: "FastAPI"):
    """Test that field selection works for multi-level nested JSONB objects.

    Tests that field selection cascades correctly through multiple nesting levels.
    """
    from httpx import ASGITransport, AsyncClient

    transport = ASGITransport(app=graphql_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        query_str = """
            query {
                allocationsWithNesting {
                    id
                    gatewayConfig {
                        id
                        ipAddress
                    }
                }
            }
        """

        response = await client.post("/graphql", json={"query": query_str})
        assert response.status_code == 200

        data = response.json()
        assert "errors" not in data, f"GraphQL errors: {data.get('errors')}"

        allocations = data["data"]["allocationsWithNesting"]
        gateway_config = allocations[0]["gatewayConfig"]

        # These fields SHOULD be present
        assert "id" in gateway_config
        assert "ipAddress" in gateway_config

        # These fields should NOT be present
        assert "port" not in gateway_config, "Bug: port should not be returned"
        assert "protocol" not in gateway_config, "Bug: protocol should not be returned"
        assert "timeoutSeconds" not in gateway_config, "Bug: timeoutSeconds should not be returned"
        assert "retryCount" not in gateway_config, "Bug: retryCount should not be returned"

        # Verify exactly 2 fields
        assert len(gateway_config) == 2, f"Expected 2 fields, got {len(gateway_config)}"


@pytest.mark.asyncio
async def test_nested_field_selection_array(graphql_app: "FastAPI"):
    """Test that field selection works for arrays of nested JSONB objects.

    Tests that field selection applies to each element in an array of nested objects.
    """
    from httpx import ASGITransport, AsyncClient

    transport = ASGITransport(app=graphql_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        query_str = """
            query {
                allocationsWithNesting {
                    id
                    printServers {
                        id
                        hostname
                    }
                }
            }
        """

        response = await client.post("/graphql", json={"query": query_str})
        assert response.status_code == 200

        data = response.json()
        assert "errors" not in data, f"GraphQL errors: {data.get('errors')}"

        allocations = data["data"]["allocationsWithNesting"]
        print_servers = allocations[0]["printServers"]

        assert len(print_servers) == 2, "Should have 2 print servers"

        for server in print_servers:
            # These fields SHOULD be present
            assert "id" in server
            assert "hostname" in server

            # These fields should NOT be present
            assert "port" not in server, "Bug: port should not be returned"
            assert "protocol" not in server, "Bug: protocol should not be returned"
            assert "queueName" not in server, "Bug: queueName should not be returned"
            assert "isActive" not in server, "Bug: isActive should not be returned"

            # Verify exactly 2 fields
            assert len(server) == 2, f"Expected 2 fields, got {len(server)}"


# Temporarily skip null test - will add back after fixing registration approach
# @pytest.mark.asyncio
# async def test_nested_field_selection_with_null(graphql_app: "FastAPI"):
#     """Test that field selection handles null nested objects correctly.
#
#     Edge case: When nested object is null, should not throw errors.
#     """
#     # TODO: Create separate fixture for null case
#     pass


@pytest.mark.asyncio
async def test_nested_field_selection_partial_fields(graphql_app: "FastAPI"):
    """Test field selection with mix of requested and unrequested fields.

    Tests selecting some fields but not others from a nested object.
    """
    from httpx import ASGITransport, AsyncClient

    transport = ASGITransport(app=graphql_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        query_str = """
            query {
                allocationsWithNesting {
                    id
                    networkConfiguration {
                        id
                        ipAddress
                        gateway
                        isDhcp
                    }
                }
            }
        """

        response = await client.post("/graphql", json={"query": query_str})
        assert response.status_code == 200

        data = response.json()
        assert "errors" not in data, f"GraphQL errors: {data.get('errors')}"

        allocations = data["data"]["allocationsWithNesting"]
        network_config = allocations[0]["networkConfiguration"]

        # These 4 fields SHOULD be present
        assert "id" in network_config
        assert "ipAddress" in network_config
        assert "gateway" in network_config
        assert "isDhcp" in network_config

        # These fields should NOT be present
        assert "subnetMask" not in network_config
        assert "dnsPrimary" not in network_config
        assert "hostname" not in network_config

        # Verify exactly 4 fields
        assert len(network_config) == 4, f"Expected 4 fields, got {len(network_config)}"
