//! WAL (Write-Ahead Log) Unit Tests
//!
//! Comprehensive tests for Graph WAL functionality including:
//! - WAL write integrity
//! - WAL read and recovery
//! - WAL rotation
//! - Corrupted WAL handling

use std::collections::HashSet;
use std::fs;
use std::sync::Arc;

use tempfile::tempdir;

use crate::storage::concurrent::WriteMetrics;
use crate::storage::wal::SyncMode;
use crate::types::graph::{Entity, Relation, entity_types, relation_types};

use super::wal::{
    GraphWALConfig, GraphWALEntry, GraphWALHeader, GraphWALOperation,
    GraphWALReader, GraphWALWriter,
};
use super::types::GraphWriteOp;

// ============================================================================
// Test Helpers
// ============================================================================

/// Create a test WAL writer with default config
fn create_test_writer(path: &std::path::Path, shard_id: usize) -> GraphWALWriter {
    let config = GraphWALConfig::new(path);
    let metrics = Arc::new(WriteMetrics::new());
    GraphWALWriter::new(config, shard_id, metrics).unwrap()
}

/// Create a test WAL writer with specific config
fn create_writer_with_config(config: GraphWALConfig, shard_id: usize) -> GraphWALWriter {
    let metrics = Arc::new(WriteMetrics::new());
    GraphWALWriter::new(config, shard_id, metrics).unwrap()
}

/// Create a test entity
fn create_entity(name: &str) -> Entity {
    Entity::new(name, entity_types::PERSON)
}

/// Create a test entity with custom type
fn create_entity_with_type(name: &str, entity_type: &str) -> Entity {
    Entity::new(name, entity_type)
}

/// Create a test relation
fn create_relation(source_id: &str, target_id: &str) -> Relation {
    Relation::new(source_id, target_id, relation_types::FRIEND)
}

// ============================================================================
// 5.1.3.1 WAL Write Integrity Tests
// ============================================================================

#[test]
fn test_wal_write_single_entity() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let entity = create_entity("Alice");
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::put_entity(seq, 0, entity.clone());
    
    let result_seq = writer.append(&entry).unwrap();
    assert_eq!(result_seq, seq);
    
    writer.close().unwrap();
    
    // Verify entry was written
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 1);
    assert_eq!(entries[0].operation, GraphWALOperation::PutEntity);
    assert_eq!(entries[0].entity.as_ref().unwrap().id, entity.id);
}

#[test]
fn test_wal_write_single_relation() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let relation = create_relation("alice-id", "bob-id");
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::put_relation(seq, 0, relation.clone());
    
    writer.append(&entry).unwrap();
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 1);
    assert_eq!(entries[0].operation, GraphWALOperation::PutRelation);
    let stored_rel = entries[0].relation.as_ref().unwrap();
    assert_eq!(stored_rel.source, relation.source);
    assert_eq!(stored_rel.target, relation.target);
}

#[test]
fn test_wal_write_delete_entity() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let delete_id = "entity-to-delete";
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::delete_entity(seq, 0, delete_id.to_string());
    
    writer.append(&entry).unwrap();
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 1);
    assert_eq!(entries[0].operation, GraphWALOperation::DeleteEntity);
    assert_eq!(entries[0].delete_id.as_ref().unwrap(), delete_id);
}

#[test]
fn test_wal_write_delete_relation() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let delete_id = "relation-to-delete";
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::delete_relation(seq, 0, delete_id.to_string());
    
    writer.append(&entry).unwrap();
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 1);
    assert_eq!(entries[0].operation, GraphWALOperation::DeleteRelation);
    assert_eq!(entries[0].delete_id.as_ref().unwrap(), delete_id);
}

#[test]
fn test_wal_write_batch_operations() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Create batch of operations
    let ops = vec![
        GraphWriteOp::PutEntity(create_entity("Alice")),
        GraphWriteOp::PutEntity(create_entity("Bob")),
        GraphWriteOp::PutRelation(create_relation("alice", "bob")),
        GraphWriteOp::DeleteEntity("old-entity".to_string()),
    ];
    
    let batch_op = GraphWriteOp::Batch(ops);
    let seq = writer.next_sequence();
    let entries = GraphWALEntry::from_op(seq, 0, &batch_op);
    
    assert_eq!(entries.len(), 4);
    
    for entry in &entries {
        writer.append(entry).unwrap();
    }
    writer.close().unwrap();
    
    // Verify all entries
    let reader = GraphWALReader::new(dir.path());
    let stored = reader.read_all().unwrap();
    
    assert_eq!(stored.len(), 4);
    assert_eq!(stored[0].operation, GraphWALOperation::PutEntity);
    assert_eq!(stored[1].operation, GraphWALOperation::PutEntity);
    assert_eq!(stored[2].operation, GraphWALOperation::PutRelation);
    assert_eq!(stored[3].operation, GraphWALOperation::DeleteEntity);
}

