//! Distributed correctness tests — Phase B
//!
//! Every test in this file runs the *same* Cypher setup + query against:
//!   1. `PropertyGraph` (standalone single-node backend)
//!   2. `DistributedCluster<NetworKitRustBackend>` (3 partitions, in-process)
//!
//! The two results must be *semantically identical* (same rows, same stats).
//! Row order is normalised before comparison because distributed fan-out does
//! not guarantee ordering.
//!
//! Run with:
//!   cargo test --test test_distributed_correctness --no-default-features \
//!              --features distributed

use std::collections::HashMap;

use ocg::{
    CypherValue, PropertyGraph, QueryResult, execute, execute_with_params,
    distributed::graph::DistributedCluster,
    graph::NetworKitRustBackend,
};

// ─── Test helpers ──────────────────────────────────────────────────────────

type Cluster = DistributedCluster<NetworKitRustBackend>;

/// Execute `setup` then `query` on a fresh `PropertyGraph` and on a fresh
/// `DistributedCluster` (3 partitions), then assert the results are equal.
fn assert_same(setup: &str, query: &str) {
    assert_same_n(setup, query, 3);
}

/// Same as `assert_same` but lets the caller control the number of partitions.
fn assert_same_n(setup: &str, query: &str, num_partitions: u32) {
    // --- standalone ---
    let mut standalone = PropertyGraph::new();
    if !setup.is_empty() {
        execute(&mut standalone, setup)
            .unwrap_or_else(|e| panic!("standalone setup failed for {:?}: {e}", setup));
    }
    let standalone_result = execute(&mut standalone, query)
        .unwrap_or_else(|e| panic!("standalone query failed for {:?}: {e}", query));

    // --- distributed ---
    let mut cluster = Cluster::new(num_partitions);
    if !setup.is_empty() {
        execute(&mut cluster, setup)
            .unwrap_or_else(|e| panic!("distributed setup failed for {:?}: {e}", setup));
    }
    let distributed_result = execute(&mut cluster, query)
        .unwrap_or_else(|e| panic!("distributed query failed for {:?}: {e}", query));

    compare_results(&standalone_result, &distributed_result, query);
}

/// Same as `assert_same` but with named parameters.
fn assert_same_with_params(setup: &str, query: &str, params: HashMap<String, CypherValue>) {
    let mut standalone = PropertyGraph::new();
    if !setup.is_empty() {
        execute(&mut standalone, setup).unwrap();
    }
    let standalone_result =
        execute_with_params(&mut standalone, query, params.clone()).unwrap();

    let mut cluster = Cluster::new(3);
    if !setup.is_empty() {
        execute(&mut cluster, setup).unwrap();
    }
    let distributed_result =
        execute_with_params(&mut cluster, query, params).unwrap();

    compare_results(&standalone_result, &distributed_result, query);
}

/// Normalise and compare two `QueryResult`s.
///
/// Rows are sorted by their debug representation so order differences between
/// standalone and distributed fan-out do not cause false failures.
/// Normalize a CypherValue for comparison: sort elements inside Lists so that
/// label ordering (from HashSet) doesn't cause spurious mismatches.
fn normalize_value(v: &CypherValue) -> String {
    match v {
        CypherValue::List(items) => {
            let mut inner: Vec<String> = items.iter().map(normalize_value).collect();
            inner.sort();
            format!("List([{}])", inner.join(", "))
        }
        CypherValue::Node(n) => {
            let mut labels: Vec<&String> = n.labels.iter().collect();
            labels.sort();
            format!("Node(id={}, labels={labels:?}, props={:?})", n.id, n.properties)
        }
        other => format!("{other:?}"),
    }
}

/// Produce a normalized debug string for a row's values.
fn normalize_row(row: &ocg::Record) -> String {
    let parts: Vec<String> = row.values().iter().map(|v| normalize_value(v)).collect();
    format!("{parts:?}")
}

