//! Performance and stress tests for block storage.
//!
//! These tests verify the database handles large datasets efficiently.

use dig_l2_storage::{BlockHeader, BlockStorage, DataLocation, Emission, StoredBlock};
use std::time::Instant;
use tempfile::TempDir;

/// Helper to create a test block with larger data.
fn create_large_block(epoch: u64, height: u64, num_hashes: usize, num_emissions: usize) -> StoredBlock {
    StoredBlock {
        header: BlockHeader {
            epoch,
            height,
            prev_block_root: [0u8; 32],
            timestamp: 1700000000 + height,
            proposer_pubkey: vec![0u8; 48], // BLS public key size
            extra_data: [0u8; 32],
        },
        data_hashes: (0..num_hashes).map(|i| {
            let mut hash = [0u8; 32];
            hash[0..8].copy_from_slice(&(i as u64).to_le_bytes());
            hash
        }).collect(),
        emissions: (0..num_emissions).map(|i| Emission {
            pubkey: vec![i as u8; 48],
            weight: (i as u64 + 1) * 100,
        }).collect(),
        data_root: [3u8; 32],
        emissions_root: [4u8; 32],
    }
}

fn make_root(epoch: u64, height: u64) -> [u8; 32] {
    let mut root = [0u8; 32];
    root[0..8].copy_from_slice(&epoch.to_le_bytes());
    root[8..16].copy_from_slice(&height.to_le_bytes());
    root
}

/// Test writing many small blocks.
#[test]
fn test_write_many_small_blocks() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let num_blocks = 1000;
    let start = Instant::now();

    for i in 0..num_blocks {
        let epoch = i / 100;
        let height = i % 100;
        let root = make_root(epoch, height);
        let block = create_large_block(epoch, height, 2, 1);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    storage.flush().unwrap();
    let elapsed = start.elapsed();

    println!(
        "Wrote {} small blocks in {:?} ({:.2} blocks/sec)",
        num_blocks,
        elapsed,
        num_blocks as f64 / elapsed.as_secs_f64()
    );

    assert_eq!(storage.block_count().unwrap(), num_blocks as usize);
}

/// Test writing blocks with large data payloads.
#[test]
fn test_write_large_blocks() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let num_blocks = 100;
    let data_hashes_per_block = 100;
    let emissions_per_block = 50;

    let start = Instant::now();

    for i in 0..num_blocks {
        let epoch = i / 10;
        let height = i % 10;
        let root = make_root(epoch, height);
        let block = create_large_block(epoch, height, data_hashes_per_block, emissions_per_block);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    storage.flush().unwrap();
    let elapsed = start.elapsed();

    println!(
        "Wrote {} large blocks ({} hashes, {} emissions each) in {:?}",
        num_blocks, data_hashes_per_block, emissions_per_block, elapsed
    );

    assert_eq!(storage.block_count().unwrap(), num_blocks as usize);
}

/// Test read performance.
#[test]
fn test_read_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let num_blocks = 500;

    // Write blocks first
    for i in 0..num_blocks {
        let epoch = i / 50;
        let height = i % 50;
        let root = make_root(epoch, height);
        let block = create_large_block(epoch, height, 10, 5);
        storage.put_block(epoch, &root, &block).unwrap();
    }
    storage.flush().unwrap();

    // Read all blocks
    let start = Instant::now();
    for i in 0..num_blocks {
        let epoch = i / 50;
        let height = i % 50;
        let root = make_root(epoch, height);
        let _ = storage.get_block(epoch, &root).unwrap().unwrap();
    }
    let elapsed = start.elapsed();

    println!(
        "Read {} blocks in {:?} ({:.2} blocks/sec)",
        num_blocks,
        elapsed,
        num_blocks as f64 / elapsed.as_secs_f64()
    );
}

