"""Performance benchmarks for RustResponseBytes pass-through architecture.

Phase 7: Performance Testing (RED Phase)
========================================

This module tests the performance of the RustResponseBytes pass-through mechanism
to ensure negligible overhead while providing massive performance gains.

Target Metrics (from architecture document):
- isinstance() detection overhead: < 0.5ms
- RustResponseBytes path faster than Python serialization
- No memory leaks with repeated queries
- P95 latency < 100ms under load

Test Categories:
1. Detection Overhead - Cost of isinstance() checks at each layer
2. End-to-End Path Comparison - Rust vs Python serialization
3. Load Testing - 1000 req/sec simulation
4. Memory Profiling - Leak detection
"""

import gc
import json
import time
from typing import Any

import pytest

from fraiseql.core.rust_pipeline import RustResponseBytes

# ============================================================================
# Test 1: Detection Overhead Benchmarks
# ============================================================================


class TestDetectionOverhead:
    """Benchmark the cost of isinstance(result, RustResponseBytes) checks."""

    @pytest.mark.performance
    def test_isinstance_check_overhead_rust_bytes(self, benchmark) -> None:
        """Measure isinstance() check latency for RustResponseBytes using pytest-benchmark.

        Target: < 2ms for 10,000 checks (< 0.2μs per check)
        This benchmark measures the overhead of isinstance() checks for RustResponseBytes detection.
        """
        # Create test data
        rust_bytes = RustResponseBytes(b'{"test": "data"}', schema_type="TestType")

        def benchmark_rust_check():
            """Benchmark isinstance check for RustResponseBytes."""
            return isinstance(rust_bytes, RustResponseBytes)

        # Run benchmark - results automatically reported by pytest-benchmark
        benchmark.pedantic(benchmark_rust_check, rounds=1000, iterations=10)

    @pytest.mark.performance
    def test_isinstance_check_overhead_regular_dict(self, benchmark) -> None:
        """Measure isinstance() check latency for regular dict using pytest-benchmark.

        This serves as a control benchmark to compare isinstance() performance.
        """
        # Create test data
        regular_dict = {"test": "data"}

        def benchmark_dict_check():
            """Benchmark isinstance check for regular dict."""
            return isinstance(regular_dict, RustResponseBytes)

        # Run benchmark - results automatically reported by pytest-benchmark
        benchmark.pedantic(benchmark_dict_check, rounds=1000, iterations=10)

    @pytest.mark.performance
    def test_multi_layer_detection_overhead(self) -> None:
        """Measure cumulative overhead across all detection layers.

        Simulates: execute_graphql() → UnifiedExecutor → FastAPI Router
        Target: < 1ms for 1,000 complete path checks
        """
        rust_bytes = RustResponseBytes(b'{"data": "test"}', schema_type="Query")

        def simulate_detection_path(result: Any) -> bool:
            """Simulate the 3-layer detection path."""
            # Layer 1: execute_graphql() detection
            if isinstance(result, RustResponseBytes):
                # Layer 2: UnifiedExecutor detection
                if isinstance(result, RustResponseBytes):
                    # Layer 3: FastAPI Router detection
                    if isinstance(result, RustResponseBytes):
                        return True
            return False

        # Warm up
        for _ in range(100):
            simulate_detection_path(rust_bytes)

        # Measure full detection path
        start = time.perf_counter()
        for _ in range(1000):
            detected = simulate_detection_path(rust_bytes)
            assert detected is True
        end = time.perf_counter()
        elapsed_ms = (end - start) * 1000

        print("\n🔍 Multi-Layer Detection Performance:")
        print(f"   Total time: {elapsed_ms:.3f}ms for 1,000 path checks")
        print(f"   Average per request: {elapsed_ms / 1000:.6f}ms")
        print(f"   Overhead per layer: {elapsed_ms / 3000:.6f}ms")

        # RED Phase - Strict threshold
        assert elapsed_ms < 1.0, (
            f"Multi-layer detection too slow: {elapsed_ms:.3f}ms (target: < 1ms for 1,000 checks)"
        )


# ============================================================================
# Test 2: End-to-End Path Comparison
# ============================================================================


