//! Integration tests for database pruning operations.
//!
//! These tests verify that old data can be pruned correctly.

use dig_l2_storage::{BlockHeader, BlockStorage, Emission, StoredBlock};
use tempfile::TempDir;

/// Helper to create a test block.
fn create_test_block(epoch: u64, height: u64) -> StoredBlock {
    StoredBlock {
        header: BlockHeader {
            epoch,
            height,
            prev_block_root: [0u8; 32],
            timestamp: 1700000000 + height,
            proposer_pubkey: vec![epoch as u8],
            extra_data: [0u8; 32],
        },
        data_hashes: vec![[1u8; 32]],
        emissions: vec![Emission {
            pubkey: vec![epoch as u8],
            weight: 100,
        }],
        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 basic pruning of old epochs.
#[test]
fn test_basic_pruning() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create 10 epochs with 5 blocks each
    for epoch in 0..10u64 {
        for height in 0..5u64 {
            let root = make_root(epoch, height);
            let block = create_test_block(epoch, height);
            storage.put_block(epoch, &root, &block).unwrap();
        }
        storage.set_canonical(epoch, &make_root(epoch, 4)).unwrap();
    }

    // Verify initial state
    assert_eq!(storage.block_count().unwrap(), 50);

    // Prune epochs 0-4 (keep 5-9)
    let deleted = storage.prune_before(5).unwrap();
    assert_eq!(deleted, 25, "Should delete 25 blocks (5 epochs * 5 blocks)");

    // Verify remaining blocks
    assert_eq!(storage.block_count().unwrap(), 25);

    // Verify pruned epochs are gone
    for epoch in 0..5u64 {
        let blocks = storage.get_epoch_blocks(epoch).unwrap();
        assert!(blocks.is_empty(), "Epoch {} should be pruned", epoch);
        assert!(
            storage.get_canonical(epoch).unwrap().is_none(),
            "Canonical for epoch {} should be pruned",
            epoch
        );
    }

    // Verify remaining epochs still exist
    for epoch in 5..10u64 {
        let blocks = storage.get_epoch_blocks(epoch).unwrap();
        assert_eq!(blocks.len(), 5, "Epoch {} should still have 5 blocks", epoch);
        assert!(
            storage.get_canonical(epoch).unwrap().is_some(),
            "Canonical for epoch {} should exist",
            epoch
        );
    }
}

/// Test pruning with no data to prune.
#[test]
fn test_prune_empty_range() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create epochs 10-15
    for epoch in 10..15u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    // Prune epochs 0-5 (which don't exist)
    let deleted = storage.prune_before(5).unwrap();
    assert_eq!(deleted, 0, "No blocks should be deleted");

    // Original blocks should still exist
    assert_eq!(storage.block_count().unwrap(), 5);
}

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

    // Create epochs 0-9
    for epoch in 0..10u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
        storage.set_canonical(epoch, &root).unwrap();
    }

    assert_eq!(storage.block_count().unwrap(), 10);

    // Prune all epochs
    let deleted = storage.prune_before(100).unwrap();
    assert_eq!(deleted, 10);

    assert_eq!(storage.block_count().unwrap(), 0);

    // All canonicals should be gone
    for epoch in 0..10u64 {
        assert!(storage.get_canonical(epoch).unwrap().is_none());
    }
}

/// Test incremental pruning.
#[test]
fn test_incremental_pruning() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create 20 epochs
    for epoch in 0..20u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    assert_eq!(storage.block_count().unwrap(), 20);

    // Prune in increments
    let deleted1 = storage.prune_before(5).unwrap();
    assert_eq!(deleted1, 5);
    assert_eq!(storage.block_count().unwrap(), 15);

    let deleted2 = storage.prune_before(10).unwrap();
    assert_eq!(deleted2, 5);
    assert_eq!(storage.block_count().unwrap(), 10);

    let deleted3 = storage.prune_before(15).unwrap();
    assert_eq!(deleted3, 5);
    assert_eq!(storage.block_count().unwrap(), 5);

    // Verify remaining epochs
    for epoch in 15..20u64 {
        assert!(storage.has_block(epoch, &make_root(epoch, 0)).unwrap());
    }
}

