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

use anyhow::Result;

// The data generation algorithm operates on 1 MiB blocks
use s3dlio::constants::DGEN_BLOCK_SIZE;

#[test]
fn test_generate_controlled_data_alt_basic() -> Result<()> {
    use s3dlio::data_gen_alt::generate_controlled_data_alt;
    
    println!("\n=== Basic Functionality Test ===");
    println!("Note: Minimum size is DGEN_BLOCK_SIZE ({} bytes = 1 MiB)", DGEN_BLOCK_SIZE);
    
    // Test various sizes - all will be at least DGEN_BLOCK_SIZE
    let test_cases = vec![
        (DGEN_BLOCK_SIZE, 1, 1, "1 MiB, no dedup, no compress"),
        (4 * DGEN_BLOCK_SIZE, 1, 1, "4 MiB, no dedup, no compress"),
        (4 * DGEN_BLOCK_SIZE, 2, 1, "4 MiB, 2x dedup, no compress"),
        (4 * DGEN_BLOCK_SIZE, 1, 2, "4 MiB, no dedup, 2x compress"),
        (4 * DGEN_BLOCK_SIZE, 2, 2, "4 MiB, 2x dedup, 2x compress"),
    ];
    
    for (size, dedup, compress, description) in test_cases {
        println!("\nTesting: {}", description);
        let data = generate_controlled_data_alt(size, dedup, compress, None);
        
        assert!(data.len() >= DGEN_BLOCK_SIZE, "Size must be at least DGEN_BLOCK_SIZE for {}", description);
        assert_eq!(data.len(), size, "Size mismatch for {}", description);
        println!("  ✓ Generated {} bytes", data.len());
        
        // Basic sanity: data should not be all zeros (unless compress is very high)
        let zero_count = data.iter().filter(|&&b| b == 0).count();
        let zero_ratio = zero_count as f64 / data.len() as f64;
        println!("  ✓ Zero ratio: {:.1}%", zero_ratio * 100.0);
    }
    
    Ok(())
}