fn compare_results(standalone: &QueryResult, distributed: &QueryResult, query: &str) {
    assert_eq!(
        standalone.columns,
        distributed.columns,
        "Column names differ for query: {query}"
    );

    let mut s_rows: Vec<String> = standalone
        .rows
        .iter()
        .map(|r| normalize_row(r))
        .collect();
    let mut d_rows: Vec<String> = distributed
        .rows
        .iter()
        .map(|r| normalize_row(r))
        .collect();
    s_rows.sort();
    d_rows.sort();

    assert_eq!(
        s_rows, d_rows,
        "Row data differs for query: {query}\n  standalone rows ({}):\n{}\n  distributed rows ({}):\n{}",
        s_rows.len(),
        s_rows.join("\n"),
        d_rows.len(),
        d_rows.join("\n"),
    );
}

/// Assert that both backends return the same row *count* (relaxed; no content
/// comparison).  Used for stats-only assertions.
fn assert_same_count(setup: &str, query: &str) {
    let mut standalone = PropertyGraph::new();
    if !setup.is_empty() {
        execute(&mut standalone, setup).unwrap();
    }
    let sr = execute(&mut standalone, query).unwrap();

    let mut cluster = Cluster::new(3);
    if !setup.is_empty() {
        execute(&mut cluster, setup).unwrap();
    }
    let dr = execute(&mut cluster, query).unwrap();

    assert_eq!(
        sr.row_count(), dr.row_count(),
        "Row count differs for query: {query}  standalone={} distributed={}",
        sr.row_count(), dr.row_count()
    );
}

// ─── 1. CREATE & MATCH ─────────────────────────────────────────────────────

#[test]
fn test_create_and_match_single_node() {
    assert_same(
        "CREATE (:Person {name: 'Alice', age: 30})",
        "MATCH (n:Person) RETURN n.name, n.age",
    );
}

#[test]
fn test_create_multiple_nodes_match_all() {
    assert_same(
        "CREATE (:Person {name: 'Alice'})
         CREATE (:Person {name: 'Bob'})
         CREATE (:Person {name: 'Carol'})",
        "MATCH (n:Person) RETURN n.name ORDER BY n.name",
    );
}

#[test]
fn test_match_all_nodes_no_label() {
    assert_same(
        "CREATE (:A) CREATE (:B) CREATE (:C)",
        "MATCH (n) RETURN count(n)",
    );
}

#[test]
fn test_match_with_property_filter() {
    assert_same(
        "CREATE (:Person {name: 'Alice', age: 30})
         CREATE (:Person {name: 'Bob', age: 17})
         CREATE (:Person {name: 'Carol', age: 45})",
        "MATCH (n:Person) WHERE n.age >= 18 RETURN n.name ORDER BY n.name",
    );
}

#[test]
fn test_match_returns_no_rows() {
    assert_same(
        "CREATE (:Person {name: 'Alice'})",
        "MATCH (n:Dog) RETURN n.name",
    );
}

// ─── 2. Relationships ──────────────────────────────────────────────────────

#[test]
fn test_create_and_match_relationship() {
    assert_same(
        "CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})",
        "MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name",
    );
}

#[test]
fn test_match_undirected_relationship() {
    assert_same(
        "CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})",
        "MATCH (a:Person)-[:KNOWS]-(b:Person) RETURN a.name, b.name ORDER BY a.name",
    );
}

#[test]
fn test_relationship_property() {
    assert_same(
        "CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:Person {name: 'Bob'})",
        "MATCH (a)-[r:KNOWS]->(b) RETURN r.since",
    );
}

#[test]
fn test_multiple_relationships_same_type() {
    assert_same(
        "CREATE (a:Person {name: 'Alice'})
         CREATE (b:Person {name: 'Bob'})
         CREATE (c:Person {name: 'Carol'})
         MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}), (c:Person {name: 'Carol'})
         CREATE (a)-[:KNOWS]->(b)
         CREATE (b)-[:KNOWS]->(c)",
        "MATCH (a)-[:KNOWS]->(b) RETURN a.name, b.name ORDER BY a.name",
    );
}

