//! Tests for Parallel LoRA Engine with performance benchmarks

use crate::test_fixtures::fixtures::*;
use rayon::prelude::*;
use rstest::*;
use serial_test::serial;
use std::sync::Arc;
use std::time::Instant;

/// Test ParallelLoRAEngine creation with cached models
#[rstest]
#[serial]
fn test_parallel_engine_creation(
    cached_intent_classifier: Option<Arc<super::intent_lora::IntentLoRAClassifier>>,
    cached_pii_classifier: Option<Arc<super::pii_lora::PIILoRAClassifier>>,
    cached_security_classifier: Option<Arc<super::security_lora::SecurityLoRAClassifier>>,
) {
    if cached_intent_classifier.is_some()
        && cached_pii_classifier.is_some()
        && cached_security_classifier.is_some()
    {
        println!("All classifiers available for parallel engine testing");
    } else {
        println!("⏭️  Skipping parallel engine creation test - models not cached");
    }
}

/// Test parallel classification with rayon optimization
#[rstest]
#[serial]
fn test_parallel_classify_basic(
    cached_intent_classifier: Option<Arc<super::intent_lora::IntentLoRAClassifier>>,
    cached_pii_classifier: Option<Arc<super::pii_lora::PIILoRAClassifier>>,
    cached_security_classifier: Option<Arc<super::security_lora::SecurityLoRAClassifier>>,
) {
    // Skip if models not available
    if cached_intent_classifier.is_none()
        || cached_pii_classifier.is_none()
        || cached_security_classifier.is_none()
    {
        println!("⏭️  Skipping parallel classification test - models not cached");
        return;
    }

    println!("\n🧪 Testing parallel classification with rayon optimization");

    let test_texts = vec![
        "I want to book a flight to New York",
        "My SSN is 123-45-6789 and my email is test@example.com",
        "DROP TABLE users; -- malicious SQL injection",
    ];

    // Note: This test validates the API structure
    // Actual performance testing requires model files
    println!("Test inputs prepared: {} texts", test_texts.len());
    println!("   - Intent text: '{}'", test_texts[0]);
    println!("   - PII text: '{}'", test_texts[1]);
    println!("   - Security text: '{}'", test_texts[2]);
}

