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

use std::{sync::Arc, time::Duration};

use tidaldb::wal::{SignalEvent, WalConfig, WalHandle, checkpoint::CheckpointManager};

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-17, AC-18: Checkpoint and truncation
#[test]
fn wal_checkpoint_and_truncation() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    // Small segments so we get multiple
    let config = WalConfig {
        wal_dir_override: None,
        dir: dir.path().to_path_buf(),
        segment_size: 256,
        batch_size: 5,
        batch_timeout: Duration::from_millis(10),
        dedup_window: Duration::from_secs(30),
    };

    let (handle, _, _) = WalHandle::open(config).expect("open should succeed");

    // Write events
    let mut last_seq = 0;
    for i in 1..=50 {
        let seq = handle.append(make_event(i)).expect("append should succeed");
        if seq > last_seq {
            last_seq = seq;
        }
    }

    // Checkpoint at a mid-point
    let checkpoint_seq = last_seq / 2;
    handle
        .checkpoint(checkpoint_seq)
        .expect("checkpoint should succeed");

    // Verify checkpoint file exists and is correct
    let wal_dir = dir.path().join("wal");
    let cp = CheckpointManager::read(&wal_dir).expect("read should succeed");
    let (seq, _ts) = cp.expect("checkpoint should exist");
    assert_eq!(seq, checkpoint_seq);

    // Truncate segments before checkpoint
    handle
        .truncate_before(checkpoint_seq)
        .expect("truncate should succeed");

    handle.shutdown().expect("shutdown should succeed");

    // Reopen and verify: only events >= checkpoint_seq are replayed
    let config = WalConfig {
        wal_dir_override: None,
        dir: dir.path().to_path_buf(),
        segment_size: 256,
        batch_size: 5,
        batch_timeout: Duration::from_millis(10),
        dedup_window: Duration::from_secs(30),
    };
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
    assert!(
        !replayed.is_empty(),
        "should replay events after checkpoint"
    );
    // All replayed events should have sequence >= checkpoint_seq
    // (we verify this implicitly by checking count)
    handle.shutdown().expect("shutdown should succeed");
}

// -- AC-19: Concurrent writers
#[test]
fn wal_concurrent_writers() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    let config = test_config(dir.path());

    let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
    let handle = Arc::new(handle);

    let num_threads = 8;
    let events_per_thread = 100;

    let mut threads = Vec::new();
    for thread_id in 0..num_threads {
        let handle = Arc::clone(&handle);
        threads.push(std::thread::spawn(move || {
            let mut seqs = Vec::with_capacity(events_per_thread);
            for i in 0..events_per_thread {
                // Each thread uses unique entity_ids to avoid dedup
                let entity_id = thread_id as u64 * events_per_thread as u64 + i as u64;
                let event = SignalEvent {
                    entity_id,
                    signal_type: thread_id as u8,
                    weight: 1.0,
                    timestamp_nanos: entity_id * 1_000,
                };
                let seq = handle.append(event).expect("append should succeed");
                seqs.push(seq);
            }
            seqs
        }));
    }

    let mut all_seqs = Vec::new();
    for thread in threads {
        let seqs = thread.join().expect("thread should join");
        all_seqs.extend(seqs);
    }

    // Shutdown by unwrapping the Arc (only holder now)
    let handle = Arc::try_unwrap(handle).expect("should be sole owner of WalHandle Arc");
    handle.shutdown().expect("shutdown should succeed");

    // Filter out dedup seq=0 (should be none)
    let non_zero: Vec<u64> = all_seqs.iter().copied().filter(|&s| s > 0).collect();
    assert_eq!(
        non_zero.len(),
        num_threads * events_per_thread,
        "all {} events should get unique sequence numbers",
        num_threads * events_per_thread
    );

    // No duplicate sequence numbers
    let mut sorted = non_zero.clone();
    sorted.sort_unstable();
    sorted.dedup();
    assert_eq!(
        sorted.len(),
        non_zero.len(),
        "no duplicate sequence numbers allowed"
    );

    // Verify all checksums valid on replay
    let config = test_config(dir.path());
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
    assert_eq!(
        replayed.len(),
        num_threads * events_per_thread,
        "all events should be present on replay"
    );
    handle.shutdown().expect("shutdown should succeed");
}

// -- AC-4: Sequence numbers survive close/reopen
#[test]
fn wal_close_and_reopen() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");

    let mut last_seq = 0;

    // Session 1: write 10 events
    let config = test_config(dir.path());
    let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
    for i in 1..=10 {
        let seq = handle.append(make_event(i)).expect("append should succeed");
        if seq > last_seq {
            last_seq = seq;
        }
    }
    handle.shutdown().expect("shutdown should succeed");

    // Session 2: write 10 more, verify seqs continue
    let config = test_config(dir.path());
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
    assert_eq!(replayed.len(), 10);

    for i in 11..=20 {
        let seq = handle.append(make_event(i)).expect("append should succeed");
        assert!(seq > last_seq, "seq {seq} should be > last_seq {last_seq}");
        last_seq = seq;
    }
    handle.shutdown().expect("shutdown should succeed");

    // Session 3: verify all 20 events
    let config = test_config(dir.path());
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
    assert_eq!(replayed.len(), 20);
    handle.shutdown().expect("shutdown should succeed");
}

#[test]
fn wal_replay_correctness() {
    let dir = tempfile::tempdir().expect("tempdir creation should succeed");
    let config = test_config(dir.path());

    // Write 100 events
    let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
    let mut seqs = Vec::new();
    for i in 1..=100 {
        let seq = handle.append(make_event(i)).expect("append should succeed");
        seqs.push(seq);
    }

    // Checkpoint at event 50
    let checkpoint_seq = seqs[49]; // seq of the 50th event
    handle
        .checkpoint(checkpoint_seq)
        .expect("checkpoint should succeed");
    handle.shutdown().expect("shutdown should succeed");

    // Reopen and verify: only post-checkpoint events are replayed
    let config = test_config(dir.path());
    let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");

    // Events with seq >= checkpoint_seq should be replayed.
    // The exact count depends on batching, but it should be at least 50
    // (the events after the checkpoint) and at most 100.
    assert!(
        replayed.len() >= 50,
        "expected at least 50 replayed events, got {}",
        replayed.len()
    );
    assert!(
        replayed.len() <= 100,
        "expected at most 100 replayed events, got {}",
        replayed.len()
    );

    handle.shutdown().expect("shutdown should succeed");
}
