// tests/test_compression_enhanced_reporting.rs
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
// SPDX-FileCopyrightText: 2025 Russ Fellows <russ.fellows@gmail.com>

use anyhow::Result;
use tempfile::tempdir;
use std::path::PathBuf;

use s3dlio::object_store::{ObjectWriter, CompressionConfig};
use s3dlio::file_store::FileSystemWriter;
use s3dlio::file_store_direct::{DirectIOWriter, FileSystemConfig};

/// Different test data patterns to show varied compression ratios
#[derive(Debug)]
struct TestDataPattern {
    name: &'static str,
    description: &'static str,
    generator: fn(usize) -> Vec<u8>,
}

impl TestDataPattern {
    fn generate(&self, size: usize) -> Vec<u8> {
        (self.generator)(size)
    }
}

fn get_test_patterns() -> Vec<TestDataPattern> {
    vec![
        TestDataPattern {
            name: "highly_compressible",
            description: "Repeated text pattern (should compress ~98%)",
            generator: |size| {
                let pattern = b"Hello, compression test! This pattern repeats. ";
                let mut data = Vec::new();
                while data.len() < size {
                    data.extend_from_slice(pattern);
                }
                data.truncate(size);
                data
            },
        },
        TestDataPattern {
            name: "moderately_compressible", 
            description: "JSON-like structured data (should compress ~60-80%)",
            generator: |size| {
                let mut data = Vec::new();
                let mut i = 0;
                while data.len() < size {
                    let json_chunk = format!(
                        r#"{{"id": {}, "name": "user_{}", "timestamp": "2024-01-01T{}:00:00Z", "data": [{}, {}, {}]}}, "#,
                        i, i, i % 24, i * 10, i * 11, i * 12
                    );
                    data.extend_from_slice(json_chunk.as_bytes());
                    i += 1;
                }
                data.truncate(size);
                data
            },
        },
        TestDataPattern {
            name: "low_compressible",
            description: "Random-like data (should compress ~5-20%)",
            generator: |size| {
                // Create pseudo-random but deterministic data
                let mut data = Vec::with_capacity(size);
                for i in 0..size {
                    // Simple LCG to create deterministic but varied data
                    let val = ((i * 1103515245 + 12345) >> 16) as u8;
                    data.push(val);
                }
                data
            },
        },
        TestDataPattern {
            name: "incompressible",
            description: "High-entropy data (should compress <5%)",
            generator: |size| {
                // Create high-entropy data using a more complex transformation
                let mut data = Vec::with_capacity(size);
                for i in 0..size {
                    let val = ((i as u64 * 2654435761_u64) ^ (i as u64).rotate_left(13)) as u8;
                    data.push(val);
                }
                data
            },
        },
    ]
}

fn format_compression_report(
    pattern_name: &str,
    description: &str,
    original_size: usize,
    compressed_size: u64,
    compression_ratio: f64,
) -> String {
    let compression_percentage = (1.0 - compression_ratio) * 100.0;
    let savings_kb = (original_size as f64 - compressed_size as f64) / 1024.0;
    
    format!(
        "📊 {} ({})\n   Original: {} bytes | Compressed: {} bytes | Ratio: {:.3} | Compression: {:.1}% | Saved: {:.1}KB",
        pattern_name,
        description,
        original_size,
        compressed_size,
        compression_ratio,
        compression_percentage,
        savings_kb
    )
}

#[tokio::test]
async fn test_filesystem_compression_varied_patterns() -> Result<()> {
    println!("🧪 Testing FileSystem compression with varied data patterns");
    let temp_dir = tempdir()?;
    let patterns = get_test_patterns();
    let test_size = 50_000; // 50KB test data
    
    for pattern in patterns {
        let test_data = pattern.generate(test_size);
        let file_path = temp_dir.path().join(format!("test_fs_{}.dat", pattern.name));
        
        // Test with moderate compression level
        let mut writer = FileSystemWriter::new_with_compression(
            file_path.clone(),
            CompressionConfig::zstd_level(5)
        ).await?;
        
        writer.write_chunk(&test_data).await?;
        Box::new(writer).finalize().await?;
        
        // Get actual compressed file size for ratio calculation
        let compressed_path = format!("{}.zst", file_path.display());
        let (compressed_size, compression_ratio) = if PathBuf::from(&compressed_path).exists() {
            let size = std::fs::metadata(&compressed_path)?.len();
            let ratio = size as f64 / test_data.len() as f64;
            (size, ratio)
        } else {
            // No compression applied - use original file
            let size = std::fs::metadata(&file_path)?.len();
            (size, 1.0)
        };
        
        println!("✅ {}", format_compression_report(
            pattern.name,
            pattern.description,
            test_data.len(),
            compressed_size,
            compression_ratio,
        ));
    }
    
    Ok(())
}