class TestPathComparison:
    """Compare RustResponseBytes path vs Python serialization path."""

    @pytest.mark.performance
    def test_serialization_comparison_small_payload(self) -> None:
        """Compare Rust bytes vs Python JSON serialization for small payloads.

        Payload: ~1KB JSON (typical single entity)
        Target: RustResponseBytes should be >= 2x faster
        """
        # Create test data
        test_data = {
            "id": "550e8400-e29b-41d4-a716-446655440000",
            "name": "Test Product",
            "description": "A test product with some description text",
            "price": 29.99,
            "category": "Electronics",
            "tags": ["test", "benchmark", "performance"],
            "metadata": {
                "brand": "TestBrand",
                "sku": "TEST-001",
                "stock": 100,
                "attributes": {"color": "blue", "size": "medium"},
            },
        }

        json_bytes = json.dumps(test_data).encode("utf-8")
        rust_bytes = RustResponseBytes(json_bytes, schema_type="Product")

        # Benchmark 1: RustResponseBytes path (just bytes() conversion)
        start = time.perf_counter()
        for _ in range(1000):
            _ = bytes(rust_bytes)
        end = time.perf_counter()
        rust_path_ms = (end - start) * 1000

        # Benchmark 2: Python serialization path (dict → JSON)
        start = time.perf_counter()
        for _ in range(1000):
            _ = json.dumps(test_data).encode("utf-8")
        end = time.perf_counter()
        python_path_ms = (end - start) * 1000

        speedup = python_path_ms / rust_path_ms

        print("\n⚡ Serialization Performance (1KB payload):")
        print(f"   RustResponseBytes path: {rust_path_ms:.3f}ms for 1,000 conversions")
        print(f"   Python serialization path: {python_path_ms:.3f}ms for 1,000 conversions")
        print(f"   Speedup: {speedup:.2f}x faster")
        print(f"   Payload size: {len(json_bytes)} bytes")

        # RED Phase - RustResponseBytes must be faster
        assert rust_path_ms < python_path_ms, (
            f"RustResponseBytes not faster: {rust_path_ms:.3f}ms vs {python_path_ms:.3f}ms"
        )
        assert speedup >= 2.0, f"RustResponseBytes not fast enough: {speedup:.2f}x (target: >= 2x)"

    @pytest.mark.performance
    def test_serialization_comparison_large_payload(self) -> None:
        """Compare Rust bytes vs Python JSON serialization for large payloads.

        Payload: ~100KB JSON (typical list of 100 entities)
        Target: RustResponseBytes should be >= 5x faster for large payloads
        """
        # Create large test data (100 entities)
        test_data = [
            {
                "id": f"550e8400-e29b-41d4-a716-{i:012d}",
                "name": f"Product {i}",
                "description": "A longer description " * 10,  # ~200 chars
                "price": 29.99 + i,
                "category": "Electronics",
                "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"],
                "metadata": {
                    "brand": "Brand" + str(i % 10),
                    "sku": f"SKU-{i:05d}",
                    "stock": 100 + i,
                    "attributes": {
                        "color": ["red", "blue", "green"][i % 3],
                        "size": ["small", "medium", "large"][i % 3],
                        "weight": 1.5 + (i * 0.1),
                    },
                },
            }
            for i in range(100)
        ]

        json_bytes = json.dumps(test_data).encode("utf-8")
        rust_bytes = RustResponseBytes(json_bytes, schema_type="ProductList")

        # Benchmark 1: RustResponseBytes path
        start = time.perf_counter()
        for _ in range(100):
            _ = bytes(rust_bytes)
        end = time.perf_counter()
        rust_path_ms = (end - start) * 1000

        # Benchmark 2: Python serialization path
        start = time.perf_counter()
        for _ in range(100):
            _ = json.dumps(test_data).encode("utf-8")
        end = time.perf_counter()
        python_path_ms = (end - start) * 1000

        speedup = python_path_ms / rust_path_ms

        print("\n⚡ Serialization Performance (100KB payload):")
        print(f"   RustResponseBytes path: {rust_path_ms:.3f}ms for 100 conversions")
        print(f"   Python serialization path: {python_path_ms:.3f}ms for 100 conversions")
        print(f"   Speedup: {speedup:.2f}x faster")
        print(f"   Payload size: {len(json_bytes):,} bytes ({len(json_bytes) / 1024:.1f} KB)")

        # RED Phase - Expect even bigger speedup for large payloads
        assert rust_path_ms < python_path_ms, (
            f"RustResponseBytes not faster: {rust_path_ms:.3f}ms vs {python_path_ms:.3f}ms"
        )
        assert speedup >= 5.0, (
            f"RustResponseBytes not fast enough for large payload: {speedup:.2f}x (target: >= 5x)"
        )