/// Performance benchmark: Single text vs Batch processing
///
/// This test compares the performance of processing texts one-by-one
/// vs using rayon's parallel batch processing.
#[rstest]
#[serial]
#[ignore] // Run with: cargo test --ignored test_performance_batch_vs_single
fn test_performance_batch_vs_single(
    cached_intent_classifier: Option<Arc<super::intent_lora::IntentLoRAClassifier>>,
    cached_pii_classifier: Option<Arc<super::pii_lora::PIILoRAClassifier>>,
) {
    if cached_intent_classifier.is_none() || cached_pii_classifier.is_none() {
        println!("⏭️  Skipping performance test - models not cached");
        return;
    }

    println!("\n📊 Performance Benchmark: Batch vs Single Processing");
    println!("{}", "=".repeat(70));

    let test_texts: Vec<&str> = vec![
        "Book a flight to Paris",
        "My email is user@example.com",
        "Schedule a meeting for tomorrow",
        "SSN: 987-65-4321",
        "Cancel my subscription",
        "Phone: +1-555-123-4567",
        "Transfer money to savings account",
        "Address: 123 Main St",
        "Check my account balance",
        "Credit card: 4532-1234-5678-9010",
    ];

    let intent_classifier = cached_intent_classifier.as_ref().unwrap();
    let pii_classifier = cached_pii_classifier.as_ref().unwrap();

    // Warmup run
    println!("🔥 Warmup run...");
    let _ = intent_classifier.batch_classify(&test_texts[..2]);
    let _ = pii_classifier.batch_detect(&test_texts[..2]);

    // Test 1: Sequential processing (one-by-one)
    println!("\n1️ Sequential Processing (baseline)");
    let start = Instant::now();
    let mut intent_results_seq = Vec::new();
    for text in &test_texts {
        if let Ok(result) = intent_classifier.classify_intent(text) {
            intent_results_seq.push(result);
        }
    }
    let seq_duration = start.elapsed();
    println!(
        "   ⏱️  Intent: {:?} for {} texts",
        seq_duration,
        test_texts.len()
    );

    let start = Instant::now();
    let mut pii_results_seq = Vec::new();
    for text in &test_texts {
        if let Ok(result) = pii_classifier.detect_pii(text) {
            pii_results_seq.push(result);
        }
    }
    let seq_pii_duration = start.elapsed();
    println!(
        "   ⏱️  PII: {:?} for {} texts",
        seq_pii_duration,
        test_texts.len()
    );

    // Test 2: Parallel processing with rayon
    println!("\n2️  Parallel Processing (rayon optimized)");
    let start = Instant::now();
    let intent_results_par = intent_classifier.parallel_classify(&test_texts);
    let par_duration = start.elapsed();
    println!(
        "   ⏱️  Intent: {:?} for {} texts",
        par_duration,
        test_texts.len()
    );

    let start = Instant::now();
    let pii_results_par = pii_classifier.parallel_detect(&test_texts);
    let par_pii_duration = start.elapsed();
    println!(
        "   ⏱️  PII: {:?} for {} texts",
        par_pii_duration,
        test_texts.len()
    );

    // Calculate speedup
    println!("\n📈 Performance Improvement");
    println!("{}", "=".repeat(70));
    if par_duration.as_millis() > 0 {
        let intent_speedup = seq_duration.as_secs_f64() / par_duration.as_secs_f64();
        println!("   Intent: {:.2}x speedup", intent_speedup);
    }
    if par_pii_duration.as_millis() > 0 {
        let pii_speedup = seq_pii_duration.as_secs_f64() / par_pii_duration.as_secs_f64();
        println!("   PII: {:.2}x speedup", pii_speedup);
    }

    // Verify correctness
    if let Ok(par_results) = intent_results_par {
        assert_eq!(
            intent_results_seq.len(),
            par_results.len(),
            "Parallel processing should produce same number of results"
        );
        println!(
            "\nCorrectness verified: {} results match",
            par_results.len()
        );
    }

    if let Ok(par_results) = pii_results_par {
        assert_eq!(
            pii_results_seq.len(),
            par_results.len(),
            "Parallel PII detection should produce same number of results"
        );
    }
}

/// Performance benchmark: Concurrent requests simulation
///
/// Simulates multiple Go requests calling FFI simultaneously
#[rstest]
#[serial]
#[ignore] // Run with: cargo test --ignored test_performance_concurrent
fn test_performance_concurrent_requests(
    cached_intent_classifier: Option<Arc<super::intent_lora::IntentLoRAClassifier>>,
) {
    if cached_intent_classifier.is_none() {
        println!("⏭️  Skipping concurrent performance test - model not cached");
        return;
    }

    println!("\n📊 Concurrent Requests Benchmark");
    println!("{}", "=".repeat(70));
    println!("Simulating multiple Go goroutines calling FFI...");

    let classifier = cached_intent_classifier.as_ref().unwrap();
    let test_text = "Book a flight to London";

    // Test with different concurrency levels
    for num_threads in &[1, 2, 4, 8, 16] {
        println!("\n🔢 Testing with {} concurrent requests", num_threads);

        let start = Instant::now();

        // Use rayon for parallel execution - simpler and more efficient
        let results: Vec<_> = (0..*num_threads)
            .into_par_iter()
            .map(|_| classifier.classify_intent(test_text))
            .collect();

        let success_count = results.iter().filter(|r| r.is_ok()).count();

        let duration = start.elapsed();
        println!(
            "   ⏱️  {} requests completed in {:?} ({} successful)",
            num_threads, duration, success_count
        );
        println!(
            "   📊 Avg latency: {:.2}ms/request",
            duration.as_millis() as f64 / *num_threads as f64
        );
    }
}