/// Test pruning with gaps in epochs.
#[test]
fn test_prune_with_gaps() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create non-contiguous epochs: 0, 2, 4, 6, 8
    for epoch in (0..10u64).step_by(2) {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    assert_eq!(storage.block_count().unwrap(), 5);

    // Prune before 5 (should prune epochs 0, 2, 4)
    let deleted = storage.prune_before(5).unwrap();
    assert_eq!(deleted, 3);

    assert_eq!(storage.block_count().unwrap(), 2);

    // Epochs 6 and 8 should remain
    assert!(storage.has_block(6, &make_root(6, 0)).unwrap());
    assert!(storage.has_block(8, &make_root(8, 0)).unwrap());
}

/// Test that pruning preserves state data.
#[test]
fn test_prune_preserves_state() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create blocks
    for epoch in 0..10u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    // Set state
    storage.set_last_height(12345).unwrap();
    storage.put_state("custom_key", b"custom_value").unwrap();

    // Prune
    storage.prune_before(5).unwrap();

    // State should be preserved
    assert_eq!(storage.get_last_height().unwrap(), Some(12345));
    assert_eq!(
        storage.get_state("custom_key").unwrap(),
        Some(b"custom_value".to_vec())
    );
}

/// Test that pruning preserves data index.
#[test]
fn test_prune_preserves_index() {
    use dig_l2_storage::DataLocation;

    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create blocks and index
    for epoch in 0..10u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();

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

    // Prune old epochs
    storage.prune_before(5).unwrap();

    // Index for ALL epochs should still exist (index is separate from blocks)
    for epoch in 0..10u64 {
        let mut hash = [0u8; 32];
        hash[0..8].copy_from_slice(&epoch.to_le_bytes());
        let location = storage.find_data(&hash).unwrap();
        assert!(location.is_some(), "Index for epoch {} should still exist", epoch);
    }
}

/// Test prune persistence.
#[test]
fn test_prune_persistence() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().to_path_buf();

    // First session: create and prune
    {
        let storage = BlockStorage::open(&db_path).unwrap();

        for epoch in 0..20u64 {
            let root = make_root(epoch, 0);
            let block = create_test_block(epoch, 0);
            storage.put_block(epoch, &root, &block).unwrap();
            storage.set_canonical(epoch, &root).unwrap();
        }

        storage.prune_before(10).unwrap();
        storage.flush().unwrap();
    }

    // Second session: verify pruning persisted
    {
        let storage = BlockStorage::open(&db_path).unwrap();

        assert_eq!(storage.block_count().unwrap(), 10);

        // Pruned epochs should be gone
        for epoch in 0..10u64 {
            assert!(!storage.has_block(epoch, &make_root(epoch, 0)).unwrap());
            assert!(storage.get_canonical(epoch).unwrap().is_none());
        }

        // Remaining epochs should exist
        for epoch in 10..20u64 {
            assert!(storage.has_block(epoch, &make_root(epoch, 0)).unwrap());
            assert!(storage.get_canonical(epoch).unwrap().is_some());
        }
    }
}

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

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

    // Create many blocks
    for epoch in 0..epochs {
        for height in 0..blocks_per_epoch {
            let root = make_root(epoch, height);
            let block = create_test_block(epoch, height);
            storage.put_block(epoch, &root, &block).unwrap();
        }
        storage.set_canonical(epoch, &make_root(epoch, blocks_per_epoch - 1)).unwrap();
    }

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

    // Prune half
    let deleted = storage.prune_before(25).unwrap();
    assert_eq!(deleted, (total / 2) as usize);
    assert_eq!(storage.block_count().unwrap(), (total / 2) as usize);

    // Verify remaining
    for epoch in 25..epochs {
        let blocks = storage.get_epoch_blocks(epoch).unwrap();
        assert_eq!(blocks.len(), blocks_per_epoch as usize);
    }
}

/// Test double pruning (pruning already pruned range).
#[test]
fn test_double_prune() {
    let tmp = TempDir::new().unwrap();
    let storage = BlockStorage::open(tmp.path()).unwrap();

    // Create 10 epochs
    for epoch in 0..10u64 {
        let root = make_root(epoch, 0);
        let block = create_test_block(epoch, 0);
        storage.put_block(epoch, &root, &block).unwrap();
    }

    // First prune
    let deleted1 = storage.prune_before(5).unwrap();
    assert_eq!(deleted1, 5);

    // Second prune of same range
    let deleted2 = storage.prune_before(5).unwrap();
    assert_eq!(deleted2, 0, "Should not delete anything on second prune");

    assert_eq!(storage.block_count().unwrap(), 5);
}