# ============================================================================
# Test 3: Memory Profiling
# ============================================================================


class TestMemoryProfile:
    """Test memory usage and leak detection for RustResponseBytes."""

    @pytest.mark.performance
    def test_memory_no_leaks_repeated_creation(self) -> None:
        """Verify no memory leaks with repeated RustResponseBytes creation.

        Target: Memory should stabilize after warm-up, no continuous growth
        """
        import sys

        test_json = json.dumps({"test": "data" * 100}).encode("utf-8")

        # Warm up and measure baseline
        gc.collect()
        for _ in range(100):
            _ = RustResponseBytes(test_json, schema_type="Test")
        gc.collect()

        baseline_size = sys.getsizeof(RustResponseBytes(test_json, schema_type="Test"))

        # Create many instances and track memory
        instances = []
        for _ in range(1000):
            instances.append(RustResponseBytes(test_json, schema_type="Test"))

        # Check total memory
        total_size = sum(sys.getsizeof(inst) for inst in instances)
        avg_size = total_size / len(instances)

        print("\n💾 Memory Usage:")
        print(f"   Baseline instance size: {baseline_size} bytes")
        print(f"   Average instance size (1000 instances): {avg_size:.1f} bytes")
        print(
            f"   Total memory (1000 instances): {total_size:,} bytes ({total_size / 1024:.1f} KB)"
        )
        print(f"   Per-instance overhead: {avg_size - baseline_size:.1f} bytes")

        # Clean up
        del instances
        gc.collect()

        # RED Phase - Verify memory is reasonable
        assert avg_size < 1000, (
            f"RustResponseBytes too large: {avg_size:.1f} bytes per instance "
            f"(should be < 1KB for small payload)"
        )
        # Verify consistent size (no memory leaks)
        assert abs(avg_size - baseline_size) < 100, (
            f"Memory inconsistency detected: baseline={baseline_size}, avg={avg_size:.1f}"
        )

    @pytest.mark.performance
    def test_memory_large_payload_efficiency(self) -> None:
        """Verify memory efficiency for large JSONB payloads.

        Target: RustResponseBytes should not duplicate data unnecessarily
        """
        import sys

        # Create large payload (~1MB)
        large_data = [{"id": i, "data": "x" * 1000} for i in range(1000)]
        large_json = json.dumps(large_data).encode("utf-8")
        payload_size = len(large_json)

        rust_bytes = RustResponseBytes(large_json, schema_type="LargeList")
        instance_size = sys.getsizeof(rust_bytes)

        # The instance should be minimal - just wrapping the bytes
        overhead = instance_size - payload_size
        overhead_percent = (overhead / payload_size) * 100

        print("\n💾 Large Payload Memory Efficiency:")
        print(f"   Payload size: {payload_size:,} bytes ({payload_size / 1024:.1f} KB)")
        print(f"   Instance size: {instance_size:,} bytes ({instance_size / 1024:.1f} KB)")
        print(f"   Overhead: {overhead:,} bytes ({overhead_percent:.2f}%)")

        # RED Phase - Overhead should be minimal
        assert overhead_percent < 10, (
            f"Too much memory overhead: {overhead_percent:.2f}% "
            f"({overhead:,} bytes on {payload_size:,} bytes payload)"
        )


# ============================================================================
# Test 4: Latency Under Load
# ============================================================================