#[test]
fn test_match_chain_of_relationships() {
    assert_same(
        "CREATE (a:Person {name: 'A'})-[:R]->(b:Person {name: 'B'})-[:R]->(c:Person {name: 'C'})",
        "MATCH (a:Person)-[:R]->(b:Person)-[:R]->(c:Person) RETURN a.name, b.name, c.name",
    );
}

// ─── 3. Aggregation ────────────────────────────────────────────────────────

#[test]
fn test_count_nodes() {
    assert_same(
        "CREATE (:N) CREATE (:N) CREATE (:N) CREATE (:N) CREATE (:N)",
        "MATCH (n:N) RETURN count(n) AS cnt",
    );
}

#[test]
fn test_sum_property() {
    assert_same(
        "CREATE (:Item {price: 10})
         CREATE (:Item {price: 20})
         CREATE (:Item {price: 30})",
        "MATCH (i:Item) RETURN sum(i.price) AS total",
    );
}

#[test]
fn test_avg_property() {
    assert_same(
        "CREATE (:Score {v: 10})
         CREATE (:Score {v: 20})
         CREATE (:Score {v: 30})",
        "MATCH (s:Score) RETURN avg(s.v) AS mean",
    );
}

#[test]
fn test_min_max_property() {
    assert_same(
        "CREATE (:T {n: 5}) CREATE (:T {n: 1}) CREATE (:T {n: 9}) CREATE (:T {n: 3})",
        "MATCH (t:T) RETURN min(t.n) AS mn, max(t.n) AS mx",
    );
}

#[test]
fn test_count_distinct() {
    assert_same(
        "CREATE (:P {dept: 'Eng'}) CREATE (:P {dept: 'Eng'}) CREATE (:P {dept: 'HR'})",
        "MATCH (p:P) RETURN count(DISTINCT p.dept) AS num_depts",
    );
}

#[test]
fn test_collect_property() {
    assert_same_count(
        "CREATE (:X {v: 1}) CREATE (:X {v: 2}) CREATE (:X {v: 3})",
        "MATCH (x:X) RETURN collect(x.v) AS vals",
    );
}

#[test]
fn test_group_by_label_count() {
    assert_same(
        "CREATE (:A {dept: 'X'}) CREATE (:A {dept: 'X'})
         CREATE (:A {dept: 'Y'}) CREATE (:A {dept: 'Z'})",
        "MATCH (a:A) RETURN a.dept, count(a) AS cnt ORDER BY a.dept",
    );
}

// ─── 4. WITH & Subqueries ──────────────────────────────────────────────────

#[test]
fn test_with_filter() {
    assert_same(
        "CREATE (:P {name: 'Alice', score: 90})
         CREATE (:P {name: 'Bob', score: 60})
         CREATE (:P {name: 'Carol', score: 75})",
        "MATCH (p:P) WITH p WHERE p.score >= 75 RETURN p.name ORDER BY p.name",
    );
}

#[test]
fn test_with_ordering_and_limit() {
    assert_same(
        "CREATE (:N {v: 3}) CREATE (:N {v: 1}) CREATE (:N {v: 4})
         CREATE (:N {v: 1}) CREATE (:N {v: 5})",
        "MATCH (n:N) WITH n ORDER BY n.v DESC LIMIT 3 RETURN n.v",
    );
}

#[test]
fn test_with_aggregation_then_filter() {
    assert_same(
        "CREATE (:Sale {region: 'East', amount: 100})
         CREATE (:Sale {region: 'East', amount: 200})
         CREATE (:Sale {region: 'West', amount: 50})",
        "MATCH (s:Sale) WITH s.region AS region, sum(s.amount) AS total
         WHERE total > 100 RETURN region, total ORDER BY region",
    );
}