#[test]
fn test_wal_write_many_entries() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let num_entries = 1000;
    
    for i in 0..num_entries {
        let entity = create_entity(&format!("Person-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), num_entries);
    
    // Verify all checksums
    for entry in &entries {
        assert!(entry.verify_checksum(), "Checksum should be valid for entry {}", entry.sequence);
    }
}

#[test]
fn test_wal_entry_checksum_integrity() {
    let entity = create_entity("Test");
    let entry = GraphWALEntry::put_entity(1, 0, entity);
    
    // Original checksum should be valid
    assert!(entry.verify_checksum());
    
    // Tampered entry should fail verification
    let mut tampered = entry.clone();
    tampered.sequence = 999;
    assert!(!tampered.verify_checksum(), "Tampered entry should fail checksum");
    
    // Recalculated checksum should pass
    tampered.checksum = tampered.calculate_checksum();
    assert!(tampered.verify_checksum());
}

#[test]
fn test_wal_append_op_helper() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let op = GraphWriteOp::PutEntity(create_entity("Alice"));
    let seq = writer.append_op(&op).unwrap();
    
    assert!(seq > 0);
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    assert_eq!(entries.len(), 1);
}

#[test]
fn test_wal_append_batch_helper() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let entries: Vec<GraphWALEntry> = (0..10)
        .map(|i| {
            let entity = create_entity(&format!("Person-{}", i));
            GraphWALEntry::put_entity(writer.next_sequence(), 0, entity)
        })
        .collect();
    
    writer.append_batch(&entries).unwrap();
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let stored = reader.read_all().unwrap();
    assert_eq!(stored.len(), 10);
}

#[test]
fn test_wal_sync_modes() {
    let dir = tempdir().unwrap();
    
    // Test with SyncMode::Every
    {
        let config = GraphWALConfig::new(dir.path().join("every"))
            .with_sync_mode(SyncMode::Every);
        let mut writer = create_writer_with_config(config, 0);
        
        let entity = create_entity("Sync Every");
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
        
        writer.close().unwrap();
        
        let reader = GraphWALReader::new(dir.path().join("every"));
        let entries = reader.read_all().unwrap();
        assert_eq!(entries.len(), 1);
    }
    
    // Test with SyncMode::Batch
    {
        let config = GraphWALConfig::new(dir.path().join("batch"))
            .with_sync_mode(SyncMode::Batch);
        let mut writer = create_writer_with_config(config, 0);
        
        for i in 0..5 {
            let entity = create_entity(&format!("Batch-{}", i));
            let seq = writer.next_sequence();
            let entry = GraphWALEntry::put_entity(seq, 0, entity);
            writer.append(&entry).unwrap();
        }
        writer.sync().unwrap();
        
        writer.close().unwrap();
        
        let reader = GraphWALReader::new(dir.path().join("batch"));
        let entries = reader.read_all().unwrap();
        assert_eq!(entries.len(), 5);
    }
}

#[test]
fn test_wal_mixed_operations() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Create entities
    let alice = create_entity("Alice");
    let bob = create_entity("Bob");
    let alice_id = alice.id.clone();
    let bob_id = bob.id.clone();
    
    // Write mixed operations
    writer.append(&GraphWALEntry::put_entity(writer.next_sequence(), 0, alice)).unwrap();
    writer.append(&GraphWALEntry::put_entity(writer.next_sequence(), 0, bob)).unwrap();
    
    let relation = create_relation(&alice_id, &bob_id);
    let rel_id = relation.id.clone();
    writer.append(&GraphWALEntry::put_relation(writer.next_sequence(), 0, relation)).unwrap();
    
    writer.append(&GraphWALEntry::delete_relation(writer.next_sequence(), 0, rel_id)).unwrap();
    writer.append(&GraphWALEntry::delete_entity(writer.next_sequence(), 0, bob_id)).unwrap();
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 5);
    assert_eq!(entries[0].operation, GraphWALOperation::PutEntity);
    assert_eq!(entries[1].operation, GraphWALOperation::PutEntity);
    assert_eq!(entries[2].operation, GraphWALOperation::PutRelation);
    assert_eq!(entries[3].operation, GraphWALOperation::DeleteRelation);
    assert_eq!(entries[4].operation, GraphWALOperation::DeleteEntity);
}

// ============================================================================
// 5.1.3.2 WAL Read and Recovery Tests
// ============================================================================

#[test]
fn test_wal_read_empty_directory() {
    let dir = tempdir().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert!(entries.is_empty());
}