/// Test random access pattern.
#[test]
fn test_random_access() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let epochs = 20u64;
    let blocks_per_epoch = 50u64;

    // Populate
    for epoch in 0..epochs {
        for height in 0..blocks_per_epoch {
            let root = make_root(epoch, height);
            let block = create_large_block(epoch, height, 5, 3);
            storage.put_block(epoch, &root, &block).unwrap();
        }
    }
    storage.flush().unwrap();

    // Random-ish access pattern
    let start = Instant::now();
    let mut reads = 0;
    for i in 0..1000 {
        let epoch = (i * 7 + 3) % epochs;
        let height = (i * 13 + 11) % blocks_per_epoch;
        let root = make_root(epoch, height);
        let _ = storage.get_block(epoch, &root).unwrap().unwrap();
        reads += 1;
    }
    let elapsed = start.elapsed();

    println!(
        "Random access: {} reads in {:?} ({:.2} reads/sec)",
        reads,
        elapsed,
        reads as f64 / elapsed.as_secs_f64()
    );
}

/// Test data indexing performance.
#[test]
fn test_index_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let num_entries = 5000;

    // Index many entries
    let start = Instant::now();
    for i in 0..num_entries {
        let mut hash = [0u8; 32];
        hash[0..8].copy_from_slice(&(i as u64).to_le_bytes());

        let location = DataLocation {
            epoch: i / 100,
            block_root: make_root(i / 100, i % 100),
            index: i as usize,
        };

        storage.index_data(&hash, &location).unwrap();
    }
    storage.flush().unwrap();
    let write_elapsed = start.elapsed();

    println!(
        "Indexed {} entries in {:?} ({:.2} entries/sec)",
        num_entries,
        write_elapsed,
        num_entries as f64 / write_elapsed.as_secs_f64()
    );

    // Look up all entries
    let start = Instant::now();
    for i in 0..num_entries {
        let mut hash = [0u8; 32];
        hash[0..8].copy_from_slice(&(i as u64).to_le_bytes());
        let _ = storage.find_data(&hash).unwrap().unwrap();
    }
    let read_elapsed = start.elapsed();

    println!(
        "Looked up {} entries in {:?} ({:.2} lookups/sec)",
        num_entries,
        read_elapsed,
        num_entries as f64 / read_elapsed.as_secs_f64()
    );
}

/// Test state read/write performance.
#[test]
fn test_state_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let num_keys = 2000;

    // Write state
    let start = Instant::now();
    for i in 0..num_keys {
        let key = format!("key_{:06}", i);
        let value = format!("value_{:06}_with_some_padding_to_make_it_larger", i);
        storage.put_state(&key, value.as_bytes()).unwrap();
    }
    storage.flush().unwrap();
    let write_elapsed = start.elapsed();

    println!(
        "Wrote {} state keys in {:?} ({:.2} keys/sec)",
        num_keys,
        write_elapsed,
        num_keys as f64 / write_elapsed.as_secs_f64()
    );

    // Read state
    let start = Instant::now();
    for i in 0..num_keys {
        let key = format!("key_{:06}", i);
        let _ = storage.get_state(&key).unwrap().unwrap();
    }
    let read_elapsed = start.elapsed();

    println!(
        "Read {} state keys in {:?} ({:.2} keys/sec)",
        num_keys,
        read_elapsed,
        num_keys as f64 / read_elapsed.as_secs_f64()
    );
}

/// Test get_epoch_blocks performance.
#[test]
fn test_epoch_blocks_query_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let epochs = 10u64;
    let blocks_per_epoch = 200u64;

    // Populate
    for epoch in 0..epochs {
        for height in 0..blocks_per_epoch {
            let root = make_root(epoch, height);
            let block = create_large_block(epoch, height, 3, 2);
            storage.put_block(epoch, &root, &block).unwrap();
        }
    }
    storage.flush().unwrap();

    // Query each epoch's blocks
    let start = Instant::now();
    for epoch in 0..epochs {
        let roots = storage.get_epoch_blocks(epoch).unwrap();
        assert_eq!(roots.len(), blocks_per_epoch as usize);
    }
    let elapsed = start.elapsed();

    println!(
        "Queried blocks for {} epochs ({} blocks/epoch) in {:?}",
        epochs, blocks_per_epoch, elapsed
    );
}

