//! Epoch supersession cascade tests.
//!
//! Tests for supersession marker writing, transitive cascade,
//! and cycle detection in epoch chains.

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

/// Test: Ingesting an epoch that supersedes another writes a SUPERSEDED marker.
///
/// Setup: Create epochs A and B where B supersedes A
/// Verify: SUPERSEDED:A key exists with value = B's ID
#[tokio::test]
async fn test_cascade_writes_superseded_marker() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    // Create epochs: B supersedes A
    let epoch_a = stemedb_core::types::Epoch {
        id: [1u8; 32],
        name: "Epoch A".to_string(),
        supersedes: None,
        supersession_type: None,
        start_timestamp: 1000,
        end_timestamp: None,
    };
    let epoch_b = testing::test_epoch_with_supersession(
        [2u8; 32],
        "Epoch B",
        [1u8; 32], // B supersedes A
        stemedb_core::types::SupersessionType::Temporal,
    );

    // Write epochs to WAL: A first, then B
    journal.append(serialize_epoch(&epoch_a).expect("ser")).expect("append");
    journal.append(serialize_epoch(&epoch_b).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");

    // Ingest both epochs
    while worker.step().await.expect("step") > 0 {}

    // Verify: SUPERSEDED:A marker exists and points to B
    let marker_key = key_codec::superseded_key(&hex::encode([1u8; 32]));
    let marker_value = store.get(&marker_key).await.expect("get").expect("marker should exist");
    assert_eq!(marker_value.as_slice(), &[2u8; 32], "SUPERSEDED marker should point to epoch B");

    // Verify epochs themselves are stored
    let epoch_a_key = key_codec::epoch_key(&hex::encode([1u8; 32]));
    let epoch_b_key = key_codec::epoch_key(&hex::encode([2u8; 32]));
    assert!(store.get(&epoch_a_key).await.expect("get").is_some(), "Epoch A should be stored");
    assert!(store.get(&epoch_b_key).await.expect("get").is_some(), "Epoch B should be stored");
}

/// Test: Transitive supersession cascade writes markers for all ancestors.
///
/// Setup: C supersedes B, B supersedes A
/// Verify: Both SUPERSEDED:A and SUPERSEDED:B exist, both pointing to C
#[tokio::test]
async fn test_cascade_transitive() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    // Create chain: C -> B -> A
    let epoch_a = stemedb_core::types::Epoch {
        id: [1u8; 32],
        name: "Epoch A".to_string(),
        supersedes: None,
        supersession_type: None,
        start_timestamp: 1000,
        end_timestamp: None,
    };
    let epoch_b = testing::test_epoch_with_supersession(
        [2u8; 32],
        "Epoch B",
        [1u8; 32], // B supersedes A
        stemedb_core::types::SupersessionType::Temporal,
    );
    let epoch_c = testing::test_epoch_with_supersession(
        [3u8; 32],
        "Epoch C",
        [2u8; 32], // C supersedes B
        stemedb_core::types::SupersessionType::Temporal,
    );

    // Ingest in order: A, then B, then C
    journal.append(serialize_epoch(&epoch_a).expect("ser")).expect("append");
    journal.append(serialize_epoch(&epoch_b).expect("ser")).expect("append");
    journal.append(serialize_epoch(&epoch_c).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");

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

    // After C is ingested:
    // - SUPERSEDED:B should point to C (immediate supersession)
    // - SUPERSEDED:A should point to C (transitive, overwriting B->A marker from step 2)
    let marker_a_key = key_codec::superseded_key(&hex::encode([1u8; 32]));
    let marker_b_key = key_codec::superseded_key(&hex::encode([2u8; 32]));

    let marker_a_value =
        store.get(&marker_a_key).await.expect("get").expect("SUPERSEDED:A should exist");
    let marker_b_value =
        store.get(&marker_b_key).await.expect("get").expect("SUPERSEDED:B should exist");

    // Both markers should point to C (the LATEST superseder)
    assert_eq!(
        marker_a_value.as_slice(),
        &[3u8; 32],
        "SUPERSEDED:A should point to C (the latest)"
    );
    assert_eq!(marker_b_value.as_slice(), &[3u8; 32], "SUPERSEDED:B should point to C");

    // Verify no marker for C (C is the head, not superseded)
    let marker_c_key = key_codec::superseded_key(&hex::encode([3u8; 32]));
    let marker_c = store.get(&marker_c_key).await.expect("get");
    assert!(marker_c.is_none(), "C should not have a SUPERSEDED marker");
}

/// Test: Cycle in supersession chain is handled gracefully.
#[tokio::test]
async fn test_cascade_cycle_detection() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    // Create a cycle: A supersedes B, B supersedes A
    // This is pathological but we must not hang
    let epoch_a = stemedb_core::types::Epoch {
        id: [1u8; 32],
        name: "Epoch A".to_string(),
        supersedes: Some([2u8; 32]), // A supersedes B
        supersession_type: Some(stemedb_core::types::SupersessionType::Temporal),
        start_timestamp: 1000,
        end_timestamp: None,
    };
    let epoch_b = stemedb_core::types::Epoch {
        id: [2u8; 32],
        name: "Epoch B".to_string(),
        supersedes: Some([1u8; 32]), // B supersedes A (cycle!)
        supersession_type: Some(stemedb_core::types::SupersessionType::Temporal),
        start_timestamp: 2000,
        end_timestamp: None,
    };

    // Ingest both
    journal.append(serialize_epoch(&epoch_a).expect("ser")).expect("append");
    journal.append(serialize_epoch(&epoch_b).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");

    // This should NOT hang - cycle detection should kick in
    while worker.step().await.expect("step") > 0 {}

    // Verify both epochs are stored (the cycle doesn't break storage)
    let epoch_a_key = key_codec::epoch_key(&hex::encode([1u8; 32]));
    let epoch_b_key = key_codec::epoch_key(&hex::encode([2u8; 32]));
    assert!(store.get(&epoch_a_key).await.expect("get").is_some(), "Epoch A should be stored");
    assert!(store.get(&epoch_b_key).await.expect("get").is_some(), "Epoch B should be stored");

    // At least one SUPERSEDED marker should exist
    let marker_a =
        store.get(&key_codec::superseded_key(&hex::encode([1u8; 32]))).await.expect("get");
    let marker_b =
        store.get(&key_codec::superseded_key(&hex::encode([2u8; 32]))).await.expect("get");

    assert!(
        marker_a.is_some() || marker_b.is_some(),
        "At least one SUPERSEDED marker should exist"
    );
}
