//! Crash recovery integration tests.
//!
//! These tests verify the full pipeline survives crashes:
//! 1. Write to WAL
//! 2. "Crash" (drop all handles)
//! 3. Reopen WAL and KV store
//! 4. Run ingestor
//! 5. Verify data is present

use super::*;
use stemedb_storage::key_codec;

/// Test: A single assertion written to the WAL survives a crash.
#[tokio::test]
async fn test_crash_recovery_assertion_survives() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    // Phase 1: Write assertion to WAL and "crash" (drop handles)
    {
        let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
        let assertion = create_test_assertion();
        journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");
        // Crash - drop journal
    }

    // Phase 2: Recovery - reopen everything and run ingestor
    {
        let journal = Journal::open(&wal_dir).expect("Failed to reopen journal");
        let store = HybridStore::open(&db_dir).expect("Failed to open store");

        let journal = Arc::new(Mutex::new(journal));
        let store = Arc::new(store);
        let mut worker =
            IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

        // Process all pending records
        loop {
            let bytes = worker.step().await.expect("Failed to step");
            if bytes == 0 {
                break;
            }
        }

        // Verify assertion was recovered and ingested
        let count_key = key_codec::assertion_count_key();
        let count_bytes = store.get(&count_key).await.expect("get").expect("count exists");
        let count = u64::from_le_bytes(count_bytes.try_into().expect("8 bytes"));
        assert_eq!(count, 1, "Assertion should survive crash");

        // Verify subject index was created
        let subj_key = key_codec::subjects_index_key("Tesla_Inc");
        let entry = store.get(&subj_key).await.expect("get");
        assert!(entry.is_some(), "Subject index should be created");
    }
}

/// Test: Full pipeline crash recovery with all record types.
///
/// Assertions, Votes, and Epochs all survive crash and are ingested correctly.
#[tokio::test]
async fn test_crash_recovery_all_record_types() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    // Create assertion and compute its hash so the vote can reference it
    let assertion = create_test_assertion();
    let assertion_payload = serialize_assertion(&assertion).expect("ser");
    let data_portion = &assertion_payload[8..]; // skip record header
    let assertion_hash: [u8; 32] = *blake3::hash(data_portion).as_bytes();

    let vote = Vote {
        assertion_hash,
        agent_id: [2u8; 32],
        weight: 0.8,
        signature: [3u8; 64],
        timestamp: 2000,
        source_url: None,
        observed_context: None,
    };
    let epoch = create_test_epoch();

    // Phase 1: Write all record types and "crash"
    {
        let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
        journal.append(assertion_payload).expect("append");
        journal.append(serialize_vote(&vote).expect("ser")).expect("append");
        journal.append(serialize_epoch(&epoch).expect("ser")).expect("append");
        // Crash
    }

    // Phase 2: Recovery
    {
        let journal = Journal::open(&wal_dir).expect("Failed to reopen journal");
        let store = HybridStore::open(&db_dir).expect("Failed to open store");

        let journal = Arc::new(Mutex::new(journal));
        let store = Arc::new(store);
        let mut worker =
            IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

        // Ingest all records
        while worker.step().await.expect("step") > 0 {}

        // Verify assertion survived
        let count_key = key_codec::assertion_count_key();
        let count_bytes = store.get(&count_key).await.expect("get").expect("count");
        let count = u64::from_le_bytes(count_bytes.try_into().expect("8 bytes"));
        assert_eq!(count, 1, "Assertion should survive crash");

        // Verify vote survived (scan by subject + assertion hash)
        let vote_prefix = key_codec::vote_scan_prefix("Tesla_Inc", &hex::encode(assertion_hash));
        let votes = store.scan_prefix(&vote_prefix).await.expect("scan");
        assert_eq!(votes.len(), 1, "Vote should survive crash");

        // Verify epoch survived
        let epoch_key = key_codec::epoch_key(&hex::encode(epoch.id));
        let epoch_entry = store.get(&epoch_key).await.expect("get");
        assert!(epoch_entry.is_some(), "Epoch should survive crash");
    }
}

/// Test: Multiple crash-recovery cycles maintain data integrity.
///
/// Simulates a flaky system that crashes and recovers multiple times,
/// each time adding more data.
#[tokio::test]
async fn test_repeated_crash_recovery_cycles() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let num_cycles = 3;

    for cycle in 0..num_cycles {
        // Write new data and crash
        {
            let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
            let assertion =
                create_signed_assertion(&format!("Subject_Cycle_{}", cycle), "has_property");
            journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");
        }

        // Recover and ingest
        {
            let journal = Journal::open(&wal_dir).expect("Failed to reopen journal");
            let store = HybridStore::open(&db_dir).expect("Failed to open store");

            let journal = Arc::new(Mutex::new(journal));
            let store = Arc::new(store);
            let mut worker =
                IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

            while worker.step().await.expect("step") > 0 {}

            // Verify we have assertions from all cycles so far
            let count_key = key_codec::assertion_count_key();
            let count_bytes = store.get(&count_key).await.expect("get").expect("count");
            let count = u64::from_le_bytes(count_bytes.try_into().expect("8 bytes"));
            assert_eq!(
                count,
                (cycle + 1) as u64,
                "Should have {} assertions after cycle {}",
                cycle + 1,
                cycle
            );
        }
    }

    // Final verification: all data from all cycles present
    {
        let store = HybridStore::open(&db_dir).expect("Failed to open store");
        let count_key = key_codec::assertion_count_key();
        let count_bytes = store.get(&count_key).await.expect("get").expect("count");
        let count = u64::from_le_bytes(count_bytes.try_into().expect("8 bytes"));
        assert_eq!(count, num_cycles as u64, "All assertions should survive multiple crashes");
    }
}

/// Test: KV store persists across restarts.
///
/// Verifies that once data is ingested to storage, it survives store restarts.
#[tokio::test]
async fn test_kv_store_persistence() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    // Phase 1: Write, ingest, and close everything
    {
        let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
        let store = HybridStore::open(&db_dir).expect("Failed to open store");

        let assertion = create_test_assertion();
        journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

        let journal = Arc::new(Mutex::new(journal));
        let store = Arc::new(store);
        let mut worker =
            IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

        while worker.step().await.expect("step") > 0 {}

        // Flush to ensure persistence
        store.flush().await.expect("Failed to flush");
    }

    // Phase 2: Reopen only the KV store and verify data persists
    {
        let store = HybridStore::open(&db_dir).expect("Failed to reopen store");
        let count_key = key_codec::assertion_count_key();
        let count_bytes = store.get(&count_key).await.expect("get").expect("count");
        let count = u64::from_le_bytes(count_bytes.try_into().expect("8 bytes"));
        assert_eq!(count, 1, "Assertion should persist in KV store across restarts");
    }
}