#[tokio::test]
async fn test_directio_compression_varied_patterns() -> Result<()> {
    println!("\n🧪 Testing DirectIO compression with varied data patterns");
    let temp_dir = tempdir()?;
    let patterns = get_test_patterns();
    let test_size = 50_000; // 50KB test data
    
    let config = FileSystemConfig {
        direct_io: false, // Use regular I/O for testing
        sync_writes: false,
        alignment: 4096,
        min_io_size: 4096,
        enable_range_engine: false,
        range_engine: Default::default(),
        buffer_pool: None,
    };
    
    for pattern in patterns {
        let test_data = pattern.generate(test_size);
        let file_path = temp_dir.path().join(format!("test_directio_{}.dat", pattern.name));
        
        // Test with moderate compression level
        let mut writer = DirectIOWriter::new_with_compression(
            file_path.clone(),
            config.clone(),
            CompressionConfig::zstd_level(5)
        ).await?;
        
        writer.write_chunk(&test_data).await?;
        Box::new(writer).finalize().await?;
        
        // Get actual compressed file size for ratio calculation
        let compressed_path = format!("{}.zst", file_path.display());
        let (compressed_size, compression_ratio) = if PathBuf::from(&compressed_path).exists() {
            let size = std::fs::metadata(&compressed_path)?.len();
            let ratio = size as f64 / test_data.len() as f64;
            (size, ratio)
        } else {
            // No compression applied - use original file
            let size = std::fs::metadata(&file_path)?.len();
            (size, 1.0)
        };
        
        println!("✅ {}", format_compression_report(
            pattern.name,
            pattern.description,
            test_data.len(),
            compressed_size,
            compression_ratio,
        ));
    }
    
    Ok(())
}

#[tokio::test]
async fn test_compression_levels_with_reporting() -> Result<()> {
    println!("\n🧪 Testing compression levels with detailed reporting");
    let temp_dir = tempdir()?;
    
    // Use moderately compressible data to show level differences
    let test_data = get_test_patterns()[1].generate(100_000); // 100KB JSON-like data
    println!("📋 Testing compression levels with JSON-like data (100KB)");
    
    for level in [1, 3, 5, 10, 15, 22] {
        let file_path = temp_dir.path().join(format!("test_level_{}.dat", level));
        let mut writer = FileSystemWriter::new_with_compression(
            file_path.clone(),
            CompressionConfig::zstd_level(level)
        ).await?;
        
        writer.write_chunk(&test_data).await?;
        Box::new(writer).finalize().await?;
        
        // Get actual compressed file size and calculate ratio
        let compressed_path = format!("{}.zst", file_path.display());
        let compressed_size = std::fs::metadata(&compressed_path)?.len();
        let compression_ratio = compressed_size as f64 / test_data.len() as f64;
        
        println!("✅ Level {}: {}", level, format_compression_report(
            &format!("zstd_level_{}", level),
            &format!("Compression level {} (higher = better compression, slower)", level),
            test_data.len(),
            compressed_size,
            compression_ratio,
        ));
    }
    
    Ok(())
}

#[tokio::test]
async fn test_compression_effectiveness_summary() -> Result<()> {
    println!("\n📈 Compression Effectiveness Summary");
    println!("====================================");
    
    let temp_dir = tempdir()?;
    let patterns = get_test_patterns();
    let test_size = 100_000; // 100KB for better analysis
    
    let mut results = Vec::new();
    
    for pattern in patterns {
        let test_data = pattern.generate(test_size);
        let file_path = temp_dir.path().join(format!("summary_{}.dat", pattern.name));
        
        let mut writer = FileSystemWriter::new_with_compression(
            file_path.clone(),
            CompressionConfig::zstd_level(5) // Standard level
        ).await?;
        
        writer.write_chunk(&test_data).await?;
        Box::new(writer).finalize().await?;
        
        let compressed_path = format!("{}.zst", file_path.display());
        let compressed_size = std::fs::metadata(&compressed_path)?.len();
        let compression_ratio = compressed_size as f64 / test_data.len() as f64;
        let compression_percentage = (1.0 - compression_ratio) * 100.0;
        
        results.push((pattern.name, pattern.description, compression_percentage, compressed_size));
        
        println!("   {}: {:.1}% compression", pattern.name, compression_percentage);
    }
    
    println!("\n🎯 Key Insights:");
    println!("   • Repeated text patterns achieve ~98% compression");
    println!("   • Structured data (JSON/XML) compresses ~60-80%");
    println!("   • Random-like data compresses ~5-20%");
    println!("   • High-entropy data compresses <5%");
    println!("   • s3dlio v0.7.0 compression working across all data types! ✨");
    
    Ok(())
}

#[tokio::test]
async fn test_performance_impact_analysis() -> Result<()> {
    println!("\n⚡ Compression Performance Impact Analysis");
    println!("==========================================");
    
    let temp_dir = tempdir()?;
    let test_data = get_test_patterns()[1].generate(1_000_000); // 1MB JSON-like data
    
    // Test different compression levels for performance impact
    let levels = [1, 5, 10, 22];
    
    for level in levels {
        let file_path = temp_dir.path().join(format!("perf_test_level_{}.dat", level));
        
        let start_time = std::time::Instant::now();
        
        let mut writer = FileSystemWriter::new_with_compression(
            file_path.clone(),
            CompressionConfig::zstd_level(level)
        ).await?;
        
        writer.write_chunk(&test_data).await?;
        Box::new(writer).finalize().await?;
        
        let duration = start_time.elapsed();
        let compressed_path = format!("{}.zst", file_path.display());
        let compressed_size = std::fs::metadata(&compressed_path)?.len();
        let compression_ratio = compressed_size as f64 / test_data.len() as f64;
        let compression_percentage = (1.0 - compression_ratio) * 100.0;
        let throughput_mbps = (test_data.len() as f64 / 1024.0 / 1024.0) / duration.as_secs_f64();
        
        println!("📊 Level {}: {:.1}% compression | {:.1}ms | {:.1} MB/s | {} bytes",
                level, compression_percentage, duration.as_millis(), throughput_mbps, compressed_size);
    }
    
    println!("\n💡 Performance Insights:");
    println!("   • Level 1: Fastest compression, good for real-time scenarios");
    println!("   • Level 5: Balanced compression/speed, recommended default");
    println!("   • Level 10: Better compression, suitable for archival");
    println!("   • Level 22: Maximum compression, use for long-term storage");
    
    Ok(())
}