// ─── 5. ORDER BY / SKIP / LIMIT ────────────────────────────────────────────

#[test]
fn test_order_by_asc() {
    assert_same(
        "CREATE (:N {v: 3}) CREATE (:N {v: 1}) CREATE (:N {v: 2})",
        "MATCH (n:N) RETURN n.v ORDER BY n.v ASC",
    );
}

#[test]
fn test_order_by_desc() {
    assert_same(
        "CREATE (:N {v: 3}) CREATE (:N {v: 1}) CREATE (:N {v: 2})",
        "MATCH (n:N) RETURN n.v ORDER BY n.v DESC",
    );
}

#[test]
fn test_skip_and_limit() {
    assert_same(
        "CREATE (:N {v: 1}) CREATE (:N {v: 2}) CREATE (:N {v: 3})
         CREATE (:N {v: 4}) CREATE (:N {v: 5})",
        "MATCH (n:N) RETURN n.v ORDER BY n.v SKIP 1 LIMIT 3",
    );
}

#[test]
fn test_limit_one() {
    assert_same_count(
        "CREATE (:N {v: 1}) CREATE (:N {v: 2}) CREATE (:N {v: 3})",
        "MATCH (n:N) RETURN n.v LIMIT 1",
    );
}

// ─── 6. SET / REMOVE ───────────────────────────────────────────────────────

#[test]
fn test_set_node_property() {
    assert_same(
        "CREATE (:P {name: 'Alice', age: 30})",
        "MATCH (p:P {name: 'Alice'}) SET p.age = 31 RETURN p.age",
    );
}

#[test]
fn test_set_multiple_properties() {
    assert_same(
        "CREATE (:P {name: 'Alice'})",
        "MATCH (p:P) SET p.age = 30, p.city = 'NYC' RETURN p.name, p.age, p.city",
    );
}

#[test]
fn test_remove_property() {
    assert_same(
        "CREATE (:P {name: 'Alice', temp: true})",
        "MATCH (p:P) REMOVE p.temp RETURN p.name, p.temp",
    );
}

#[test]
fn test_add_label() {
    assert_same(
        "CREATE (:Base {x: 1})",
        "MATCH (n:Base) SET n:Extra RETURN labels(n) AS lbls",
    );
}

#[test]
fn test_remove_label() {
    assert_same(
        "CREATE (:A:B {x: 1})",
        "MATCH (n:A:B) REMOVE n:B RETURN labels(n) AS lbls",
    );
}

// ─── 7. DELETE / DETACH DELETE ─────────────────────────────────────────────

#[test]
fn test_delete_node() {
    assert_same(
        "CREATE (:Tmp) CREATE (:Keep {x: 1})",
        "MATCH (t:Tmp) DELETE t
         MATCH (k:Keep) RETURN k.x",
    );
}

#[test]
fn test_detach_delete_node_with_relationships() {
    assert_same(
        "CREATE (a:A)-[:R]->(b:B)",
        "MATCH (a:A) DETACH DELETE a
         MATCH (n) RETURN count(n) AS remaining",
    );
}

#[test]
fn test_delete_relationship() {
    assert_same(
        "CREATE (a:P {name:'A'})-[:KNOWS]->(b:P {name:'B'})",
        "MATCH ()-[r:KNOWS]->() DELETE r
         MATCH ()-[r2:KNOWS]->() RETURN count(r2) AS cnt",
    );
}

// ─── 8. MERGE ──────────────────────────────────────────────────────────────

#[test]
fn test_merge_creates_if_absent() {
    assert_same(
        "",
        "MERGE (p:Person {name: 'Alice'}) RETURN p.name",
    );
}

#[test]
fn test_merge_matches_if_present() {
    assert_same(
        "CREATE (:Person {name: 'Alice', age: 30})",
        "MERGE (p:Person {name: 'Alice'}) RETURN p.age",
    );
}