class TestLatencyUnderLoad:
    """Simulate production load and measure P50/P95/P99 latencies."""

    @pytest.mark.performance
    def test_detection_latency_percentiles(self) -> None:
        """Measure latency percentiles for RustResponseBytes detection.

        Simulates 1,000 requests and measures P50, P95, P99
        Target: P95 < 0.01ms, P99 < 0.05ms
        """
        import statistics

        rust_bytes = RustResponseBytes(
            json.dumps({"test": "data"}).encode("utf-8"), schema_type="Test"
        )

        # Simulate 1,000 requests
        latencies = []
        for _ in range(1000):
            start = time.perf_counter()

            # Simulate full detection path
            if isinstance(rust_bytes, RustResponseBytes):
                if isinstance(rust_bytes, RustResponseBytes):
                    if isinstance(rust_bytes, RustResponseBytes):
                        _ = bytes(rust_bytes)

            end = time.perf_counter()
            latencies.append((end - start) * 1000)  # Convert to ms

        # Calculate percentiles
        latencies.sort()
        p50 = statistics.median(latencies)
        p95 = latencies[int(len(latencies) * 0.95)]
        p99 = latencies[int(len(latencies) * 0.99)]
        avg = statistics.mean(latencies)

        print("\n📊 Latency Percentiles (1,000 requests):")
        print(f"   Average: {avg:.6f}ms")
        print(f"   P50 (median): {p50:.6f}ms")
        print(f"   P95: {p95:.6f}ms")
        print(f"   P99: {p99:.6f}ms")
        print(f"   Min: {min(latencies):.6f}ms")
        print(f"   Max: {max(latencies):.6f}ms")

        # RED Phase - Strict latency targets
        assert p95 < 0.01, f"P95 latency too high: {p95:.6f}ms (target: < 0.01ms)"
        assert p99 < 0.05, f"P99 latency too high: {p99:.6f}ms (target: < 0.05ms)"

    @pytest.mark.performance
    @pytest.mark.slow
    def test_sustained_throughput(self) -> None:
        """Measure sustained throughput for RustResponseBytes operations.

        Target: 10,000 operations/second (0.1ms per operation)
        """
        test_data = json.dumps({"test": "data" * 50}).encode("utf-8")
        rust_bytes = RustResponseBytes(test_data, schema_type="Test")

        operations = 10000

        # Measure throughput
        start = time.perf_counter()
        for _ in range(operations):
            if isinstance(rust_bytes, RustResponseBytes):
                _ = bytes(rust_bytes)
        end = time.perf_counter()

        elapsed_ms = (end - start) * 1000
        ops_per_sec = operations / (elapsed_ms / 1000)
        avg_time_ms = elapsed_ms / operations

        print("\n⚡ Sustained Throughput:")
        print(f"   Operations: {operations:,}")
        print(f"   Total time: {elapsed_ms:.3f}ms")
        print(f"   Throughput: {ops_per_sec:,.0f} ops/sec")
        print(f"   Average time per operation: {avg_time_ms:.6f}ms ({avg_time_ms * 1000:.3f}μs)")

        # RED Phase - Must handle 10,000 ops/sec
        assert ops_per_sec >= 10000, (
            f"Throughput too low: {ops_per_sec:,.0f} ops/sec (target: >= 10,000 ops/sec)"
        )


# ============================================================================
# Test 5: Comparison Benchmarks (Optional - for documentation)
# ============================================================================


class TestComparisonBenchmarks:
    """Document the performance improvement of the pass-through architecture."""

    @pytest.mark.performance
    def test_document_performance_gains(self) -> None:
        """Document overall performance gains for the architecture document.

        This test measures and reports performance improvements without assertions.
        Results will be used to update Phase 7 in the architecture document.
        """
        # Small payload test
        small_data = {"id": "test", "name": "Product", "price": 29.99}
        small_json = json.dumps(small_data).encode("utf-8")
        small_rust = RustResponseBytes(small_json, schema_type="Product")

        # Measure
        start = time.perf_counter()
        for _ in range(10000):
            _ = bytes(small_rust)
        rust_small = (time.perf_counter() - start) * 1000

        start = time.perf_counter()
        for _ in range(10000):
            _ = json.dumps(small_data).encode("utf-8")
        python_small = (time.perf_counter() - start) * 1000

        # Large payload test
        large_data = [{"id": i, "name": f"Item {i}"} for i in range(100)]
        large_json = json.dumps(large_data).encode("utf-8")
        large_rust = RustResponseBytes(large_json, schema_type="ItemList")

        start = time.perf_counter()
        for _ in range(1000):
            _ = bytes(large_rust)
        rust_large = (time.perf_counter() - start) * 1000

        start = time.perf_counter()
        for _ in range(1000):
            _ = json.dumps(large_data).encode("utf-8")
        python_large = (time.perf_counter() - start) * 1000

        print("\n" + "=" * 70)
        print("📊 RUSTRESPONSEBYTES PERFORMANCE SUMMARY")
        print("=" * 70)
        print("\nSmall Payload (~100 bytes):")
        print(f"  Rust path:   {rust_small:.3f}ms for 10,000 ops")
        print(f"  Python path: {python_small:.3f}ms for 10,000 ops")
        print(f"  Speedup:     {python_small / rust_small:.2f}x")
        print("\nLarge Payload (~10KB):")
        print(f"  Rust path:   {rust_large:.3f}ms for 1,000 ops")
        print(f"  Python path: {python_large:.3f}ms for 1,000 ops")
        print(f"  Speedup:     {python_large / rust_large:.2f}x")
        print("\n" + "=" * 70)
