//! Migration tests (Design Invariant 8): open + migrate fixture databases
//! frozen from every released schema version. `fixtures/v1.db` was generated
//! at v0.1.0 (deterministic content touching every table; see git history for
//! the generator) and must stay openable by every future release.
//!
//! When SCHEMA_VERSION grows: add the migration, freeze a new fixture, and
//! add a case here — never edit an existing fixture.

use std::sync::Arc;

use grit_core::{Budget, Grit, ManualClock, Options, Query, Traversal};
use uuid::Uuid;

fn open_fixture_copy(dir: &tempfile::TempDir, name: &str) -> Grit {
    // Fixtures are immutable artifacts; work on a copy (opening creates WAL
    // sidecars and a future version would migrate in place).
    let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name);
    let dst = dir.path().join(name);
    std::fs::copy(&src, &dst).unwrap();
    Grit::open(
        &dst,
        Options::new("migration-test").clock(Arc::new(ManualClock::new(2_000_000))),
    )
    .unwrap()
}

#[test]
fn v1_fixture_opens_and_reads() {
    let dir = tempfile::tempdir().unwrap();
    let g = open_fixture_copy(&dir, "v1.db");

    // Exact counts frozen with the fixture.
    let stats = g.stats().unwrap();
    assert_eq!(
        (
            stats.nodes,
            stats.edges,
            stats.episodes,
            stats.mentions,
            stats.oplog,
            stats.purged
        ),
        (5, 2, 1, 2, 12, 1)
    );

    // The graph is queryable: FTS, traversal, provenance, bi-temporal state.
    let hits = g
        .search(Query::text("fixtures").budget(Budget::items(5)))
        .unwrap();
    assert!(!hits.is_empty(), "episode content must be FTS-searchable");

    let n0 = Uuid::from_u128(1);
    let sub = g.traverse(&[n0], &Traversal::default().depth(1)).unwrap();
    assert_eq!(sub.edges.len(), 1, "n0 -R-> n1 must survive");
    assert_eq!(g.mentions_of(n0).unwrap().len(), 1);

    // Merge audit pointer and purge tombstone survived.
    let merged = g.node(Uuid::from_u128(6)).unwrap().unwrap();
    assert_eq!(merged.merged_into, Some(Uuid::from_u128(5)));
    assert!(
        g.node(Uuid::from_u128(4)).unwrap().is_none(),
        "purged node stays gone"
    );

    // The invalidated edge is belief-versioned: gone now, present before the
    // invalidation was recorded.
    let e2 = Uuid::from_u128(0x101);
    let edge = g.edge(e2).unwrap().unwrap();
    assert_eq!(edge.invalid_at, Some(1_000_500));

    // The frozen file reports the current schema (v1 == latest today; when
    // v2 lands this assertion moves to "migrated up to SCHEMA_VERSION").
    assert_eq!(grit_core::SCHEMA_VERSION, 1);

    // And new writes still work post-open.
    g.apply(grit_core::GraphOp::AddNode {
        id: g.new_id(),
        kind: "k".into(),
        name: "post-migration write".into(),
        summary: String::new(),
        attrs: serde_json::json!({}),
        group_id: String::new(),
    })
    .unwrap();
    assert_eq!(g.stats().unwrap().nodes, 6);
}