#[test]
fn test_merge_on_create_set() {
    assert_same(
        "",
        "MERGE (p:Person {name: 'Bob'})
         ON CREATE SET p.created = true
         RETURN p.name, p.created",
    );
}

#[test]
fn test_merge_on_match_set() {
    assert_same(
        "CREATE (:Person {name: 'Alice', visits: 0})",
        "MERGE (p:Person {name: 'Alice'})
         ON MATCH SET p.visits = p.visits + 1
         RETURN p.name, p.visits",
    );
}

// ─── 9. UNWIND ─────────────────────────────────────────────────────────────

#[test]
fn test_unwind_literal_list() {
    assert_same(
        "",
        "UNWIND [1, 2, 3, 4, 5] AS x RETURN x ORDER BY x",
    );
}

#[test]
fn test_unwind_create_nodes() {
    assert_same(
        "",
        "UNWIND ['Alice', 'Bob', 'Carol'] AS name
         CREATE (:Person {name: name})
         RETURN count(*) AS created",
    );
}

// ─── 10. String functions ──────────────────────────────────────────────────

#[test]
fn test_string_functions() {
    assert_same(
        "CREATE (:P {name: 'Alice'})",
        "MATCH (p:P) RETURN
           toLower(p.name) AS lower,
           toUpper(p.name) AS upper,
           size(p.name) AS len",
    );
}

#[test]
fn test_string_contains() {
    assert_same(
        "CREATE (:P {name: 'Alice'}) CREATE (:P {name: 'Bob'})",
        "MATCH (p:P) WHERE p.name CONTAINS 'li' RETURN p.name",
    );
}

#[test]
fn test_string_starts_ends_with() {
    assert_same(
        "CREATE (:P {name: 'Alice'}) CREATE (:P {name: 'Bob'}) CREATE (:P {name: 'Alicia'})",
        "MATCH (p:P) WHERE p.name STARTS WITH 'Ali' RETURN p.name ORDER BY p.name",
    );
}

// ─── 11. Math functions ────────────────────────────────────────────────────

#[test]
fn test_math_functions() {
    assert_same(
        "",
        "RETURN abs(-5) AS a, floor(3.7) AS f, ceil(3.2) AS c, round(3.5) AS r",
    );
}

#[test]
fn test_type_conversion() {
    assert_same(
        "",
        "RETURN toInteger('42') AS i, toFloat('3.14') AS f, toString(99) AS s",
    );
}

// ─── 12. List operations ───────────────────────────────────────────────────

#[test]
fn test_list_size_and_head_tail() {
    assert_same(
        "",
        "RETURN size([1,2,3]) AS sz, head([1,2,3]) AS h, last([1,2,3]) AS t",
    );
}

#[test]
fn test_list_range() {
    assert_same(
        "",
        "RETURN range(1, 5) AS r",
    );
}

#[test]
fn test_list_in_operator() {
    assert_same(
        "CREATE (:P {role: 'admin'}) CREATE (:P {role: 'user'}) CREATE (:P {role: 'guest'})",
        "MATCH (p:P) WHERE p.role IN ['admin', 'guest'] RETURN p.role ORDER BY p.role",
    );
}

// ─── 13. CASE expressions ─────────────────────────────────────────────────

#[test]
fn test_case_simple() {
    assert_same(
        "CREATE (:P {score: 85}) CREATE (:P {score: 55}) CREATE (:P {score: 70})",
        "MATCH (p:P)
         RETURN p.score,
                CASE WHEN p.score >= 80 THEN 'A'
                     WHEN p.score >= 65 THEN 'B'
                     ELSE 'C' END AS grade
         ORDER BY p.score DESC",
    );
}

// ─── 14. NULL handling ─────────────────────────────────────────────────────

#[test]
fn test_null_coalesce() {
    assert_same(
        "CREATE (:P {name: 'Alice'}) CREATE (:P {name: 'Bob', nickname: 'Bobby'})",
        "MATCH (p:P) RETURN p.name, coalesce(p.nickname, p.name) AS display ORDER BY p.name",
    );
}

