//! bl-015 — durable commit-layer snapshots + incremental recovery.
//!
//! Proves the snapshot mechanism: an offline verifier reads a historical
//! `state_root` straight from the snapshot file without replaying the oplog,
//! and a restart that loads the snapshot + replays only later ops yields the
//! exact same state root as a full replay.

use ed25519_dalek::SigningKey;
use ontoref_core::{Snapshot, Substrate};
use ontoref_types::{
    operation::{OpBody, OpPayload, Operation},
    AttrId, EntityId, Hlc, PublicKey, Value,
};

fn op(counter: u64, key: &SigningKey) -> Operation {
    Operation::sign(
        OpBody {
            actor: PublicKey::from_signing_key(key),
            parents: vec![],
            payload: OpPayload::EntityAssert {
                attrs: vec![(AttrId::new("v"), Value::Int(counter as i64))],
                entity: EntityId::new(format!("e{counter}")),
                valid_from: None,
            },
            timestamp: Hlc::new(counter + 1, 0),
        },
        key,
    )
}

#[test]
fn snapshot_enables_incremental_recovery() {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("substrate");
    let snapshot_file = path.join("commit-snapshot.bin");
    let key = SigningKey::from_bytes(&[42u8; 32]);

    // Apply 3 ops, then snapshot.
    let root_at_snapshot = {
        let mut s = Substrate::open(&path).expect("open");
        for i in 0..3 {
            s.apply(&op(i, &key)).expect("apply");
        }
        s.write_snapshot().expect("write snapshot");
        s.state_root()
    };

    // The snapshot file alone yields the historical state root — no oplog
    // replay required (the offline-verification success criterion).
    let snap = Snapshot::load(&snapshot_file)
        .expect("load")
        .expect("snapshot present");
    assert_eq!(
        snap.state_root, root_at_snapshot,
        "snapshot records the state root for direct offline verification"
    );
    assert!(
        snap.boundary.is_some(),
        "boundary pins the replay resume point"
    );

    // Reopen via the snapshot reproduces the root with nothing new to replay.
    let root_final = {
        let mut s = Substrate::open(&path).expect("reopen via snapshot");
        assert_eq!(
            s.state_root(),
            root_at_snapshot,
            "snapshot restore reproduces the attested root"
        );
        // Append 2 more ops AFTER the snapshot boundary.
        for i in 3..5 {
            s.apply(&op(i, &key)).expect("apply");
        }
        s.state_root()
    };

    // Reopen: snapshot (ops 0..3) + incremental replay (ops 3..5). Scoped
    // so the redb handle is released before the cross-check reopen.
    {
        let reopened = Substrate::open(&path).expect("reopen incremental");
        assert_eq!(
            reopened.state_root(),
            root_final,
            "snapshot + incremental replay equals the live root"
        );
    }

    // Cross-check: drop the snapshot and full-replay — must agree, proving
    // the incremental path is a faithful shortcut, not a divergent one.
    std::fs::remove_file(&snapshot_file).expect("remove snapshot");
    let full = Substrate::open(&path).expect("reopen full replay");
    assert_eq!(
        full.state_root(),
        root_final,
        "full replay agrees with the snapshot path"
    );
}
