//! Performance benchmarks for hadris-iso
//!
//! Run with: cargo test --release -p hadris-iso --test benchmark -- --nocapture

use std::fs;
use std::io::Cursor;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
use tempfile::TempDir;

fn xorriso_available() -> bool {
    Command::new("xorriso")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

fn create_test_content(dir: &Path, file_count: usize, file_size: usize) {
    for i in 0..file_count {
        let filename = format!("file_{:04}.dat", i);
        let content: Vec<u8> = (0..file_size).map(|j| ((i + j) % 256) as u8).collect();
        fs::write(dir.join(&filename), &content).unwrap();
    }
}

fn create_nested_dirs(dir: &Path, depth: usize, breadth: usize) {
    fn create_recursive(dir: &Path, depth: usize, breadth: usize, prefix: &str) {
        if depth == 0 {
            return;
        }
        for i in 0..breadth {
            let subdir = dir.join(format!("{}dir_{}", prefix, i));
            fs::create_dir_all(&subdir).unwrap();
            fs::write(
                subdir.join("data.txt"),
                format!("Depth {} index {}", depth, i),
            )
            .unwrap();
            create_recursive(&subdir, depth - 1, breadth, &format!("{}_{}", prefix, i));
        }
    }
    create_recursive(dir, depth, breadth, "");
}

fn create_iso_with_xorriso(content_dir: &Path, iso_path: &Path) -> bool {
    let output = Command::new("xorriso")
        .args([
            "-as",
            "mkisofs",
            "-o",
            iso_path.to_str().unwrap(),
            "-V",
            "BENCHMARK",
            "-J",
            "-R",
            content_dir.to_str().unwrap(),
        ])
        .output()
        .expect("Failed to run xorriso");
    output.status.success()
}

#[test]
fn bench_iso_open_small() {
    if !xorriso_available() {
        eprintln!("Skipping benchmark: xorriso not available");
        return;
    }

    let temp_dir = TempDir::new().unwrap();
    let content_dir = temp_dir.path().join("content");
    let iso_path = temp_dir.path().join("small.iso");

    fs::create_dir(&content_dir).unwrap();
    create_test_content(&content_dir, 10, 1024);

    assert!(create_iso_with_xorriso(&content_dir, &iso_path));

    let iso_data = fs::read(&iso_path).unwrap();
    let iso_size = iso_data.len();

    // Benchmark opening
    let iterations = 100;
    let start = Instant::now();
    for _ in 0..iterations {
        let cursor = Cursor::new(iso_data.clone());
        let _image = hadris_iso::read::IsoImage::open(cursor).unwrap();
    }
    let elapsed = start.elapsed();

    println!("\n=== Small ISO Open Benchmark ===");
    println!("ISO size: {} bytes", iso_size);
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per open: {:?}", elapsed / iterations);
}

#[test]
fn bench_iso_open_large() {
    if !xorriso_available() {
        eprintln!("Skipping benchmark: xorriso not available");
        return;
    }

    let temp_dir = TempDir::new().unwrap();
    let content_dir = temp_dir.path().join("content");
    let iso_path = temp_dir.path().join("large.iso");

    fs::create_dir(&content_dir).unwrap();
    // 100 files of 64KB each = ~6.4MB of content
    create_test_content(&content_dir, 100, 65536);

    assert!(create_iso_with_xorriso(&content_dir, &iso_path));

    let iso_data = fs::read(&iso_path).unwrap();
    let iso_size = iso_data.len();

    let iterations = 50;
    let start = Instant::now();
    for _ in 0..iterations {
        let cursor = Cursor::new(iso_data.clone());
        let _image = hadris_iso::read::IsoImage::open(cursor).unwrap();
    }
    let elapsed = start.elapsed();

    println!("\n=== Large ISO Open Benchmark ===");
    println!(
        "ISO size: {} bytes ({:.2} MB)",
        iso_size,
        iso_size as f64 / 1024.0 / 1024.0
    );
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per open: {:?}", elapsed / iterations);
}

#[test]
fn bench_directory_traversal() {
    if !xorriso_available() {
        eprintln!("Skipping benchmark: xorriso not available");
        return;
    }

    let temp_dir = TempDir::new().unwrap();
    let content_dir = temp_dir.path().join("content");
    let iso_path = temp_dir.path().join("nested.iso");

    fs::create_dir(&content_dir).unwrap();
    // Create nested structure: 4 levels deep, 3 directories per level
    create_nested_dirs(&content_dir, 4, 3);

    assert!(create_iso_with_xorriso(&content_dir, &iso_path));

    let iso_data = fs::read(&iso_path).unwrap();
    let iso_size = iso_data.len();

    let cursor = Cursor::new(iso_data.clone());
    let image = hadris_iso::read::IsoImage::open(cursor).unwrap();

    // Count entries in root
    let iterations = 100;
    let start = Instant::now();
    let mut total_entries = 0;
    for _ in 0..iterations {
        let root = image.root_dir();
        let dir = root.iter(&image);
        for entry in dir.entries() {
            let _ = entry.unwrap();
            total_entries += 1;
        }
    }
    let elapsed = start.elapsed();

    println!("\n=== Directory Traversal Benchmark ===");
    println!("ISO size: {} bytes", iso_size);
    println!("Iterations: {}", iterations);
    println!("Total entries read: {}", total_entries);
    println!("Entries per iteration: {}", total_entries / iterations);
    println!("Total time: {:?}", elapsed);
    println!(
        "Average time per traversal: {:?}",
        elapsed / iterations as u32
    );
}

#[test]
fn bench_volume_descriptor_parsing() {
    if !xorriso_available() {
        eprintln!("Skipping benchmark: xorriso not available");
        return;
    }

    let temp_dir = TempDir::new().unwrap();
    let content_dir = temp_dir.path().join("content");
    let iso_path = temp_dir.path().join("vd.iso");

    fs::create_dir(&content_dir).unwrap();
    fs::write(content_dir.join("test.txt"), "test").unwrap();

    assert!(create_iso_with_xorriso(&content_dir, &iso_path));

    let iso_data = fs::read(&iso_path).unwrap();

    let iterations = 500;
    let start = Instant::now();
    for _ in 0..iterations {
        let cursor = Cursor::new(iso_data.clone());
        let image = hadris_iso::read::IsoImage::open(cursor).unwrap();
        let _pvd = image.read_pvd();
        let mut count = 0;
        for _ in image.read_volume_descriptors() {
            count += 1;
        }
        assert!(count > 0);
    }
    let elapsed = start.elapsed();

    println!("\n=== Volume Descriptor Parsing Benchmark ===");
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per parse: {:?}", elapsed / iterations);
}

#[test]
fn bench_joliet_encoding_decoding() {
    use hadris_iso::joliet::{decode_joliet_name, encode_joliet_name};

    let test_strings = [
        "simple.txt",
        "test_file_with_longer_name.txt",
        "日本語ファイル.txt",
        "文件名称很长的中文文档.doc",
        "한국어_파일명.xlsx",
        "mixed_混合_ファイル.pdf",
    ];

    let iterations = 10000;

    // Encode benchmark
    let start = Instant::now();
    for _ in 0..iterations {
        for s in &test_strings {
            let _ = encode_joliet_name(s);
        }
    }
    let encode_elapsed = start.elapsed();

    // Create encoded versions for decode benchmark
    let encoded: Vec<Vec<u8>> = test_strings.iter().map(|s| encode_joliet_name(s)).collect();

    // Decode benchmark
    let start = Instant::now();
    for _ in 0..iterations {
        for enc in &encoded {
            let _ = decode_joliet_name(enc);
        }
    }
    let decode_elapsed = start.elapsed();

    println!("\n=== Joliet Encoding/Decoding Benchmark ===");
    println!("Test strings: {}", test_strings.len());
    println!("Iterations: {}", iterations);
    println!("Total encodes: {}", iterations * test_strings.len());
    println!("Encode time: {:?}", encode_elapsed);
    println!("Decode time: {:?}", decode_elapsed);
    println!(
        "Avg encode per string: {:?}",
        encode_elapsed / (iterations * test_strings.len()) as u32
    );
    println!(
        "Avg decode per string: {:?}",
        decode_elapsed / (iterations * test_strings.len()) as u32
    );
}

#[test]
fn bench_rrip_builder() {
    use hadris_iso::rrip::RripBuilder;

    let iterations = 10000;

    let start = Instant::now();
    for i in 0..iterations {
        let mut builder = RripBuilder::new();
        builder
            .add_sp(0)
            .add_rrip_er()
            .add_px(0o100644, 1, 1000, 1000, i as u32)
            .add_nm(b"test_file.txt")
            .add_st();
        let _ = builder.build();
    }
    let elapsed = start.elapsed();

    println!("\n=== RRIP Builder Benchmark ===");
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per build: {:?}", elapsed / iterations);
}

#[test]
fn bench_susp_iterator() {
    use hadris_iso::susp::{SystemUseBuilder, SystemUseIter};

    // Build a typical SUSP area
    let mut builder = SystemUseBuilder::new();
    builder
        .add_sp(0)
        .add_er("RRIP_1991A", "Rock Ridge", "IEEE P1282", 1)
        .add_padding(4)
        .add_st();
    let data = builder.build();

    let iterations = 100000;

    let start = Instant::now();
    for _ in 0..iterations {
        let iter = SystemUseIter::new(&data, 0);
        let mut count = 0;
        for _ in iter {
            count += 1;
        }
        assert!(count > 0);
    }
    let elapsed = start.elapsed();

    println!("\n=== SUSP Iterator Benchmark ===");
    println!("Data size: {} bytes", data.len());
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per iteration: {:?}", elapsed / iterations);
}

#[test]
fn bench_boot_catalog_roundtrip() {
    use hadris_iso::boot::{BootCatalog, BootSectionEntry, EmulationType, PlatformId};

    let iterations = 10000;

    let start = Instant::now();
    for _ in 0..iterations {
        let mut catalog = BootCatalog::default();
        catalog.add_section(
            PlatformId::X80X86,
            vec![BootSectionEntry::new(
                EmulationType::NoEmulation,
                0x07C0,
                4,
                20,
            )],
        );

        let mut buf = Vec::new();
        catalog.write(&mut buf).unwrap();

        let mut cursor = Cursor::new(buf);
        let _parsed = BootCatalog::parse(&mut cursor).unwrap();
    }
    let elapsed = start.elapsed();

    println!("\n=== Boot Catalog Roundtrip Benchmark ===");
    println!("Iterations: {}", iterations);
    println!("Total time: {:?}", elapsed);
    println!("Average time per roundtrip: {:?}", elapsed / iterations);
}

#[test]
fn stress_test_many_files() {
    if !xorriso_available() {
        eprintln!("Skipping stress test: xorriso not available");
        return;
    }

    let temp_dir = TempDir::new().unwrap();
    let content_dir = temp_dir.path().join("content");
    let iso_path = temp_dir.path().join("stress.iso");

    fs::create_dir(&content_dir).unwrap();

    // Create 500 files
    let file_count = 500;
    for i in 0..file_count {
        let filename = format!("file_{:05}.txt", i);
        fs::write(content_dir.join(&filename), format!("Content {}", i)).unwrap();
    }

    let start = Instant::now();
    assert!(create_iso_with_xorriso(&content_dir, &iso_path));
    let create_time = start.elapsed();

    let iso_data = fs::read(&iso_path).unwrap();
    let iso_size = iso_data.len();

    let start = Instant::now();
    let cursor = Cursor::new(iso_data);
    let image = hadris_iso::read::IsoImage::open(cursor).unwrap();
    let open_time = start.elapsed();

    // Debug: Check PVD root directory info
    let pvd = image.read_pvd();
    let root_extent = pvd.dir_record.header.extent.read();
    let root_size = pvd.dir_record.header.data_len.read();

    println!("\n=== Stress Test: Many Files ===");
    println!("Files created: {}", file_count);
    println!(
        "ISO size: {} bytes ({:.2} KB)",
        iso_size,
        iso_size as f64 / 1024.0
    );
    println!("Root directory extent: sector {}", root_extent);
    println!("Root directory size: {} bytes", root_size);
    println!(
        "Expected entries (approx): {} per 2048-byte sector",
        2048 / 40
    ); // ~40 bytes per entry

    // Count all files
    let start = Instant::now();
    let root = image.root_dir();
    let dir = root.iter(&image);
    let mut entry_count = 0;
    let mut total_bytes = 0;
    for entry_result in dir.entries() {
        let entry = entry_result.unwrap();
        total_bytes += entry.size();
        if !entry.is_special() {
            entry_count += 1;
        }
    }
    let traverse_time = start.elapsed();

    println!("ISO creation time (xorriso): {:?}", create_time);
    println!("ISO open time: {:?}", open_time);
    println!("Directory traversal time: {:?}", traverse_time);
    println!("Entries found: {} (plus 2 special entries)", entry_count);
    println!("Total bytes read from directory: {}", total_bytes);

    // For stress test with many files, we need to understand that:
    // - The root directory may span multiple sectors
    // - Each entry is variable size (33 + name length + padding)
    // This is more of a diagnostic than a strict assertion
    if entry_count < file_count {
        eprintln!("Warning: Found fewer entries than created. This may indicate:");
        eprintln!("  - Multi-sector directory traversal issue");
        eprintln!("  - The directory iterator may stop at sector boundaries");
        eprintln!("  - Expected: {}, Found: {}", file_count, entry_count);
    }
}