#[test]
fn test_is_null_filter() {
    assert_same(
        "CREATE (:P {name: 'Alice', age: 30}) CREATE (:P {name: 'Bob'})",
        "MATCH (p:P) WHERE p.age IS NULL RETURN p.name",
    );
}

#[test]
fn test_is_not_null_filter() {
    assert_same(
        "CREATE (:P {name: 'Alice', age: 30}) CREATE (:P {name: 'Bob'})",
        "MATCH (p:P) WHERE p.age IS NOT NULL RETURN p.name",
    );
}

// ─── 15. Parameters ────────────────────────────────────────────────────────

#[test]
fn test_parameterized_create_and_match() {
    let mut params = HashMap::new();
    params.insert("name".to_string(), CypherValue::String("Alice".to_string()));
    params.insert("age".to_string(), CypherValue::Integer(30));

    assert_same_with_params(
        "CREATE (:Person {name: 'Alice', age: 30})",
        "MATCH (p:Person {name: $name}) RETURN p.name, p.age",
        params,
    );
}

// ─── 16. OPTIONAL MATCH ────────────────────────────────────────────────────

#[test]
fn test_optional_match_returns_null() {
    assert_same(
        "CREATE (:Person {name: 'Alice'})",
        "MATCH (p:Person)
         OPTIONAL MATCH (p)-[:KNOWS]->(friend)
         RETURN p.name, friend.name AS friend_name",
    );
}

#[test]
fn test_optional_match_finds_some() {
    assert_same(
        "CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})",
        "MATCH (p:Person {name: 'Alice'})
         OPTIONAL MATCH (p)-[:KNOWS]->(friend)
         RETURN p.name, friend.name AS friend_name",
    );
}

// ─── 17. EXISTS / Predicate functions ──────────────────────────────────────

#[test]
fn test_exists_property() {
    assert_same(
        "CREATE (:P {name: 'Alice', age: 30}) CREATE (:P {name: 'Bob'})",
        "MATCH (p:P) RETURN p.name, p.age IS NOT NULL AS has_age ORDER BY p.name",
    );
}

// ─── 18. Labels & types introspection ─────────────────────────────────────

#[test]
fn test_labels_function() {
    assert_same(
        "CREATE (:A:B:C {x: 1})",
        "MATCH (n:A) RETURN size(labels(n)) AS num_labels",
    );
}

#[test]
fn test_type_function_on_relationship() {
    assert_same(
        "CREATE ()-[:KNOWS]->() CREATE ()-[:LIKES]->()",
        "MATCH ()-[r]->() RETURN type(r) AS t ORDER BY t",
    );
}

// ─── 19. Cross-partition specific ─────────────────────────────────────────

/// Verify that relationships between nodes on different partitions are visible.
#[test]
fn test_cross_partition_relationship_match() {
    // With 2 partitions and round-robin, node 1 → p0, node 2 → p1
    let mut cluster = Cluster::new(2);
    execute(&mut cluster, "CREATE (:A {name:'Alice'})").unwrap();
    execute(&mut cluster, "CREATE (:B {name:'Bob'})").unwrap();

    // Connect Alice (p0) → Bob (p1) cross-partition
    execute(
        &mut cluster,
        "MATCH (a:A), (b:B) CREATE (a)-[:LINKED]->(b)",
    )
    .unwrap();

    let result = execute(&mut cluster, "MATCH (a:A)-[:LINKED]->(b:B) RETURN a.name, b.name")
        .unwrap();
    assert_eq!(result.row_count(), 1, "cross-partition relationship must be matchable");
}