#[test]
fn test_incompressible_with_compress_1() -> Result<()> {
    use s3dlio::data_gen_alt::generate_controlled_data_alt;
    use std::io::Write;
    use std::process::{Command, Stdio};
    
    println!("\n=== Incompressibility Test (compress=1) ===");
    println!("Goal: compress=1 should produce zstd ratio ~1.0 (incompressible)\n");
    
    // Generate 4MB of data with compress=1 (should be incompressible)
    let size = 4 * 1024 * 1024;
    let data = generate_controlled_data_alt(size, 1, 1, None);
    
    println!("Generated {} bytes with compress=1", data.len());
    
    // Try to compress with zstd
    let mut child = Command::new("zstd")
        .args(&["-c", "-1", "-q"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    
    if let Some(mut stdin) = child.stdin.take() {
        stdin.write_all(&data)?;
    }
    
    let output = child.wait_with_output()?;
    let compressed_size = output.stdout.len();
    let ratio = data.len() as f64 / compressed_size as f64;
    
    println!("Original size:    {} bytes", data.len());
    println!("Compressed size:  {} bytes", compressed_size);
    println!("Compression ratio: {:.4}", ratio);
    
    // compress=1 should produce ratio very close to 1.0 (incompressible)
    // Allow small overhead from zstd headers/framing (ratio 0.95-1.05)
    assert!(ratio >= 0.95 && ratio <= 1.05, 
            "compress=1 should be incompressible, got ratio {:.4}", ratio);
    
    println!("  ✓ Data is incompressible (ratio {:.4} ≈ 1.0)", ratio);
    
    Ok(())
}

#[test]
fn test_compressible_with_higher_compress() -> Result<()> {
    use s3dlio::data_gen_alt::generate_controlled_data_alt;
    use std::io::Write;
    use std::process::{Command, Stdio};
    
    println!("\n=== Compressibility Test (compress > 1) ===");
    println!("Goal: Higher compress values should produce better compression\n");
    
    let size = 4 * 1024 * 1024;
    
    let test_cases = vec![
        (2, "2x compress"),
        (3, "3x compress"),
        (4, "4x compress"),
    ];
    
    for (compress, description) in test_cases {
        let data = generate_controlled_data_alt(size, 1, compress, None);
        
        let mut child = Command::new("zstd")
            .args(&["-c", "-1", "-q"])
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()?;
        
        if let Some(mut stdin) = child.stdin.take() {
            stdin.write_all(&data)?;
        }
        
        let output = child.wait_with_output()?;
        let compressed_size = output.stdout.len();
        let ratio = data.len() as f64 / compressed_size as f64;
        
        println!("{}: ratio = {:.4}", description, ratio);
        
        // Higher compress should give better compression (ratio > 1.1)
        assert!(ratio > 1.1, 
                "{} should be compressible, got ratio {:.4}", description, ratio);
        println!("  ✓ Data is compressible (ratio {:.4})", ratio);
    }
    
    Ok(())
}

#[test]
fn test_deduplication_behavior() -> Result<()> {
    use s3dlio::data_gen_alt::generate_controlled_data_alt;
    
    println!("\n=== Deduplication Test ===");
    println!("Goal: Same dedup factor should produce correct number of unique blocks\n");
    println!("Note: Algorithm uses DGEN_BLOCK_SIZE ({} bytes = 1 MiB) blocks", DGEN_BLOCK_SIZE);
    
    // Use 8 blocks total for testing
    let num_blocks = 8;
    let size = num_blocks * DGEN_BLOCK_SIZE;
    
    // Generate with dedup=2 (half unique blocks)
    let data1 = generate_controlled_data_alt(size, 2, 1, None);
    let data2 = generate_controlled_data_alt(size, 2, 1, None);
    
    println!("Generated two datasets with dedup=2");
    println!("Block size: {} bytes (DGEN_BLOCK_SIZE)", DGEN_BLOCK_SIZE);
    println!("Total blocks: {}", num_blocks);
    println!("Expected unique: {} (dedup=2 means 50% unique)", num_blocks / 2);
    
    // Count unique blocks in first dataset - use DGEN_BLOCK_SIZE
    let mut unique_blocks = std::collections::HashSet::new();
    for i in 0..num_blocks {
        let start = i * DGEN_BLOCK_SIZE;
        let end = start + DGEN_BLOCK_SIZE;
        let block = &data1[start..end];
        unique_blocks.insert(block.to_vec());
    }
    
    let actual_unique = unique_blocks.len();
    println!("Actual unique blocks: {}", actual_unique);
    
    // Should have roughly half unique blocks (allow ±1 for rounding)
    let expected_unique = num_blocks / 2;
    assert!((actual_unique as i32 - expected_unique as i32).abs() <= 1,
            "Expected ~{} unique blocks, got {}", expected_unique, actual_unique);
    
    println!("  ✓ Deduplication working correctly");
    
    // Different runs should produce different data (different entropy seed)
    assert_ne!(&data1[..], &data2[..], "Different runs should produce different data");
    println!("  ✓ Different runs produce different data");
    
    Ok(())
}

#[test]
fn test_streaming_generator() -> Result<()> {
    use s3dlio::data_gen_alt::ObjectGenAlt;
    
    println!("\n=== Streaming Generator Test ===");
    println!("Goal: ObjectGenAlt should stream data consistently\n");
    
    let total_size = 1024 * 1024; // 1MB
    let chunk_size = 64 * 1024;   // 64KB chunks
    
    // Note: Single-pass and streaming use different call_entropy,
    // so they produce different data. This is correct behavior.
    // We test that streaming works consistently instead.
    
    let mut streaming = ObjectGenAlt::new(total_size, 1, 1);
    let mut streamed_data = Vec::with_capacity(total_size);
    
    while streamed_data.len() < total_size {
        let mut chunk = vec![0u8; chunk_size];
        let bytes_written = streaming.fill_chunk(&mut chunk);
        streamed_data.extend_from_slice(&chunk[..bytes_written]);
        
        if bytes_written == 0 {
            break; // Done
        }
    }
    
    println!("Streamed size: {} bytes", streamed_data.len());
    
    assert_eq!(streamed_data.len(), total_size, "Should stream exactly total_size bytes");
    
    // Verify data is not all zeros
    let all_zeros = streamed_data.iter().all(|&b| b == 0);
    assert!(!all_zeros, "Streamed data should not be all zeros");
    
    println!("  ✓ Streaming generator works correctly");
    
    Ok(())
}