#[test]
fn test_wal_read_sorted_by_sequence() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Write entries with specific sequences
    for i in 0..50 {
        let entity = create_entity(&format!("Person-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    // Verify entries are sorted by sequence
    for i in 1..entries.len() {
        assert!(
            entries[i].sequence > entries[i - 1].sequence,
            "Entries should be sorted by sequence"
        );
    }
}

#[test]
fn test_wal_get_max_sequence() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let mut max_written_seq = 0;
    for i in 0..30 {
        let entity = create_entity(&format!("Person-{}", i));
        let seq = writer.next_sequence();
        max_written_seq = max_written_seq.max(seq);
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let max_seq = reader.get_max_sequence().unwrap();
    
    assert_eq!(max_seq, max_written_seq);
}

#[test]
fn test_wal_replay_basic() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let entity_names: Vec<String> = (0..10)
        .map(|i| format!("Person-{}", i))
        .collect();
    
    for name in &entity_names {
        let entity = create_entity(name);
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    // Replay and collect entity names
    let mut replayed_names = Vec::new();
    let reader = GraphWALReader::new(dir.path());
    
    let result = reader.replay(|entry| {
        if let Some(ref entity) = entry.entity {
            replayed_names.push(entity.name.clone());
        }
        Ok(())
    }).unwrap();
    
    assert_eq!(result.entries_replayed, 10);
    assert_eq!(result.entries_skipped, 0);
    assert_eq!(replayed_names.len(), 10);
    
    // Verify all names were replayed
    for name in &entity_names {
        assert!(replayed_names.contains(name), "Missing entity: {}", name);
    }
}

#[test]
fn test_wal_replay_with_errors() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    for i in 0..10 {
        let entity = create_entity(&format!("Person-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    // Replay with some failures
    let reader = GraphWALReader::new(dir.path());
    
    let result = reader.replay(|entry| {
        // Fail on even sequences
        if entry.sequence % 2 == 0 {
            Err("Simulated error".to_string())
        } else {
            Ok(())
        }
    }).unwrap();
    
    // Some should succeed, some should fail
    assert!(result.entries_replayed > 0);
    assert!(result.entries_skipped > 0);
    assert_eq!(result.entries_replayed + result.entries_skipped, 10);
}

#[test]
fn test_wal_sequence_recovery_on_restart() {
    let dir = tempdir().unwrap();
    
    // First writer session
    let (last_seq_first_session, max_entry_seq) = {
        let mut writer = create_test_writer(dir.path(), 0);
        let mut max_seq = 0u64;
        
        for i in 0..20 {
            let entity = create_entity(&format!("First-Session-{}", i));
            let seq = writer.next_sequence();
            max_seq = max_seq.max(seq);
            let entry = GraphWALEntry::put_entity(seq, 0, entity);
            writer.append(&entry).unwrap();
        }
        
        let last_seq = writer.current_sequence();
        writer.close().unwrap();
        (last_seq, max_seq)
    };
    
    // Second writer session (simulating restart)
    {
        let mut writer = create_test_writer(dir.path(), 0);
        
        // Sequence should continue from where we left off
        // After recovery, the next sequence should be > max entry sequence from first session
        let first_new_seq = writer.next_sequence();
        assert!(
            first_new_seq > max_entry_seq,
            "New session should continue from last entry sequence: {} > {}",
            first_new_seq, max_entry_seq
        );
        
        // Also verify it's >= the last sequence (which may include uncommitted sequences)
        assert!(
            first_new_seq >= last_seq_first_session,
            "New session should be at or after last session's sequence: {} >= {}",
            first_new_seq, last_seq_first_session
        );
        
        // Write more entries
        for i in 0..10 {
            let entity = create_entity(&format!("Second-Session-{}", i));
            let seq = writer.next_sequence();
            let entry = GraphWALEntry::put_entity(seq, 0, entity);
            writer.append(&entry).unwrap();
        }
        
        writer.close().unwrap();
    }
    
    // Verify all entries
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    

    // Should have 20 + 10 = 30 entries (first_new_seq was used for checking only, not written)
    assert!(entries.len() >= 30, "Expected at least 30 entries, got {}", entries.len());
    
    // Verify sequences are unique
    let mut seen_seqs = std::collections::HashSet::new();
    for entry in &entries {
        assert!(
            seen_seqs.insert(entry.sequence),
            "Duplicate sequence found: {}",
            entry.sequence
        );
    }
}

#[test]
fn test_wal_entry_to_write_op_conversion() {
    // Test PutEntity conversion
    let entity = create_entity("Test");
    let entry = GraphWALEntry::put_entity(1, 0, entity.clone());
    let op = entry.to_write_op().unwrap();
    match op {
        GraphWriteOp::PutEntity(e) => assert_eq!(e.id, entity.id),
        _ => panic!("Expected PutEntity"),
    }
    
    // Test PutRelation conversion
    let relation = create_relation("a", "b");
    let entry = GraphWALEntry::put_relation(2, 0, relation.clone());
    let op = entry.to_write_op().unwrap();
    match op {
        GraphWriteOp::PutRelation(r) => {
            assert_eq!(r.source, relation.source);
            assert_eq!(r.target, relation.target);
        }
        _ => panic!("Expected PutRelation"),
    }
    
    // Test DeleteEntity conversion
    let entry = GraphWALEntry::delete_entity(3, 0, "delete-me".to_string());
    let op = entry.to_write_op().unwrap();
    match op {
        GraphWriteOp::DeleteEntity(id) => assert_eq!(id, "delete-me"),
        _ => panic!("Expected DeleteEntity"),
    }
    
    // Test DeleteRelation conversion
    let entry = GraphWALEntry::delete_relation(4, 0, "delete-rel".to_string());
    let op = entry.to_write_op().unwrap();
    match op {
        GraphWriteOp::DeleteRelation(id) => assert_eq!(id, "delete-rel"),
        _ => panic!("Expected DeleteRelation"),
    }
}

// ============================================================================
// 5.1.3.3 WAL Rotation Tests
// ============================================================================

#[test]
fn test_wal_rotation_on_size() {
    let dir = tempdir().unwrap();
    
    // Create config with very small max file size to trigger rotation
    let config = GraphWALConfig {
        path: dir.path().to_path_buf(),
        max_file_size: 1024, // 1KB max
        sync_mode: SyncMode::Batch,
        retention_count: 10,
        buffer_size: 256,
        enabled: true,
    };
    
    let mut writer = create_writer_with_config(config, 0);
    
    // Write enough entries to trigger rotation
    for i in 0..100 {
        let entity = create_entity(&format!("Person-with-long-name-for-rotation-test-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    // Check multiple WAL files were created
    let reader = GraphWALReader::new(dir.path());
    let files = reader.list_files().unwrap();
    
    assert!(files.len() > 1, "Should have multiple WAL files after rotation");
    
    // Verify all entries can be read across files
    let entries = reader.read_all().unwrap();
    assert_eq!(entries.len(), 100);
}

#[test]
fn test_wal_files_sorted_by_sequence() {
    let dir = tempdir().unwrap();
    
    // Small file size for multiple rotations
    let config = GraphWALConfig {
        path: dir.path().to_path_buf(),
        max_file_size: 512,
        sync_mode: SyncMode::Batch,
        retention_count: 10,
        buffer_size: 128,
        enabled: true,
    };
    
    let mut writer = create_writer_with_config(config, 0);
    
    for i in 0..50 {
        let entity = create_entity(&format!("Rotation-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let files = reader.list_files().unwrap();
    
    // Files should be sorted by sequence (filename contains sequence)
    for i in 1..files.len() {
        let seq_prev = files[i - 1].file_stem()
            .and_then(|s| s.to_str())
            .and_then(|s| s.split('_').last())
            .and_then(|s| s.parse::<u64>().ok())
            .unwrap_or(0);
        let seq_curr = files[i].file_stem()
            .and_then(|s| s.to_str())
            .and_then(|s| s.split('_').last())
            .and_then(|s| s.parse::<u64>().ok())
            .unwrap_or(0);
        
        assert!(seq_curr > seq_prev, "Files should be sorted by sequence");
    }
}

#[test]
fn test_wal_multiple_shards() {
    let dir = tempdir().unwrap();
    
    // Write to multiple shards
    for shard_id in 0..4 {
        let mut writer = create_test_writer(dir.path(), shard_id);
        
        for i in 0..10 {
            let entity = create_entity(&format!("Shard{}-Person-{}", shard_id, i));
            let seq = writer.next_sequence();
            let entry = GraphWALEntry::put_entity(seq, shard_id, entity);
            writer.append(&entry).unwrap();
        }
        
        writer.close().unwrap();
    }
    
    // Read all files
    let reader = GraphWALReader::new(dir.path());
    let all_files = reader.list_files().unwrap();
    assert_eq!(all_files.len(), 4); // One file per shard
    
    // Read only shard 2
    let shard2_reader = GraphWALReader::new(dir.path()).for_shard(2);
    let shard2_files = shard2_reader.list_files().unwrap();
    assert_eq!(shard2_files.len(), 1);
    
    let shard2_entries = shard2_reader.read_all().unwrap();
    assert_eq!(shard2_entries.len(), 10);
    for entry in &shard2_entries {
        assert_eq!(entry.shard_id, 2);
    }
}

#[test]
fn test_wal_manual_rotation() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Write some entries
    for i in 0..5 {
        let entity = create_entity(&format!("Before-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    // Manual rotation
    writer.rotate().unwrap();
    
    // Write more entries
    for i in 0..5 {
        let entity = create_entity(&format!("After-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    // Should have 2 files (original + after rotation)
    let reader = GraphWALReader::new(dir.path());
    let files = reader.list_files().unwrap();
    assert_eq!(files.len(), 2);
    
    // All entries should be readable
    let entries = reader.read_all().unwrap();
    assert_eq!(entries.len(), 10);
}

// ============================================================================
// 5.1.3.4 Corrupted WAL Handling Tests
// ============================================================================

#[test]
fn test_wal_skip_corrupted_checksum() {
    let dir = tempdir().unwrap();
    
    // Write valid entries
    {
        let mut writer = create_test_writer(dir.path(), 0);
        
        for i in 0..5 {
            let entity = create_entity(&format!("Valid-{}", i));
            let seq = writer.next_sequence();
            let entry = GraphWALEntry::put_entity(seq, 0, entity);
            writer.append(&entry).unwrap();
        }
        
        writer.close().unwrap();
    }
    
    // Corrupt one entry by modifying the file
    let reader = GraphWALReader::new(dir.path());
    let files = reader.list_files().unwrap();
    assert!(!files.is_empty());
    
    let file_content = fs::read_to_string(&files[0]).unwrap();
    let lines: Vec<&str> = file_content.lines().collect();
    
    // Modify a data line (not header/footer)
    let mut corrupted_lines = Vec::new();
    for (i, line) in lines.iter().enumerate() {
        if i == 2 {
            // Corrupt the checksum field
            let corrupted = line.replace("checksum\":", "checksum\":99999999");
            corrupted_lines.push(corrupted);
        } else {
            corrupted_lines.push(line.to_string());
        }
    }
    
    fs::write(&files[0], corrupted_lines.join("\n")).unwrap();
    
    // Reader should skip corrupted entries
    let entries = reader.read_all().unwrap();
    
    // Should have fewer entries due to corruption (or at least not crash)
    // The exact count depends on which line was corrupted
    assert!(entries.len() < 5 || entries.iter().all(|e| e.verify_checksum()));
}

#[test]
fn test_wal_handle_empty_lines() {
    let dir = tempdir().unwrap();
    
    // Write valid entries
    {
        let mut writer = create_test_writer(dir.path(), 0);
        
        for i in 0..3 {
            let entity = create_entity(&format!("Entity-{}", i));
            let seq = writer.next_sequence();
            let entry = GraphWALEntry::put_entity(seq, 0, entity);
            writer.append(&entry).unwrap();
        }
        
        writer.close().unwrap();
    }
    
    // Add empty lines to the file
    let reader = GraphWALReader::new(dir.path());
    let files = reader.list_files().unwrap();
    
    let file_content = fs::read_to_string(&files[0]).unwrap();
    let content_with_empty_lines = file_content.replace("\n", "\n\n");
    fs::write(&files[0], content_with_empty_lines).unwrap();
    
    // Reader should handle empty lines gracefully
    let entries = reader.read_all().unwrap();
    assert_eq!(entries.len(), 3);
}

#[test]
fn test_wal_handle_invalid_json() {
    let dir = tempdir().unwrap();
    
    // Create a WAL file with some invalid JSON
    let wal_path = dir.path().join("graph_0_0000000000000000.gwal");
    
    let header = GraphWALHeader::new(0, 0);
    let header_json = serde_json::to_string(&header).unwrap();
    
    let entity = create_entity("Valid");
    let entry = GraphWALEntry::put_entity(1, 0, entity);
    let entry_json = serde_json::to_string(&entry).unwrap();
    
    let content = format!(
        "{}\n{}\nthis is not valid json\n{}\n",
        header_json,
        entry_json,
        entry_json.replace("sequence\":1", "sequence\":2")
    );
    
    fs::write(&wal_path, content).unwrap();
    
    // Reader should skip invalid lines
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    // Should have at least the valid entries
    assert!(!entries.is_empty());
}

#[test]
fn test_wal_header_validation() {
    // Valid header
    let header = GraphWALHeader::new(0, 0);
    assert!(header.validate().is_ok());
    
    // Invalid magic
    let mut bad_header = header.clone();
    bad_header.magic = [0, 0, 0, 0];
    assert!(bad_header.validate().is_err());
    
    // Unsupported version (future version)
    let mut future_header = header.clone();
    future_header.version = 999;
    assert!(future_header.validate().is_err());
}

#[test]
fn test_wal_replay_handles_missing_data() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Write a valid entry first
    let entity = create_entity("Valid");
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::put_entity(seq, 0, entity);
    writer.append(&entry).unwrap();
    writer.close().unwrap();
    
    // Test that to_write_op returns None for incomplete entries
    let incomplete_entry = GraphWALEntry {
        sequence: 999,
        operation: GraphWALOperation::PutEntity,
        shard_id: 0,
        timestamp: 1234567890,
        entity: None, // Missing entity data!
        relation: None,
        delete_id: None,
        checksum: 0,
    };
    
    // to_write_op should return None when entity is missing for PutEntity
    assert!(incomplete_entry.to_write_op().is_none(), "Should return None for missing entity data");
    
    // Verify replay works with valid entries
    let reader = GraphWALReader::new(dir.path());
    let result = reader.replay(|entry| {
        match entry.to_write_op() {
            Some(_) => Ok(()),
            None => Err("Missing data".to_string()),
        }
    }).unwrap();
    
    // The valid entry should be replayed
    assert_eq!(result.entries_replayed, 1);
}

// ============================================================================
// Additional WAL Tests
// ============================================================================

#[test]
fn test_wal_disabled() {
    let config = GraphWALConfig::disabled();
    let mut writer = create_writer_with_config(config, 0);
    
    assert!(!writer.is_enabled());
    
    // Operations should succeed but do nothing
    let entity = create_entity("Test");
    let entry = GraphWALEntry::put_entity(1, 0, entity);
    let seq = writer.append(&entry).unwrap();
    assert_eq!(seq, 1);
    
    // Sync should succeed
    writer.sync().unwrap();
    writer.close().unwrap();
}

#[test]
fn test_wal_timestamp() {
    let entity = create_entity("Test");
    let entry = GraphWALEntry::put_entity(1, 0, entity);
    
    // Timestamp should be recent (within last 10 seconds)
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_millis() as i64;
    
    assert!(entry.timestamp > 0);
    assert!(entry.timestamp <= now);
    assert!(now - entry.timestamp < 10_000); // Within 10 seconds
}

#[test]
fn test_wal_unicode_content() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Test various unicode content
    let unicode_names = vec![
        "用户张三",
        "пользователь",
        "ユーザー太郎",
        "사용자철수",
        "المستخدم",
        "emoji 😀🎉🚀",
    ];
    
    for name in &unicode_names {
        let entity = create_entity(name);
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    // Verify unicode content is preserved
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), unicode_names.len());
    
    let stored_names: HashSet<String> = entries
        .iter()
        .filter_map(|e| e.entity.as_ref().map(|ent| ent.name.clone()))
        .collect();
    
    for name in &unicode_names {
        assert!(stored_names.contains(*name), "Missing unicode name: {}", name);
    }
}

#[test]
fn test_wal_large_entity() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    // Create entity with large name (simpler approach for large data test)
    let large_name = "Large-Entity-".to_string() + &"x".repeat(1000);
    let entity = create_entity(&large_name);
    
    let seq = writer.next_sequence();
    let entry = GraphWALEntry::put_entity(seq, 0, entity.clone());
    
    // Verify checksum is valid before writing
    assert!(entry.verify_checksum(), "Entry checksum should be valid");
    
    writer.append(&entry).unwrap();
    writer.close().unwrap();
    
    // Verify large entity is preserved
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), 1);
    let stored_entity = entries[0].entity.as_ref().unwrap();
    assert_eq!(stored_entity.id, entity.id);
    assert_eq!(stored_entity.name, entity.name);
}

#[test]
fn test_wal_different_entity_types() {
    let dir = tempdir().unwrap();
    let mut writer = create_test_writer(dir.path(), 0);
    
    let entity_types_to_test = vec![
        entity_types::PERSON,
        entity_types::CONCEPT,
        entity_types::EVENT,
        entity_types::LOCATION,
        entity_types::TAG,
    ];
    
    for entity_type in &entity_types_to_test {
        let entity = create_entity_with_type(&format!("{}-instance", entity_type), entity_type);
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let entries = reader.read_all().unwrap();
    
    assert_eq!(entries.len(), entity_types_to_test.len());
    
    let stored_types: HashSet<String> = entries
        .iter()
        .filter_map(|e| e.entity.as_ref().map(|ent| ent.entity_type.clone()))
        .collect();
    
    for et in &entity_types_to_test {
        assert!(stored_types.contains(*et), "Missing entity type: {}", et);
    }
}

#[test]
fn test_wal_files_recovery_result() {
    let dir = tempdir().unwrap();
    
    // Create multiple files via rotation
    let config = GraphWALConfig {
        path: dir.path().to_path_buf(),
        max_file_size: 512,
        sync_mode: SyncMode::Batch,
        retention_count: 10,
        buffer_size: 128,
        enabled: true,
    };
    
    let mut writer = create_writer_with_config(config, 0);
    
    for i in 0..30 {
        let entity = create_entity(&format!("Recovery-{}", i));
        let seq = writer.next_sequence();
        let entry = GraphWALEntry::put_entity(seq, 0, entity);
        writer.append(&entry).unwrap();
    }
    
    writer.close().unwrap();
    
    let reader = GraphWALReader::new(dir.path());
    let result = reader.replay(|_| Ok(())).unwrap();
    
    assert_eq!(result.entries_replayed, 30);
    assert!(result.files_processed > 0);
    assert!(result.last_sequence > 0);
}

// ============================================================================
// Phase-96: WAL Tombstone Replay Regression Tests
// ============================================================================

/// Phase-96: Verify tombstone-aware replay skips PutEntity for deleted entities.
///
/// Scenario: put(A) → put(A, type change) → delete(A)
/// Expected: During replay, PutEntity entries for A are skipped (tombstoned).
#[test]
fn test_wal_tombstone_replay_put_then_delete() {
    let dir = tempdir().unwrap();
    let metrics = Arc::new(WriteMetrics::new());
    
    // Write WAL entries: put, put (type change), delete
    {
        let config = GraphWALConfig::new(dir.path());
        let mut writer = GraphWALWriter::new(config, 0, metrics).unwrap();
        
        // Put entity with initial type
        let entity_v1 = Entity::new("Node1", "tree_section");
        let seq1 = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq1, 0, entity_v1.clone())).unwrap();
        
        // Put entity with changed type (demote)
        let mut entity_v2 = entity_v1.clone();
        entity_v2.entity_type = "tree_section_shadow".to_string();
        let seq2 = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq2, 0, entity_v2.clone())).unwrap();
        
        // Delete entity
        let seq3 = writer.next_sequence();
        writer.append(&GraphWALEntry::delete_entity(seq3, 0, entity_v1.id.clone())).unwrap();
        
        writer.close().unwrap();
    }
    
    // Replay with tombstone awareness
    let reader = GraphWALReader::new(dir.path()).for_shard(0);
    let mut replayed_ops: Vec<(GraphWALOperation, Option<String>)> = Vec::new();
    
    let result = reader.replay(|entry| {
        let id = match entry.operation {
            GraphWALOperation::PutEntity => entry.entity.as_ref().map(|e| e.id.clone()),
            GraphWALOperation::DeleteEntity => entry.delete_id.clone(),
            _ => None,
        };
        replayed_ops.push((entry.operation, id));
        Ok(())
    }).unwrap();
    
    // Both PutEntity entries should be skipped (tombstoned)
    // Only the DeleteEntity should be replayed
    assert_eq!(result.tombstoned_count, 1, "Should detect 1 tombstoned entity");
    assert_eq!(result.entries_skipped, 2, "Should skip 2 PutEntity entries for tombstoned entity");
    assert_eq!(result.entries_replayed, 1, "Should only replay the DeleteEntity");
    
    // Verify the only replayed entry is DeleteEntity
    assert_eq!(replayed_ops.len(), 1);
    assert_eq!(replayed_ops[0].0, GraphWALOperation::DeleteEntity);
}

/// Phase-96: Verify tombstone does NOT affect non-deleted entities.
///
/// Scenario: put(A) → put(B) → delete(A) → put(B, update)
/// Expected: A's puts are skipped, but B's puts are replayed normally.
#[test]
fn test_wal_tombstone_selective() {
    let dir = tempdir().unwrap();
    let metrics = Arc::new(WriteMetrics::new());
    
    {
        let config = GraphWALConfig::new(dir.path());
        let mut writer = GraphWALWriter::new(config, 0, metrics).unwrap();
        
        let entity_a = Entity::new("DeleteMe", entity_types::PERSON);
        let entity_b = Entity::new("KeepMe", entity_types::PERSON);
        let entity_a_id = entity_a.id.clone();
        
        // Put A
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_a)).unwrap();
        
        // Put B
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_b.clone())).unwrap();
        
        // Delete A
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::delete_entity(seq, 0, entity_a_id)).unwrap();
        
        // Update B (new name)
        let mut entity_b_v2 = entity_b.clone();
        entity_b_v2.name = "KeepMe Updated".to_string();
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_b_v2)).unwrap();
        
        writer.close().unwrap();
    }
    
    let reader = GraphWALReader::new(dir.path()).for_shard(0);
    let mut replayed_entities: Vec<String> = Vec::new();
    
    let result = reader.replay(|entry| {
        if let Some(ref entity) = entry.entity {
            replayed_entities.push(entity.name.clone());
        }
        Ok(())
    }).unwrap();
    
    // A's PutEntity should be skipped, B's both PutEntity entries should be replayed
    assert_eq!(result.tombstoned_count, 1);
    assert_eq!(result.entries_replayed, 3, "2 puts for B + 1 delete for A");
    assert_eq!(result.entries_skipped, 1, "1 put for A skipped");
    
    // Only B's entities should be in the replayed list
    assert_eq!(replayed_entities.len(), 2); // "KeepMe" and "KeepMe Updated"
    assert!(replayed_entities.iter().all(|n| n.starts_with("KeepMe")));
}

