#![allow(
    clippy::too_many_lines,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::missing_const_for_fn
)]

use std::{fs, time::Duration};

use tidaldb::{
    replication::ShardId,
    wal::{
        SignalEvent, WalConfig, WalHandle,
        format::{self, EventRecord, HEADER_SIZE},
        reader, segment,
    },
};

fn test_config(dir: &std::path::Path) -> WalConfig {
    WalConfig {
        wal_dir_override: None,
        dir: dir.to_path_buf(),
        segment_size: 16 * 1024 * 1024,
        batch_size: 100,
        batch_timeout: Duration::from_millis(1),
        dedup_window: Duration::from_secs(30),
    }
}

fn make_event(id: u64) -> SignalEvent {
    SignalEvent {
        entity_id: id,
        signal_type: 1,
        weight: 1.0,
        timestamp_nanos: id * 1_000_000_000,
    }
}

// -- AC-13, AC-14: Crash recovery with torn write
#[test]
fn wal_crash_recovery_torn_write() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    let wal_dir = dir.path().join("wal");
    fs::create_dir_all(&wal_dir).expect("create dir should succeed");

    // Write valid batches directly to simulate a crash mid-write
    let events1: Vec<EventRecord> = (1..=5)
        .map(|i| EventRecord::signal(i, 1, 1.0, i * 1_000_000_000))
        .collect();

    let events2: Vec<EventRecord> = (6..=10)
        .map(|i| EventRecord::signal(i, 1, 1.0, i * 1_000_000_000))
        .collect();

    let batch1 = format::encode_batch(&events1, 1, 1_000_000_000).expect("encode should succeed");
    let batch2 = format::encode_batch(&events2, 6, 6_000_000_000).expect("encode should succeed");

    // Write batch1 fully, then truncate batch2 at various offsets
    for truncate_at in [
        0,
        10,
        32,
        63,
        HEADER_SIZE,
        HEADER_SIZE + 5,
        HEADER_SIZE + 20,
    ] {
        let seg_name = segment::segment_filename(ShardId::SINGLE, 1);
        let seg_path = wal_dir.join(&seg_name);

        let mut data = batch1.clone();
        if truncate_at > 0 {
            data.extend_from_slice(&batch2[..truncate_at.min(batch2.len())]);
        }
        fs::write(&seg_path, &data).expect("write should succeed");

        let recovery = reader::recover(&wal_dir).expect("recovery should succeed");
        assert_eq!(
            recovery.events.len(),
            5,
            "torn write at offset {truncate_at}: should recover 5 events"
        );

        // Clean up for next iteration
        fs::remove_file(&seg_path).expect("cleanup should succeed");
    }
}

// -- AC-15: No phantom records (clean shutdown variant)
#[test]
fn wal_clean_shutdown_no_data_loss() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    let config = test_config(dir.path());

    // Write 5 events
    let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
    for i in 1..=5 {
        handle.append(make_event(i)).expect("append should succeed");
    }
    handle.shutdown().expect("shutdown should succeed");

    // Verify exactly 5 events on replay
    let config = test_config(dir.path());
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
    assert_eq!(
        replayed.len(),
        5,
        "should replay exactly 5 events, not more"
    );

    // No phantom events (events from un-fsynced batches should not appear)
    for event in &replayed {
        assert!(
            event.entity_id >= 1 && event.entity_id <= 5,
            "unexpected entity_id {}",
            event.entity_id
        );
    }
    handle.shutdown().expect("shutdown should succeed");
}

// -- AC-16: Crash at any byte position never produces corrupt state
#[test]
fn wal_crash_at_any_byte_position() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    let wal_dir = dir.path().join("wal");
    fs::create_dir_all(&wal_dir).expect("create dir should succeed");

    let events: Vec<EventRecord> = (1..=3)
        .map(|i| EventRecord::signal(i, 1, 1.0, i * 1_000_000_000))
        .collect();
    let batch = format::encode_batch(&events, 1, 1_000_000_000).expect("encode should succeed");

    // Test truncation at every byte offset
    for truncate_at in 0..=batch.len() {
        let seg_name = segment::segment_filename(ShardId::SINGLE, 1);
        let seg_path = wal_dir.join(&seg_name);

        fs::write(&seg_path, &batch[..truncate_at]).expect("write should succeed");

        let recovery = reader::recover(&wal_dir).expect("recovery should never fail");

        if truncate_at == batch.len() {
            assert_eq!(
                recovery.events.len(),
                3,
                "full batch should recover 3 events"
            );
        } else {
            assert_eq!(
                recovery.events.len(),
                0,
                "truncated at byte {truncate_at}: no events should be recovered"
            );
        }

        // Clean up for next iteration
        fs::remove_file(&seg_path).expect("cleanup should succeed");
    }
}