/// Performance benchmark: rayon::join vs manual threading
///
/// Compares the new rayon::join implementation with the old manual threading approach
#[rstest]
#[serial]
#[ignore] // Run with: cargo test --ignored test_performance_rayon_vs_manual
fn test_performance_rayon_vs_manual(
    cached_intent_classifier: Option<Arc<super::intent_lora::IntentLoRAClassifier>>,
    cached_pii_classifier: Option<Arc<super::pii_lora::PIILoRAClassifier>>,
    cached_security_classifier: Option<Arc<super::security_lora::SecurityLoRAClassifier>>,
) {
    use std::sync::Mutex;

    if cached_intent_classifier.is_none()
        || cached_pii_classifier.is_none()
        || cached_security_classifier.is_none()
    {
        println!("⏭️  Skipping rayon vs manual threading test - models not cached");
        return;
    }

    println!("\n📊 Rayon vs Manual Threading Comparison");
    println!("{}", "=".repeat(70));

    let intent_classifier = cached_intent_classifier.as_ref().unwrap();
    let pii_classifier = cached_pii_classifier.as_ref().unwrap();
    let security_classifier = cached_security_classifier.as_ref().unwrap();

    let test_texts: Vec<&str> = vec!["Book a flight", "My SSN is 123-45-6789", "DROP TABLE users"];

    // Warmup
    let _ = intent_classifier.batch_classify(&test_texts[..1]);
    let _ = pii_classifier.batch_detect(&test_texts[..1]);
    let _ = security_classifier.batch_detect(&test_texts[..1]);

    // Test 1: Old approach (manual threading with Arc<Mutex>)
    println!("\n1️ Old Approach: Manual threading with Arc<Mutex<Vec>>");
    let start = Instant::now();
    {
        let texts_owned: Vec<String> = test_texts.iter().map(|s| s.to_string()).collect();

        let intent_results = Arc::new(Mutex::new(Vec::new()));
        let pii_results = Arc::new(Mutex::new(Vec::new()));
        let security_results = Arc::new(Mutex::new(Vec::new()));

        let handles = vec![
            {
                let classifier = Arc::clone(intent_classifier);
                let results = Arc::clone(&intent_results);
                let texts = texts_owned.clone();
                std::thread::spawn(move || {
                    let text_refs: Vec<&str> = texts.iter().map(|s| s.as_str()).collect();
                    if let Ok(task_results) = classifier.batch_classify(&text_refs) {
                        let mut guard = results.lock().unwrap();
                        *guard = task_results;
                    }
                })
            },
            {
                let classifier = Arc::clone(pii_classifier);
                let results = Arc::clone(&pii_results);
                let texts = texts_owned.clone();
                std::thread::spawn(move || {
                    let text_refs: Vec<&str> = texts.iter().map(|s| s.as_str()).collect();
                    if let Ok(task_results) = classifier.batch_detect(&text_refs) {
                        let mut guard = results.lock().unwrap();
                        *guard = task_results;
                    }
                })
            },
            {
                let classifier = Arc::clone(security_classifier);
                let results = Arc::clone(&security_results);
                let texts = texts_owned;
                std::thread::spawn(move || {
                    let text_refs: Vec<&str> = texts.iter().map(|s| s.as_str()).collect();
                    if let Ok(task_results) = classifier.batch_detect(&text_refs) {
                        let mut guard = results.lock().unwrap();
                        *guard = task_results;
                    }
                })
            },
        ];

        for handle in handles {
            let _ = handle.join();
        }
    }
    let manual_duration = start.elapsed();
    println!("   ⏱️  Duration: {:?}", manual_duration);

    // Test 2: New approach (rayon::join)
    println!("\n2️  New Approach: rayon::join (no Arc<Mutex>)");
    let start = Instant::now();
    {
        let _ = rayon::join(
            || {
                rayon::join(
                    || intent_classifier.batch_classify(&test_texts),
                    || pii_classifier.batch_detect(&test_texts),
                )
            },
            || security_classifier.batch_detect(&test_texts),
        );
    }
    let rayon_duration = start.elapsed();
    println!("   ⏱️  Duration: {:?}", rayon_duration);

    // Calculate improvement
    println!("\n📈 Performance Comparison");
    println!("{}", "=".repeat(70));
    if rayon_duration.as_millis() > 0 {
        let speedup = manual_duration.as_secs_f64() / rayon_duration.as_secs_f64();
        println!("   Speedup: {:.2}x", speedup);

        if speedup > 1.0 {
            let improvement = (speedup - 1.0) * 100.0;
            println!("   Improvement: {:.1}% faster", improvement);
        }
    }

    println!("\nBenefits of rayon::join:");
    println!("   • No Arc<Mutex> overhead");
    println!("   • No manual thread management");
    println!("   • Cleaner code (~70% reduction)");
    println!("   • Better error propagation");
}