/// Phase-96: Verify no false tombstones — entity deleted then re-created.
///
/// Scenario: put(A) → delete(A) → put(A again, new data)
/// Expected: A should exist after replay (the last operation is PutEntity).
#[test]
fn test_wal_tombstone_recreate_after_delete() {
    let dir = tempdir().unwrap();
    let metrics = Arc::new(WriteMetrics::new());
    
    {
        let config = GraphWALConfig::new(dir.path());
        let mut writer = GraphWALWriter::new(config, 0, metrics).unwrap();
        
        let entity_v1 = Entity::new("OriginalName", entity_types::PERSON);
        let entity_id = entity_v1.id.clone();
        
        // Put, then delete
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_v1)).unwrap();
        
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::delete_entity(seq, 0, entity_id.clone())).unwrap();
        
        // Re-create with same ID (but new name)
        let mut entity_v2 = Entity::new("RecreatedName", entity_types::PERSON);
        entity_v2.id = entity_id;
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_v2)).unwrap();
        
        writer.close().unwrap();
    }
    
    let reader = GraphWALReader::new(dir.path()).for_shard(0);
    let mut final_entities: std::collections::HashMap<String, String> = std::collections::HashMap::new();
    
    let result = reader.replay(|entry| {
        match entry.operation {
            GraphWALOperation::PutEntity => {
                if let Some(ref e) = entry.entity {
                    final_entities.insert(e.id.clone(), e.name.clone());
                }
            }
            GraphWALOperation::DeleteEntity => {
                if let Some(ref id) = entry.delete_id {
                    final_entities.remove(id);
                }
            }
            _ => {}
        }
        Ok(())
    }).unwrap();
    
    // The last op for this entity is PutEntity, so it should NOT be tombstoned
    assert_eq!(result.tombstoned_count, 0, "Re-created entity should not be tombstoned");
    assert_eq!(result.entries_replayed, 3, "All 3 entries should be replayed");
    assert_eq!(final_entities.len(), 1);
    assert_eq!(final_entities.values().next().unwrap(), "RecreatedName");
}