/// Verify node_count + edge_count across partitions.
#[test]
fn test_distributed_node_edge_counts_sum_correctly() {
    let mut cluster = Cluster::new(3);

    execute(
        &mut cluster,
        "CREATE (:P {n:1}) CREATE (:P {n:2}) CREATE (:P {n:3})
         CREATE (:P {n:4}) CREATE (:P {n:5}) CREATE (:P {n:6})",
    )
    .unwrap();

    let result = execute(&mut cluster, "MATCH (n:P) RETURN count(n) AS cnt").unwrap();
    let cnt = result.rows[0].get("cnt").unwrap();
    assert_eq!(*cnt, CypherValue::Integer(6));

    // Partition-level checks
    assert_eq!(cluster.partition_node_count(0), 2);
    assert_eq!(cluster.partition_node_count(1), 2);
    assert_eq!(cluster.partition_node_count(2), 2);
}

/// Verify kill + recover does not silently corrupt node count queries.
#[test]
fn test_kill_recover_node_count_stays_consistent() {
    let mut cluster = Cluster::new(3);

    // 9 nodes, 3 per partition
    let mut node_data: Vec<(u64, Vec<String>, indexmap::IndexMap<String, ocg::PropertyValue>)> = Vec::new();
    for i in 0..9u64 {
        let mut props = indexmap::IndexMap::new();
        props.insert("i".to_string(), ocg::PropertyValue::Integer(i as i64));
        let id = ocg::graph::GraphBackend::create_node(
            &mut cluster,
            vec!["N"],
            props.clone(),
        );
        if ocg::distributed::graph::partitioned_backend::PartitionedBackend::<NetworKitRustBackend>::new(
            0,
            std::sync::Arc::new(std::sync::RwLock::new(ocg::distributed::partition::PartitionMap::new(3))),
        )
        .owns_node(id)
        {
            node_data.push((id, vec!["N".to_string()], props));
        }
    }

    let before = execute(&mut cluster, "MATCH (n:N) RETURN count(n) AS c")
        .unwrap()
        .rows[0]
        .get("c")
        .unwrap()
        .clone();
    assert_eq!(before, CypherValue::Integer(9));

    // Kill partition 0, count drops
    cluster.kill_partition(0);
    let mid = execute(&mut cluster, "MATCH (n:N) RETURN count(n) AS c")
        .unwrap()
        .rows[0]
        .get("c")
        .unwrap()
        .clone();
    assert!(
        matches!(mid, CypherValue::Integer(n) if n < 9),
        "count should drop after kill"
    );

    // Recover partition 0
    cluster.recover_partition_nodes(0, node_data);
    let after = execute(&mut cluster, "MATCH (n:N) RETURN count(n) AS c")
        .unwrap()
        .rows[0]
        .get("c")
        .unwrap()
        .clone();
    assert_eq!(after, CypherValue::Integer(9), "count must be restored after recovery");
}

// ─── 20. Multi-partition single-node compatibility ─────────────────────────

/// A single-partition cluster should behave identically to standalone for all
/// queries (no routing overhead, same IDs).
#[test]
fn test_single_partition_cluster_equals_standalone() {
    let queries = [
        ("CREATE (:P {v:1}) CREATE (:P {v:2})", "MATCH (p:P) RETURN p.v ORDER BY p.v"),
        ("CREATE (a:X)-[:R]->(b:Y)", "MATCH (a)-[r]->(b) RETURN type(r)"),
        ("", "RETURN 1+1 AS result"),
        ("", "UNWIND range(1,3) AS x RETURN x"),
    ];
    for (setup, query) in queries {
        assert_same_n(setup, query, 1);
    }
}

/// Five-partition cluster still matches standalone.
#[test]
fn test_five_partition_cluster_equals_standalone() {
    assert_same_n(
        "CREATE (:P {n:1}) CREATE (:P {n:2}) CREATE (:P {n:3})
         CREATE (:P {n:4}) CREATE (:P {n:5}) CREATE (:P {n:6})
         CREATE (:P {n:7}) CREATE (:P {n:8}) CREATE (:P {n:9})
         CREATE (:P {n:10})",
        "MATCH (p:P) RETURN count(p) AS cnt",
        5,
    );
}