/// Test block_count performance with many blocks.
#[test]
fn test_block_count_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let total_blocks = 2000;

    // Populate
    for i in 0..total_blocks {
        let root = make_root(i / 100, i % 100);
        let block = create_large_block(i / 100, i % 100, 2, 1);
        storage.put_block(i / 100, &root, &block).unwrap();
    }
    storage.flush().unwrap();

    // Measure block count
    let start = Instant::now();
    let count = storage.block_count().unwrap();
    let elapsed = start.elapsed();

    assert_eq!(count, total_blocks as usize);
    println!(
        "Counted {} blocks in {:?}",
        count, elapsed
    );
}

/// Test prune performance.
#[test]
fn test_prune_performance() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let epochs = 50u64;
    let blocks_per_epoch = 50u64;

    // Populate
    for epoch in 0..epochs {
        for height in 0..blocks_per_epoch {
            let root = make_root(epoch, height);
            let block = create_large_block(epoch, height, 5, 3);
            storage.put_block(epoch, &root, &block).unwrap();
        }
        storage.set_canonical(epoch, &make_root(epoch, 0)).unwrap();
    }
    storage.flush().unwrap();

    println!("Created {} blocks", epochs * blocks_per_epoch);

    // Prune half
    let start = Instant::now();
    let deleted = storage.prune_before(25).unwrap();
    let elapsed = start.elapsed();

    assert_eq!(deleted, (25 * blocks_per_epoch) as usize);
    println!(
        "Pruned {} blocks in {:?} ({:.2} blocks/sec)",
        deleted,
        elapsed,
        deleted as f64 / elapsed.as_secs_f64()
    );

    assert_eq!(storage.block_count().unwrap(), (25 * blocks_per_epoch) as usize);
}

/// Test serialization/deserialization performance.
#[test]
fn test_serialization_performance() {
    let block = create_large_block(100, 1000, 100, 50);

    let iterations = 1000;

    // Serialization
    let start = Instant::now();
    for _ in 0..iterations {
        let _ = block.to_bytes().unwrap();
    }
    let ser_elapsed = start.elapsed();

    println!(
        "Serialized {} times in {:?} ({:.2} ser/sec)",
        iterations,
        ser_elapsed,
        iterations as f64 / ser_elapsed.as_secs_f64()
    );

    // Deserialization
    let bytes = block.to_bytes().unwrap();
    let start = Instant::now();
    for _ in 0..iterations {
        let _ = StoredBlock::from_bytes(&bytes).unwrap();
    }
    let deser_elapsed = start.elapsed();

    println!(
        "Deserialized {} times in {:?} ({:.2} deser/sec)",
        iterations,
        deser_elapsed,
        iterations as f64 / deser_elapsed.as_secs_f64()
    );
}

/// Stress test with mixed operations.
#[test]
fn test_mixed_operations_stress() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    let start = Instant::now();
    let mut operations = 0;

    // Mixed workload
    for i in 0..1000u64 {
        // Write block
        let epoch = i / 20;
        let height = i % 20;
        let root = make_root(epoch, height);
        let block = create_large_block(epoch, height, 10, 5);
        storage.put_block(epoch, &root, &block).unwrap();
        operations += 1;

        // Set canonical periodically
        if height == 19 {
            storage.set_canonical(epoch, &root).unwrap();
            operations += 1;
        }

        // Index data
        let mut hash = [0u8; 32];
        hash[0..8].copy_from_slice(&i.to_le_bytes());
        let location = DataLocation {
            epoch,
            block_root: root,
            index: height as usize,
        };
        storage.index_data(&hash, &location).unwrap();
        operations += 1;

        // Write state
        let key = format!("state_{}", i);
        storage.put_state(&key, &i.to_le_bytes()).unwrap();
        operations += 1;

        // Read operations (every 10 iterations)
        if i % 10 == 0 && i > 0 {
            let read_epoch = (i - 10) / 20;
            let read_height = (i - 10) % 20;
            let read_root = make_root(read_epoch, read_height);
            let _ = storage.get_block(read_epoch, &read_root).unwrap();
            operations += 1;
        }
    }

    storage.flush().unwrap();
    let elapsed = start.elapsed();

    println!(
        "Completed {} mixed operations in {:?} ({:.2} ops/sec)",
        operations,
        elapsed,
        operations as f64 / elapsed.as_secs_f64()
    );
}