/// Phase-96: Verify relation tombstone works correctly.
#[test]
fn test_wal_tombstone_relation_delete() {
    let dir = tempdir().unwrap();
    let metrics = Arc::new(WriteMetrics::new());
    
    {
        let config = GraphWALConfig::new(dir.path());
        let mut writer = GraphWALWriter::new(config, 0, metrics).unwrap();
        
        let entity_a = Entity::new("A", entity_types::PERSON);
        let entity_b = Entity::new("B", entity_types::PERSON);
        let rel = Relation::new(&entity_a.id, &entity_b.id, relation_types::FRIEND);
        let rel_id = rel.id.clone();
        
        // Put entities (should not be tombstoned)
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_a)).unwrap();
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_entity(seq, 0, entity_b)).unwrap();
        
        // Put relation then delete it
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::put_relation(seq, 0, rel)).unwrap();
        let seq = writer.next_sequence();
        writer.append(&GraphWALEntry::delete_relation(seq, 0, rel_id)).unwrap();
        
        writer.close().unwrap();
    }
    
    let reader = GraphWALReader::new(dir.path()).for_shard(0);
    let mut op_types: Vec<GraphWALOperation> = Vec::new();
    
    let result = reader.replay(|entry| {
        op_types.push(entry.operation);
        Ok(())
    }).unwrap();
    
    // Relation's PutRelation should be skipped, DeleteRelation should be replayed
    // Both PutEntity entries should be replayed (entities are not deleted)
    assert_eq!(result.tombstoned_count, 1, "1 tombstoned relation");
    assert_eq!(result.entries_replayed, 3, "2 PutEntity + 1 DeleteRelation");
    assert_eq!(result.entries_skipped, 1, "1 PutRelation skipped");
    
    assert!(op_types.contains(&GraphWALOperation::PutEntity));
    assert!(op_types.contains(&GraphWALOperation::DeleteRelation));
    assert!(!op_types.contains(&GraphWALOperation::PutRelation));
}