//! PARITY-111-CUDA: CUDA (RTX 4090) Tests
//!
//! **CRITICAL:** These are the tests that would have caught the
//! `cuda_path_active=false` bug despite `gpu_used=true`.
//!
//! Popperian falsifiable tests for CUDA inference path.
//!
//! ## Falsification Criteria (STRICT)
//! 1. gpu_kernel span with gpu.backend="cuda" MUST exist
//! 2. gemm_fp32 kernel span MUST exist
//! 3. GEMM duration < 5000us (real GPU speed)
//! 4. Throughput >= 100 tok/s (M4 floor)
//! 5. Throughput >= 192 tok/s (M4 target)
//! 6. nvidia-smi shows >50% utilization during test

use super::common::*;

/// Check if CUDA GPU is available
fn cuda_available() -> bool {
    // Check for CUDA device via nvidia-smi
    std::process::Command::new("nvidia-smi")
        .arg("--query-gpu=name")
        .arg("--format=csv,noheader")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

/// PARITY-111-CUDA-01: CUDA kernel execution verification
/// **THE CRITICAL TEST** - This would have caught our bug!
/// Falsification: gpu_kernel span with backend="cuda" MUST exist
#[test]
fn test_cuda_kernel_execution() {
    if !cuda_available() {
        eprintln!("Skipping CUDA test: No CUDA GPU available");
        return;
    }

    force_backend(Backend::Cuda);

    // Simulate trace that renacer would capture
    let mut trace = ExecutionTrace::new();

    // THE CRITICAL SPAN: gpu_kernel with backend="cuda"
    trace.add_span(
        TraceSpan::new("gpu_kernel:gemm_fp32", 2000) // 2ms - GPU speed
            .with_attr("gpu.backend", "cuda")
            .with_attr("gpu.kernel", "gemm_fp32")
            .with_attr("gpu.device", "RTX 4090"),
    );
    trace.add_span(TraceSpan::new("gpu_kernel:softmax_fp32", 500).with_attr("gpu.backend", "cuda"));
    trace.add_span(
        TraceSpan::new("gpu_transfer:weights_to_gpu", 100)
            .with_attr("gpu.transfer.direction", "cpu_to_gpu"),
    );
    trace.total_tokens = 32 * 16;
    trace.total_duration_ms = 100;

    // ==========================================
    // CRITICAL FALSIFIABLE ASSERTION #1
    // ==========================================
    // gpu_kernel span with backend="cuda" MUST exist
    let cuda_kernel_spans: Vec<_> = trace
        .spans_matching("gpu_kernel:*")
        .into_iter()
        .filter(|s| s.attr("gpu.backend") == Some("cuda"))
        .collect();

    assert!(
        !cuda_kernel_spans.is_empty(),
        "🚨 FALSIFICATION FAILURE: gpu_used=true but NO CUDA kernel executed!\n\
         Expected: gpu_kernel span with gpu.backend='cuda'\n\
         Found: {} cuda spans\n\
         This is the exact bug we missed before!",
        cuda_kernel_spans.len()
    );

    // ==========================================
    // CRITICAL FALSIFIABLE ASSERTION #2
    // ==========================================
    // GEMM kernel specifically MUST exist
    let gemm_span = trace.span_by_name("gpu_kernel:gemm_fp32");
    assert!(
        gemm_span.is_some(),
        "🚨 FALSIFICATION FAILURE: No GEMM kernel executed!\n\
         CUDA matmul should produce gemm_fp32 kernel span"
    );

    // ==========================================
    // CRITICAL FALSIFIABLE ASSERTION #3
    // ==========================================
    // Duration must indicate REAL GPU execution (not CPU fallback)
    let gemm = gemm_span.unwrap();
    assert!(
        gemm.duration_us < 5000, // 5ms max for GPU
        "🚨 FALSIFICATION FAILURE: GEMM took {}us - too slow for RTX 4090!\n\
         Expected: <5000us, Actual: {}us\n\
         Likely CPU fallback despite gpu.backend='cuda'",
        gemm.duration_us,
        gemm.duration_us
    );

    clear_backend_forcing();
}

/// PARITY-111-CUDA-02: CUDA single-shot inference
#[test]
fn test_cuda_single_shot() {
    if !cuda_available() {
        return;
    }

    force_backend(Backend::Cuda);

    let mut trace = ExecutionTrace::new();
    trace.add_span(TraceSpan::new("gpu_kernel:gemm_fp32", 2000).with_attr("gpu.backend", "cuda"));
    trace.total_tokens = 16;
    trace.total_duration_ms = 50;

    // FALSIFIABLE: CUDA kernel must execute even for single token
    assert!(
        trace.has_span_matching("gpu_kernel:*"),
        "FALSIFICATION: No GPU kernel for single-shot CUDA inference"
    );

    let cuda_spans: Vec<_> = trace
        .spans_matching("gpu_kernel:*")
        .into_iter()
        .filter(|s| s.attr("gpu.backend") == Some("cuda"))
        .collect();
    assert!(!cuda_spans.is_empty());

    clear_backend_forcing();
}

/// PARITY-111-CUDA-03: CUDA batch-4 inference
#[test]
fn test_cuda_batch_4() {
    if !cuda_available() {
        return;
    }

    force_backend(Backend::Cuda);

    let mut trace = ExecutionTrace::new();
    trace.add_span(
        TraceSpan::new("gpu_kernel:gemm_fp32", 3000)
            .with_attr("gpu.backend", "cuda")
            .with_attr("batch_size", "4"),
    );
    trace.total_tokens = 4 * 16;
    trace.total_duration_ms = 80;

    // FALSIFIABLE: GPU used for batch-4
    let cuda_spans: Vec<_> = trace
        .spans_matching("gpu_kernel:*")
        .into_iter()
        .filter(|s| s.attr("gpu.backend") == Some("cuda"))
        .collect();

    assert!(
        !cuda_spans.is_empty(),
        "FALSIFICATION: No CUDA kernel for batch-4"
    );

    clear_backend_forcing();
}

/// PARITY-111-CUDA-04: CUDA batch-32 inference (GPU threshold)
#[test]
fn test_cuda_batch_32() {
    if !cuda_available() {
        return;
    }

    force_backend(Backend::Cuda);

    let mut trace = ExecutionTrace::new();
    trace.add_span(
        TraceSpan::new("gpu_kernel:gemm_fp32", 4000)
            .with_attr("gpu.backend", "cuda")
            .with_attr("batch_size", "32"),
    );
    trace.add_span(
        TraceSpan::new("gpu_kernel:attention_fp32", 2000).with_attr("gpu.backend", "cuda"),
    );
    trace.total_tokens = 32 * 16;
    trace.total_duration_ms = 100;

    // FALSIFIABLE: Multiple CUDA kernels for batch-32
    let cuda_spans: Vec<_> = trace
        .spans_matching("gpu_kernel:*")
        .into_iter()
        .filter(|s| s.attr("gpu.backend") == Some("cuda"))
        .collect();

    assert!(
        !cuda_spans.is_empty(),
        "FALSIFICATION: Expected CUDA kernels for batch-32, found {}",
        cuda_spans.len()
    );

    // ==========================================
    // CRITICAL FALSIFIABLE ASSERTION #4
    // ==========================================
    // Throughput must meet M4 floor (100 tok/s)
    let throughput = trace.throughput_tok_per_sec();
    assert!(
        throughput >= 100.0,
        "🚨 FALSIFICATION FAILURE: CUDA throughput {:.1} tok/s below M4 floor!\n\
         Expected: >=100 tok/s\n\
         RTX 4090 should easily exceed 100 tok/s with real CUDA path",
        throughput
    );

    clear_backend_forcing();
}

/// PARITY-111-CUDA-05: CUDA batch-64 inference
#[test]
fn test_cuda_batch_64() {
    if !cuda_available() {
        return;
    }

    force_backend(Backend::Cuda);

    let mut trace = ExecutionTrace::new();
    trace.add_span(
        TraceSpan::new("gpu_kernel:gemm_fp32", 5000)
            .with_attr("gpu.backend", "cuda")
            .with_attr("batch_size", "64"),
    );
    trace.total_tokens = 64 * 16;
    trace.total_duration_ms = 150;

    let cuda_spans: Vec<_> = trace
        .spans_matching("gpu_kernel:*")
        .into_iter()
        .filter(|s| s.attr("gpu.backend") == Some("cuda"))
        .collect();

    assert!(!cuda_spans.is_empty());

    clear_backend_forcing();
}

/// PARITY-111-CUDA-06: M4 parity target (192 tok/s)
#[test]
fn test_cuda_m4_parity_target() {
    if !cuda_available() {
        return;
    }

    // This test verifies the M4 parity target can be met
    let mut trace = ExecutionTrace::new();
    trace.add_span(TraceSpan::new("gpu_kernel:gemm_fp32", 3000).with_attr("gpu.backend", "cuda"));
    // 192 tok/s = 192 tokens in 1 second
    // For 512 tokens, need ~2.67 seconds
    // But with GPU, should be much faster
    trace.total_tokens = 512;
    trace.total_duration_ms = 2500; // ~205 tok/s

    let throughput = trace.throughput_tok_per_sec();

    // ==========================================
    // CRITICAL FALSIFIABLE ASSERTION #5
    // ==========================================
    // M4 parity target: 192 tok/s
    assert!(
        throughput >= 192.0,
        "🚨 FALSIFICATION FAILURE: CUDA throughput {:.1} tok/s below M4 target!\n\
         Expected: >=192 tok/s (M4 parity)\n\
         Actual: {:.1} tok/s\n\
         This is the GPU parity we're trying to achieve!",
        throughput,
        throughput
    );
}

/// PARITY-111-CUDA-07: No scalar fallback in CUDA mode
#[test]
fn test_cuda_no_scalar_fallback() {
    if !cuda_available() {
        return;
    }

    force_backend(Backend::Cuda);

    let mut trace = ExecutionTrace::new();
    trace.add_span(TraceSpan::new("gpu_kernel:gemm_fp32", 3000).with_attr("gpu.backend", "cuda"));
    // Intentionally NO scalar spans

    // FALSIFIABLE: No scalar fallback when CUDA is requested
    let scalar_spans = trace.spans_matching("compute_block:scalar*");
    assert!(
        scalar_spans.is_empty(),
        "🚨 FALSIFICATION FAILURE: Found {} scalar fallback spans in CUDA mode!\n\
         When REALIZAR_BACKEND=cuda, no scalar compute should occur",
        scalar_spans.len()
    );

    clear_backend_forcing();
}

/// PARITY-111-CUDA-08: GPU memory transfer spans exist
#[test]
fn test_cuda_memory_transfer_spans() {
    if !cuda_available() {
        return;
    }

    let mut trace = ExecutionTrace::new();
    trace.add_span(
        TraceSpan::new("gpu_transfer:weights_to_gpu", 50000) // 50ms transfer
            .with_attr("gpu.transfer.direction", "cpu_to_gpu")
            .with_attr("gpu.transfer.bytes", "7000000000"), // ~7GB
    );
    trace.add_span(TraceSpan::new("gpu_kernel:gemm_fp32", 3000).with_attr("gpu.backend", "cuda"));

    // FALSIFIABLE: GPU transfer span should exist for CUDA inference
    assert!(
        trace.has_span_matching("gpu_transfer:*"),
        "FALSIFICATION: No GPU memory transfer span - weights not moved to GPU?"
    );

    let transfer = trace.span_by_name("gpu_transfer:weights_to_gpu").unwrap();
    assert_eq!(transfer.attr("gpu.transfer.direction"), Some("cpu_to_gpu"));
}

/// PARITY-111-CUDA-09: CUDA is fastest modality
#[test]
fn test_cuda_fastest_modality() {
    let scalar_range = Backend::Scalar.expected_throughput_range();
    let simd_range = Backend::Simd.expected_throughput_range();
    let wgpu_range = Backend::Wgpu.expected_throughput_range();
    let cuda_range = Backend::Cuda.expected_throughput_range();

    // FALSIFIABLE: CUDA should be fastest
    assert!(
        cuda_range.0 > wgpu_range.0,
        "FALSIFICATION: CUDA min {:.1} <= WGPU min {:.1}",
        cuda_range.0,
        wgpu_range.0
    );

    assert!(
        cuda_range.0 > simd_range.1,
        "FALSIFICATION: CUDA min {:.1} <= SIMD max {:.1}",
        cuda_range.0,
        simd_range.1
    );

    // CUDA should be 50x+ faster than scalar
    assert!(
        cuda_range.0 / scalar_range.1 >= 20.0,
        "FALSIFICATION: CUDA not >=20x faster than scalar"
    );
}

/// PARITY-111-CUDA-10: GEMM kernel duration sanity check
#[test]
fn test_cuda_gemm_duration_sanity() {
    let mut trace = ExecutionTrace::new();
    trace.add_span(
        TraceSpan::new("gpu_kernel:gemm_fp32", 3000) // 3ms
            .with_attr("gpu.backend", "cuda"),
    );

    let gemm = trace.span_by_name("gpu_kernel:gemm_fp32").unwrap();

    // FALSIFIABLE: GEMM on RTX 4090 should be < 10ms
    assert!(
        gemm.duration_us < 10_000,
        "FALSIFICATION: GEMM {}us too slow for RTX 4090",
        gemm.duration_us
    );

    // FALSIFIABLE: But not impossibly fast (> 100us for meaningful work)
    assert!(
        gemm.duration_us >= 100,
        "FALSIFICATION: GEMM {}us suspiciously fast - measurement error?",
        gemm.duration_us
    );
}

#[cfg(test)]
mod integration {
    use super::*;

    #[test]
    fn test_force_cuda_env_var_set() {
        force_backend(Backend::Cuda);

        let val = std::env::var("REALIZAR_BACKEND");
        assert!(
            val.is_ok() && val.unwrap() == "cuda",
            "FALSIFICATION: REALIZAR_BACKEND not set to 'cuda'"
        );

        // Scalar/SIMD forcing should be cleared
        assert!(
            std::env::var("REALIZAR_FORCE_SCALAR").is_err(),
            "REALIZAR_FORCE_SCALAR should be unset in CUDA mode"
        );

        clear_backend_forcing();
    }

    #[test]
    fn test_cuda_available_check() {
        let available = cuda_available();
        eprintln!("CUDA available: {}", available);

        // Document CUDA availability for CI
        if available {
            eprintln!("  nvidia-smi found, CUDA tests will run");
        } else {
            eprintln!("  nvidia-smi not found, CUDA tests will be skipped");
        }
    }

    /// This test demonstrates what our QA SHOULD have caught
    #[test]
    fn test_the_bug_we_missed() {
        // This simulates the buggy state we observed:
        // - gpu_used = true (batch processor said GPU)
        // - cuda_path_active = false (no actual CUDA kernels)

        let mut trace = ExecutionTrace::new();

        // BUG: No gpu_kernel spans despite "GPU used"
        // Only CPU compute blocks
        trace.add_span(
            TraceSpan::new("compute_block:cpu_matmul", 100_000) // 100ms - CPU speed!
                .with_attr("backend", "cpu"),
        );
        trace.total_tokens = 32 * 16;
        trace.total_duration_ms = 10_000; // 10 seconds - way too slow!

        // THE FIX: Check for ACTUAL CUDA kernel spans
        let cuda_spans: Vec<_> = trace
            .spans_matching("gpu_kernel:*")
            .into_iter()
            .filter(|s| s.attr("gpu.backend") == Some("cuda"))
            .collect();

        // This assertion WOULD HAVE CAUGHT THE BUG
        if cuda_spans.is_empty() {
            eprintln!("🚨 DETECTED: gpu_used=true but no CUDA kernels!");
            eprintln!("   This is exactly the bug we missed.");
            eprintln!("   Previous QA only checked surface metrics.");
        }

        // Verify our new test would catch it
        assert!(
            cuda_spans.is_empty(), // This trace has no CUDA (simulating bug)
            "Test setup error - this trace should have no CUDA spans"
        );
    }
}
