//! Cross-backend algorithm correctness test suite.
//!
//! Validates that all graph algorithms produce equivalent, correct results
//! across every backend implementation:
//!   - PropertyGraph (petgraph StableDiGraph)
//!   - NetworKitRustBackend (adjacency list, native algorithms)
//!   - RustworkxCoreBackend (petgraph + rustworkx-core)
//!   - GraphrsBackend (petgraph + graphrs community)
//!
//! Each test builds the same graph on all backends, runs the algorithm,
//! and asserts that:
//!   1. Results are numerically equivalent (within epsilon for float comparisons)
//!   2. Results are structurally correct (known ground-truth values)
//!
//! Future: distributed partitioned graph results are validated against
//! single-node results once the BSP layer is implemented (Phase 4-5).
//!
use std::collections::{HashMap, HashSet};
use indexmap::IndexMap;

use ocg::graph::{GraphBackend, EdgeLike, PropertyGraph, AlgorithmEngine};
use ocg::graph::backends::{NetworKitRustBackend, RustworkxCoreBackend, GraphrsBackend};

// ─── Helper: create_node via trait ────────────────────────────────────────────
// Avoids the inherent method on PropertyGraph which returns &GraphNode instead of u64.

fn cn<G: GraphBackend>(g: &mut G, labels: &[&str]) -> u64 {
    g.create_node(labels.iter().copied(), IndexMap::new())
}

fn cr<G: GraphBackend>(g: &mut G, src: u64, dst: u64) {
    g.create_relationship(src, dst, "E", IndexMap::new()).unwrap();
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

const EPSILON: f64 = 1e-4;

fn approx_eq(a: f64, b: f64) -> bool {
    (a - b).abs() < EPSILON
}

/// Build a simple directed triangle: n1→n2→n3→n1
fn build_triangle<G: GraphBackend + Default>() -> (G, u64, u64, u64) {
    let mut g = G::default();
    let n1 = cn(&mut g, &["A"]);
    let n2 = cn(&mut g, &["B"]);
    let n3 = cn(&mut g, &["C"]);
    cr(&mut g, n1, n2);
    cr(&mut g, n2, n3);
    cr(&mut g, n3, n1);
    (g, n1, n2, n3)
}

fn build_triangle_nk() -> (NetworKitRustBackend, u64, u64, u64) { build_triangle() }
fn build_triangle_pg() -> (PropertyGraph, u64, u64, u64)         { build_triangle() }
fn build_triangle_rx() -> (RustworkxCoreBackend, u64, u64, u64)  { build_triangle() }
fn build_triangle_gr() -> (GraphrsBackend, u64, u64, u64)         { build_triangle() }

/// Build a directed path graph: n[0]→n[1]→...→n[len-1]
fn build_path<G: GraphBackend + Default>(len: usize) -> (G, Vec<u64>) {
    let mut g = G::default();
    let nodes: Vec<u64> = (0..len).map(|_| cn(&mut g, &["N"])).collect();
    for i in 0..len - 1 { cr(&mut g, nodes[i], nodes[i + 1]); }
    (g, nodes)
}

fn build_path_nk(len: usize) -> (NetworKitRustBackend, Vec<u64>) { build_path(len) }
fn build_path_pg(len: usize) -> (PropertyGraph, Vec<u64>)         { build_path(len) }
fn build_path_rx(len: usize) -> (RustworkxCoreBackend, Vec<u64>)  { build_path(len) }
fn build_path_gr(len: usize) -> (GraphrsBackend, Vec<u64>)         { build_path(len) }

/// Build a directed star graph: center → leaves
fn build_star<G: GraphBackend + Default>(leaves: usize) -> (G, u64, Vec<u64>) {
    let mut g = G::default();
    let center = cn(&mut g, &["Center"]);
    let leaf_ids: Vec<u64> = (0..leaves).map(|_| {
        let l = cn(&mut g, &["Leaf"]);
        g.create_relationship(center, l, "CONNECTS", IndexMap::new()).unwrap();
        l
    }).collect();
    (g, center, leaf_ids)
}

fn build_star_nk(leaves: usize) -> (NetworKitRustBackend, u64, Vec<u64>) { build_star(leaves) }
fn build_star_pg(leaves: usize) -> (PropertyGraph, u64, Vec<u64>)         { build_star(leaves) }
fn build_star_rx(leaves: usize) -> (RustworkxCoreBackend, u64, Vec<u64>)  { build_star(leaves) }
fn build_star_gr(leaves: usize) -> (GraphrsBackend, u64, Vec<u64>)         { build_star(leaves) }

// ─── PageRank ─────────────────────────────────────────────────────────────────

#[test]
fn test_pagerank_triangle_all_backends() {
    // On a symmetric directed triangle, all nodes have equal PageRank
    let (nk, n1, n2, n3) = build_triangle_nk();
    let pr_nk = nk.pagerank(0.85, 100);

    let (pg, p1, p2, p3) = build_triangle_pg();
    let pr_pg = pg.pagerank(0.85, 100);

    let (rx, r1, r2, r3) = build_triangle_rx();
    let pr_rx = rx.pagerank(0.85, 100);

    let (gr, g1, g2, g3) = build_triangle_gr();
    let pr_gr = gr.pagerank(0.85, 100);

    // All nodes should have equal rank in a symmetric triangle
    let nk_n1 = pr_nk[&n1];
    let nk_n2 = pr_nk[&n2];
    let nk_n3 = pr_nk[&n3];
    assert!(approx_eq(nk_n1, nk_n2), "NK: PageRank should be equal in triangle: {nk_n1} vs {nk_n2}");
    assert!(approx_eq(nk_n2, nk_n3), "NK: PageRank should be equal in triangle: {nk_n2} vs {nk_n3}");

    // Cross-backend: same graph → same node IDs (all backends start IDs from same base)
    assert!(approx_eq(pr_nk[&n1], pr_pg[&p1]), "NK vs PG pagerank mismatch node 1");
    assert!(approx_eq(pr_nk[&n2], pr_pg[&p2]), "NK vs PG pagerank mismatch node 2");
    assert!(approx_eq(pr_nk[&n3], pr_pg[&p3]), "NK vs PG pagerank mismatch node 3");

    assert!(approx_eq(pr_nk[&n1], pr_rx[&r1]), "NK vs RX pagerank mismatch node 1");
    assert!(approx_eq(pr_nk[&n1], pr_gr[&g1]), "NK vs GR pagerank mismatch node 1");
    let _ = (r2, r3, g2, g3); // suppress unused variable warnings
}

#[test]
fn test_pagerank_star_hub_dominates() {
    // In a directed out-star, PageRank flows from center to leaves
    let leaves = 5;
    let (nk, center_nk, _) = build_star_nk(leaves);
    let pr_nk = nk.pagerank(0.85, 100);

    let (pg, center_pg, leaf_ids_pg) = build_star_pg(leaves);
    let pr_pg = pg.pagerank(0.85, 100);

    let (rx, center_rx, _) = build_star_rx(leaves);
    let pr_rx = rx.pagerank(0.85, 100);

    let (gr, center_gr, _) = build_star_gr(leaves);
    let pr_gr = gr.pagerank(0.85, 100);

    // Cross-backend consistency: center rank should be the same
    assert!(
        approx_eq(pr_nk[&center_nk], pr_pg[&center_pg]),
        "NK vs PG: center PageRank mismatch: {} vs {}",
        pr_nk[&center_nk], pr_pg[&center_pg]
    );
    assert!(
        approx_eq(pr_nk[&center_nk], pr_rx[&center_rx]),
        "NK vs RX: center PageRank mismatch"
    );
    assert!(
        approx_eq(pr_nk[&center_nk], pr_gr[&center_gr]),
        "NK vs GR: center PageRank mismatch"
    );
    let _ = leaf_ids_pg;
}

#[test]
fn test_pagerank_empty_graph() {
    let nk = NetworKitRustBackend::new();
    let pg = PropertyGraph::new();
    let rx = RustworkxCoreBackend::new();
    let gr = GraphrsBackend::new();

    assert!(nk.pagerank(0.85, 100).is_empty(), "NK: empty graph should have empty PageRank");
    assert!(pg.pagerank(0.85, 100).is_empty(), "PG: empty graph should have empty PageRank");
    assert!(rx.pagerank(0.85, 100).is_empty(), "RX: empty graph should have empty PageRank");
    assert!(gr.pagerank(0.85, 100).is_empty(), "GR: empty graph should have empty PageRank");
}

#[test]
fn test_pagerank_single_node() {
    let mut nk = NetworKitRustBackend::new();
    let n = cn(&mut nk, &["N"]);
    let pr = nk.pagerank(0.85, 100);
    assert_eq!(pr.len(), 1);
    assert!(pr.contains_key(&n));
}

// ─── BFS / Shortest Path ─────────────────────────────────────────────────────

#[test]
fn test_bfs_path_all_backends() {
    let len = 5;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);
    let (rx, nodes_rx) = build_path_rx(len);
    let (gr, nodes_gr) = build_path_gr(len);

    // BFS from first to last node: path should traverse all len nodes
    let path_nk = nk.bfs_path(nodes_nk[0], nodes_nk[4]).expect("NK: path should exist");
    let path_pg = pg.bfs_path(nodes_pg[0], nodes_pg[4]).expect("PG: path should exist");
    let path_rx = rx.bfs_path(nodes_rx[0], nodes_rx[4]).expect("RX: path should exist");
    let path_gr = gr.bfs_path(nodes_gr[0], nodes_gr[4]).expect("GR: path should exist");

    assert_eq!(path_nk.len(), len, "NK: BFS path length should be {len}");
    assert_eq!(path_pg.len(), len, "PG: BFS path length should be {len}");
    assert_eq!(path_rx.len(), len, "RX: BFS path length should be {len}");
    assert_eq!(path_gr.len(), len, "GR: BFS path length should be {len}");

    // First and last nodes should match
    assert_eq!(path_nk[0], nodes_nk[0]);
    assert_eq!(path_nk[4], nodes_nk[4]);
    assert_eq!(path_pg[0], nodes_pg[0]);
    assert_eq!(path_pg[4], nodes_pg[4]);
}

#[test]
fn test_bfs_path_not_found() {
    // Reversed path: edges go 4→3→2→1→0, so BFS from 0 to 4 should fail
    let mut nk = NetworKitRustBackend::new();
    let nodes: Vec<u64> = (0..5).map(|_| cn(&mut nk, &["N"])).collect();
    for i in (1..5).rev() {
        nk.create_relationship(nodes[i], nodes[i - 1], "E", IndexMap::new()).unwrap();
    }
    let result = nk.bfs_path(nodes[0], nodes[4]);
    assert!(result.is_none(), "BFS should not find path against edge direction");

    let mut pg = PropertyGraph::new();
    let nodes_pg: Vec<u64> = (0..5).map(|_| cn(&mut pg, &["N"])).collect();
    for i in (1..5).rev() {
        pg.create_relationship(nodes_pg[i], nodes_pg[i - 1], "E", IndexMap::new()).unwrap();
    }
    let result_pg = pg.bfs_path(nodes_pg[0], nodes_pg[4]);
    assert!(result_pg.is_none(), "PG: BFS should not find path against edge direction");
}

#[test]
fn test_bfs_path_triangle_all_backends() {
    let (nk, n1, n2, _n3) = build_triangle_nk();
    let (pg, p1, p2, _p3) = build_triangle_pg();
    let (rx, r1, r2, _r3) = build_triangle_rx();
    let (gr, g1, g2, _g3) = build_triangle_gr();

    // Direct edge exists: path length should be 2 (source + dest)
    let path_nk = nk.bfs_path(n1, n2).expect("NK: direct edge path");
    let path_pg = pg.bfs_path(p1, p2).expect("PG: direct edge path");
    let path_rx = rx.bfs_path(r1, r2).expect("RX: direct edge path");
    let path_gr = gr.bfs_path(g1, g2).expect("GR: direct edge path");

    assert_eq!(path_nk.len(), 2, "NK: direct edge path should have 2 nodes");
    assert_eq!(path_pg.len(), 2, "PG: direct edge path should have 2 nodes");
    assert_eq!(path_rx.len(), 2, "RX: direct edge path should have 2 nodes");
    assert_eq!(path_gr.len(), 2, "GR: direct edge path should have 2 nodes");
}

// ─── Dijkstra ─────────────────────────────────────────────────────────────────

#[test]
fn test_dijkstra_path_all_backends() {
    let len = 4;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);
    let (rx, nodes_rx) = build_path_rx(len);
    let (gr, nodes_gr) = build_path_gr(len);

    let (cost_nk, path_nk) = nk.dijkstra_path(nodes_nk[0], nodes_nk[3]).expect("NK: dijkstra path");
    let (cost_pg, path_pg) = pg.dijkstra_path(nodes_pg[0], nodes_pg[3]).expect("PG: dijkstra path");
    let (cost_rx, path_rx) = rx.dijkstra_path(nodes_rx[0], nodes_rx[3]).expect("RX: dijkstra path");
    let (cost_gr, path_gr) = gr.dijkstra_path(nodes_gr[0], nodes_gr[3]).expect("GR: dijkstra path");

    // Default edge weight is 1.0, path length = 3 edges
    assert!(approx_eq(cost_nk, 3.0), "NK: Dijkstra cost should be 3.0, got {cost_nk}");
    assert!(approx_eq(cost_pg, 3.0), "PG: Dijkstra cost should be 3.0, got {cost_pg}");
    assert!(approx_eq(cost_rx, 3.0), "RX: Dijkstra cost should be 3.0, got {cost_rx}");
    assert!(approx_eq(cost_gr, 3.0), "GR: Dijkstra cost should be 3.0, got {cost_gr}");

    assert_eq!(path_nk.len(), len, "NK: path node count");
    assert_eq!(path_pg.len(), len, "PG: path node count");
    assert_eq!(path_rx.len(), len, "RX: path node count");
    assert_eq!(path_gr.len(), len, "GR: path node count");
}

#[test]
fn test_dijkstra_not_found() {
    let mut nk = NetworKitRustBackend::new();
    let a = cn(&mut nk, &["A"]);
    let b = cn(&mut nk, &["B"]);
    // No edge — path should not exist
    assert!(nk.dijkstra_path(a, b).is_none(), "NK: no path should return None");

    let mut pg = PropertyGraph::new();
    let a = cn(&mut pg, &["A"]);
    let b = cn(&mut pg, &["B"]);
    assert!(pg.dijkstra_path(a, b).is_none(), "PG: no path should return None");
}

// ─── Betweenness Centrality ───────────────────────────────────────────────────

#[test]
fn test_betweenness_centrality_path_graph() {
    // In a path 1→2→3→4→5, middle node has highest betweenness
    let len = 5;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);
    let (rx, nodes_rx) = build_path_rx(len);
    let (gr, nodes_gr) = build_path_gr(len);

    let bc_nk = nk.betweenness_centrality(false);
    let bc_pg = pg.betweenness_centrality(false);
    let bc_rx = rx.betweenness_centrality(false);
    let bc_gr = gr.betweenness_centrality(false);

    // Middle node (index 2) should have highest betweenness
    let mid_nk = bc_nk[&nodes_nk[2]];
    let mid_pg = bc_pg[&nodes_pg[2]];
    let mid_rx = bc_rx[&nodes_rx[2]];
    let mid_gr = bc_gr[&nodes_gr[2]];

    for i in [0usize, 1, 3, 4] {
        assert!(
            mid_nk >= bc_nk[&nodes_nk[i]],
            "NK: middle node should have highest BC: {} vs {}",
            mid_nk, bc_nk[&nodes_nk[i]]
        );
        assert!(mid_pg >= bc_pg[&nodes_pg[i]], "PG: middle node should have highest BC");
        assert!(mid_rx >= bc_rx[&nodes_rx[i]], "RX: middle node should have highest BC");
        assert!(mid_gr >= bc_gr[&nodes_gr[i]], "GR: middle node should have highest BC");
    }

    // Cross-backend numerical consistency
    assert!(approx_eq(mid_nk, mid_pg), "NK vs PG betweenness mismatch: {mid_nk} vs {mid_pg}");
    assert!(approx_eq(mid_nk, mid_rx), "NK vs RX betweenness mismatch: {mid_nk} vs {mid_rx}");
    assert!(approx_eq(mid_nk, mid_gr), "NK vs GR betweenness mismatch: {mid_nk} vs {mid_gr}");
}

#[test]
fn test_betweenness_centrality_triangle_equal() {
    // In a symmetric triangle all nodes should have equal betweenness
    let (nk, n1, n2, n3) = build_triangle_nk();
    let (pg, p1, p2, p3) = build_triangle_pg();

    let bc_nk = nk.betweenness_centrality(false);
    let bc_pg = pg.betweenness_centrality(false);

    assert!(approx_eq(bc_nk[&n1], bc_nk[&n2]), "NK: triangle BC should be equal: {} vs {}", bc_nk[&n1], bc_nk[&n2]);
    assert!(approx_eq(bc_nk[&n2], bc_nk[&n3]), "NK: triangle BC should be equal");
    assert!(approx_eq(bc_pg[&p1], bc_pg[&p2]), "PG: triangle BC should be equal");
    assert!(approx_eq(bc_pg[&p2], bc_pg[&p3]), "PG: triangle BC should be equal");
    assert!(approx_eq(bc_nk[&n1], bc_pg[&p1]), "NK vs PG betweenness cross-backend");
}

// ─── Degree Centrality ────────────────────────────────────────────────────────

#[test]
fn test_degree_centrality_star_all_backends() {
    let leaves = 4;
    let (nk, center_nk, _) = build_star_nk(leaves);
    let (pg, center_pg, _) = build_star_pg(leaves);
    let (rx, center_rx, _) = build_star_rx(leaves);
    let (gr, center_gr, _) = build_star_gr(leaves);

    let dc_nk = nk.degree_centrality();
    let dc_pg = pg.degree_centrality();
    let dc_rx = rx.degree_centrality();
    let dc_gr = gr.degree_centrality();

    let center_deg_nk = dc_nk[&center_nk];
    let center_deg_pg = dc_pg[&center_pg];
    let center_deg_rx = dc_rx[&center_rx];
    let center_deg_gr = dc_gr[&center_gr];

    assert!(
        approx_eq(center_deg_nk, center_deg_pg),
        "NK vs PG: degree centrality mismatch: {center_deg_nk} vs {center_deg_pg}"
    );
    assert!(approx_eq(center_deg_nk, center_deg_rx), "NK vs RX: degree centrality mismatch");
    assert!(approx_eq(center_deg_nk, center_deg_gr), "NK vs GR: degree centrality mismatch");
}

// ─── Connected Components ─────────────────────────────────────────────────────

#[test]
fn test_connected_components_all_backends() {
    // Two disconnected chains → 2 components
    let mut nk = NetworKitRustBackend::new();
    let a1 = cn(&mut nk, &["A"]);
    let a2 = cn(&mut nk, &["A"]);
    let a3 = cn(&mut nk, &["A"]);
    nk.create_relationship(a1, a2, "E", IndexMap::new()).unwrap();
    nk.create_relationship(a2, a3, "E", IndexMap::new()).unwrap();
    let b1 = cn(&mut nk, &["B"]);
    let b2 = cn(&mut nk, &["B"]);
    nk.create_relationship(b1, b2, "E", IndexMap::new()).unwrap();

    let mut pg = PropertyGraph::new();
    let pa1 = cn(&mut pg, &["A"]);
    let pa2 = cn(&mut pg, &["A"]);
    let pa3 = cn(&mut pg, &["A"]);
    pg.create_relationship(pa1, pa2, "E", IndexMap::new()).unwrap();
    pg.create_relationship(pa2, pa3, "E", IndexMap::new()).unwrap();
    let pb1 = cn(&mut pg, &["B"]);
    let pb2 = cn(&mut pg, &["B"]);
    pg.create_relationship(pb1, pb2, "E", IndexMap::new()).unwrap();

    let comps_nk = nk.connected_components_list();
    let comps_pg = pg.connected_components_list();

    assert_eq!(comps_nk.len(), 2, "NK: expected 2 connected components, got {}", comps_nk.len());
    assert_eq!(comps_pg.len(), 2, "PG: expected 2 connected components, got {}", comps_pg.len());

    // Sizes: one component of size 3, one of size 2
    let mut sizes_nk: Vec<usize> = comps_nk.iter().map(|c| c.len()).collect();
    let mut sizes_pg: Vec<usize> = comps_pg.iter().map(|c| c.len()).collect();
    sizes_nk.sort_unstable();
    sizes_pg.sort_unstable();
    assert_eq!(sizes_nk, vec![2, 3], "NK: component sizes mismatch");
    assert_eq!(sizes_pg, vec![2, 3], "PG: component sizes mismatch");
    let _ = (a1, a2, a3, b1, b2, pa1, pa2, pa3, pb1, pb2);
}

#[test]
fn test_connected_components_single_node() {
    let mut nk = NetworKitRustBackend::new();
    cn(&mut nk, &["N"]);
    let comps = nk.connected_components_list();
    assert_eq!(comps.len(), 1);
    assert_eq!(comps[0].len(), 1);
}

// ─── Strongly Connected Components ───────────────────────────────────────────

#[test]
fn test_strongly_connected_components_all_backends() {
    // Triangle 1→2→3→1 is one SCC; isolated node 4 is another
    let (mut nk, n1, n2, n3) = build_triangle_nk();
    let _n4 = cn(&mut nk, &["D"]); // isolated node → separate SCC

    let (mut pg, p1, p2, p3) = build_triangle_pg();
    let _p4 = cn(&mut pg, &["D"]);

    let sccs_nk = nk.strongly_connected_components_list();
    let sccs_pg = pg.strongly_connected_components_list();

    assert_eq!(sccs_nk.len(), 2, "NK: expected 2 SCCs");
    assert_eq!(sccs_pg.len(), 2, "PG: expected 2 SCCs");

    let mut sizes_nk: Vec<usize> = sccs_nk.iter().map(|s| s.len()).collect();
    let mut sizes_pg: Vec<usize> = sccs_pg.iter().map(|s| s.len()).collect();
    sizes_nk.sort_unstable();
    sizes_pg.sort_unstable();
    assert_eq!(sizes_nk, vec![1, 3], "NK: SCC sizes mismatch");
    assert_eq!(sizes_pg, vec![1, 3], "PG: SCC sizes mismatch");

    let _ = (n1, n2, n3, p1, p2, p3);
}

// ─── Minimum Spanning Tree ────────────────────────────────────────────────────

#[test]
fn test_minimum_spanning_tree_all_backends() {
    // Path graph: MST = the path itself (n-1 edges)
    let len = 5;
    let (nk, _) = build_path_nk(len);
    let (pg, _) = build_path_pg(len);
    let (rx, _) = build_path_rx(len);
    let (gr, _) = build_path_gr(len);

    let mst_nk = nk.minimum_spanning_tree();
    let mst_pg = pg.minimum_spanning_tree();
    let mst_rx = rx.minimum_spanning_tree();
    let mst_gr = gr.minimum_spanning_tree();

    // MST of a connected graph on n nodes has n-1 edges
    assert_eq!(mst_nk.len(), len - 1, "NK: MST edge count");
    assert_eq!(mst_pg.len(), len - 1, "PG: MST edge count");
    assert_eq!(mst_rx.len(), len - 1, "RX: MST edge count");
    assert_eq!(mst_gr.len(), len - 1, "GR: MST edge count");
}

// ─── Topological Sort ─────────────────────────────────────────────────────────

#[test]
fn test_topological_sort_path_all_backends() {
    let len = 4;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);
    let (rx, nodes_rx) = build_path_rx(len);
    let (gr, nodes_gr) = build_path_gr(len);

    let order_nk = nk.topological_sort().expect("NK: topo sort on DAG");
    let order_pg = pg.topological_sort().expect("PG: topo sort on DAG");
    let order_rx = rx.topological_sort().expect("RX: topo sort on DAG");
    let order_gr = gr.topological_sort().expect("GR: topo sort on DAG");

    // In a path 1→2→3→4, topological order must have each node before its successors
    let pos_nk: HashMap<u64, usize> = order_nk.iter().enumerate().map(|(i, &n)| (n, i)).collect();
    for i in 0..len - 1 {
        assert!(
            pos_nk[&nodes_nk[i]] < pos_nk[&nodes_nk[i + 1]],
            "NK: topo order violated at position {i}"
        );
    }

    let pos_pg: HashMap<u64, usize> = order_pg.iter().enumerate().map(|(i, &n)| (n, i)).collect();
    for i in 0..len - 1 {
        assert!(
            pos_pg[&nodes_pg[i]] < pos_pg[&nodes_pg[i + 1]],
            "PG: topo order violated at position {i}"
        );
    }

    let pos_rx: HashMap<u64, usize> = order_rx.iter().enumerate().map(|(i, &n)| (n, i)).collect();
    for i in 0..len - 1 {
        assert!(pos_rx[&nodes_rx[i]] < pos_rx[&nodes_rx[i + 1]], "RX: topo order violated");
    }

    let pos_gr: HashMap<u64, usize> = order_gr.iter().enumerate().map(|(i, &n)| (n, i)).collect();
    for i in 0..len - 1 {
        assert!(pos_gr[&nodes_gr[i]] < pos_gr[&nodes_gr[i + 1]], "GR: topo order violated");
    }
}

#[test]
fn test_topological_sort_cycle_returns_err() {
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();
    let (rx, _, _, _) = build_triangle_rx();
    let (gr, _, _, _) = build_triangle_gr();

    assert!(nk.topological_sort().is_err(), "NK: cycle should cause topo sort to fail");
    assert!(pg.topological_sort().is_err(), "PG: cycle should cause topo sort to fail");
    assert!(rx.topological_sort().is_err(), "RX: cycle should cause topo sort to fail");
    assert!(gr.topological_sort().is_err(), "GR: cycle should cause topo sort to fail");
}

// ─── Is DAG ───────────────────────────────────────────────────────────────────

#[test]
fn test_is_dag_all_backends() {
    let (nk_tri, _, _, _) = build_triangle_nk();
    let (pg_tri, _, _, _) = build_triangle_pg();
    let (rx_tri, _, _, _) = build_triangle_rx();
    let (gr_tri, _, _, _) = build_triangle_gr();

    assert!(!nk_tri.is_dag(), "NK: triangle is not a DAG");
    assert!(!pg_tri.is_dag(), "PG: triangle is not a DAG");
    assert!(!rx_tri.is_dag(), "RX: triangle is not a DAG");
    assert!(!gr_tri.is_dag(), "GR: triangle is not a DAG");

    let (nk_path, _) = build_path_nk(4);
    let (pg_path, _) = build_path_pg(4);
    let (rx_path, _) = build_path_rx(4);
    let (gr_path, _) = build_path_gr(4);

    assert!(nk_path.is_dag(), "NK: path is a DAG");
    assert!(pg_path.is_dag(), "PG: path is a DAG");
    assert!(rx_path.is_dag(), "RX: path is a DAG");
    assert!(gr_path.is_dag(), "GR: path is a DAG");
}

// ─── Coloring ─────────────────────────────────────────────────────────────────

#[test]
fn test_node_coloring_validity_all_backends() {
    // Any valid coloring: adjacent nodes must have different colors
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();
    let (rx, _, _, _) = build_triangle_rx();
    let (gr, _, _, _) = build_triangle_gr();

    let coloring_nk = nk.node_coloring();
    let coloring_pg = pg.node_coloring();
    let coloring_rx = rx.node_coloring();
    let coloring_gr = gr.node_coloring();

    // Validate: for each edge (u,v), color[u] != color[v]
    fn validate_coloring<G: GraphBackend>(g: &G, coloring: &HashMap<u64, usize>, label: &str) {
        for edge in g.all_edges() {
            let eid = edge.id();
            let (src, dst) = g.get_edge_endpoints(eid).unwrap();
            let c_src = coloring.get(&src).unwrap_or_else(|| panic!("{label}: missing color for {src}"));
            let c_dst = coloring.get(&dst).unwrap_or_else(|| panic!("{label}: missing color for {dst}"));
            assert_ne!(c_src, c_dst, "{label}: adjacent nodes {src}→{dst} have same color {c_src}");
        }
    }

    validate_coloring(&nk, &coloring_nk, "NK");
    validate_coloring(&pg, &coloring_pg, "PG");
    validate_coloring(&rx, &coloring_rx, "RX");
    validate_coloring(&gr, &coloring_gr, "GR");
}

#[test]
fn test_chromatic_number_triangle() {
    // A triangle needs at least 2 colors (directed, each node has in-degree 1, out-degree 1)
    // Greedy coloring gives at least 2 colors; exact number depends on algorithm
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();

    let chi_nk = nk.chromatic_number();
    let chi_pg = pg.chromatic_number();

    // Must be at least 2 (adjacency exists); at most 3 (triangle upper bound)
    assert!(chi_nk >= 2, "NK: triangle needs at least 2 colors, got {chi_nk}");
    assert!(chi_pg >= 2, "PG: triangle needs at least 2 colors, got {chi_pg}");
    assert_eq!(chi_nk, chi_pg, "NK vs PG: chromatic number should match");
}

// ─── Community Detection ──────────────────────────────────────────────────────

#[test]
fn test_louvain_two_cliques_all_backends() {
    // Two disconnected triangles → should detect 2 communities
    let mut nk = NetworKitRustBackend::new();
    let a1 = cn(&mut nk, &["A"]);
    let a2 = cn(&mut nk, &["A"]);
    let a3 = cn(&mut nk, &["A"]);
    nk.create_relationship(a1, a2, "E", IndexMap::new()).unwrap();
    nk.create_relationship(a2, a3, "E", IndexMap::new()).unwrap();
    nk.create_relationship(a3, a1, "E", IndexMap::new()).unwrap();
    let b1 = cn(&mut nk, &["B"]);
    let b2 = cn(&mut nk, &["B"]);
    let b3 = cn(&mut nk, &["B"]);
    nk.create_relationship(b1, b2, "E", IndexMap::new()).unwrap();
    nk.create_relationship(b2, b3, "E", IndexMap::new()).unwrap();
    nk.create_relationship(b3, b1, "E", IndexMap::new()).unwrap();

    let communities = nk.louvain_communities(None);
    // Louvain is a non-deterministic approximation; on small disconnected graphs
    // it may split cliques further. The only hard guarantee is:
    //   - At least 2 communities (the two disconnected components must differ)
    //   - At most 6 communities (one per node)
    //   - All 6 nodes are assigned a community
    assert!(communities.num_communities() >= 2,
        "NK: two disconnected cliques should produce >= 2 communities, got {}", communities.num_communities());
    assert!(communities.num_communities() <= 6,
        "NK: 6-node graph cannot have > 6 communities, got {}", communities.num_communities());
    assert_eq!(communities.node_to_community.len(), 6, "NK: all 6 nodes should be assigned a community");
    let _ = (a1, a2, a3, b1, b2, b3);
}

#[test]
fn test_label_propagation_all_backends() {
    // Label propagation should find at least 1 community on any graph
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();

    let lp_nk = nk.label_propagation_communities();
    let lp_pg = pg.label_propagation_communities();

    assert!(lp_nk.num_communities() >= 1, "NK: label propagation should find at least 1 community");
    assert!(lp_pg.num_communities() >= 1, "PG: label propagation should find at least 1 community");
}

// ─── Max Flow ─────────────────────────────────────────────────────────────────

#[test]
fn test_max_flow_all_backends() {
    // Source→A→Sink and Source→B→Sink, each edge capacity 1.0 (default) → max flow = 2
    fn build_flow<G: GraphBackend + Default>() -> (G, u64, u64) {
        let mut g = G::default();
        let source = cn(&mut g, &["S"]);
        let a = cn(&mut g, &["A"]);
        let b = cn(&mut g, &["B"]);
        let sink = cn(&mut g, &["T"]);
        g.create_relationship(source, a, "E", IndexMap::new()).unwrap();
        g.create_relationship(source, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(a, sink, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, sink, "E", IndexMap::new()).unwrap();
        (g, source, sink)
    }

    let (nk, src_nk, snk_nk) = build_flow::<NetworKitRustBackend>();
    let (pg, src_pg, snk_pg) = build_flow::<PropertyGraph>();

    // NK: max_flow returns Option<(f64, HashMap<...>)>
    let flow_nk = nk.max_flow(src_nk, snk_nk)
        .expect("NK: max_flow should succeed");
    let flow_pg = pg.max_flow(src_pg, snk_pg)
        .expect("PG: max_flow should succeed");

    assert!(
        approx_eq(flow_nk.0, 2.0),
        "NK: max flow should be 2.0, got {}", flow_nk.0
    );
    assert!(
        approx_eq(flow_pg.0, 2.0),
        "PG: max flow should be 2.0, got {}", flow_pg.0
    );
}

// ─── Closeness Centrality ─────────────────────────────────────────────────────

#[test]
fn test_closeness_centrality_all_backends() {
    let len = 5;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);

    let cc_nk = nk.closeness_centrality(true);
    let cc_pg = pg.closeness_centrality(true);

    // Cross-backend consistency for middle node
    assert!(
        approx_eq(cc_nk[&nodes_nk[2]], cc_pg[&nodes_pg[2]]),
        "NK vs PG: closeness centrality mismatch: {} vs {}",
        cc_nk[&nodes_nk[2]], cc_pg[&nodes_pg[2]]
    );
}

// ─── Transitive Closure ───────────────────────────────────────────────────────

#[test]
fn test_transitive_closure_path_graph() {
    // Path 1→2→3→4: transitive closure must include 1→3, 1→4, 2→4
    let len = 4;
    let (nk, nodes_nk) = build_path_nk(len);
    let (pg, nodes_pg) = build_path_pg(len);

    // NK returns Vec<(u64,u64)>, PG returns HashSet<(u64,u64)>
    let tc_nk: HashSet<(u64, u64)> = nk.transitive_closure().into_iter().collect();
    let tc_pg: HashSet<(u64, u64)> = pg.transitive_closure();

    // Check all forward reachability pairs are present
    for i in 0..len {
        for j in (i + 1)..len {
            let pair_nk = (nodes_nk[i], nodes_nk[j]);
            let pair_pg = (nodes_pg[i], nodes_pg[j]);
            assert!(tc_nk.contains(&pair_nk),
                "NK: transitive closure missing ({},{})", nodes_nk[i], nodes_nk[j]);
            assert!(tc_pg.contains(&pair_pg),
                "PG: transitive closure missing ({},{})", nodes_pg[i], nodes_pg[j]);
        }
    }
}

// ─── Node/Edge count consistency ──────────────────────────────────────────────

#[test]
fn test_node_edge_counts_all_backends() {
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();
    let (rx, _, _, _) = build_triangle_rx();
    let (gr, _, _, _) = build_triangle_gr();

    assert_eq!(nk.node_count(), 3, "NK: node count");
    assert_eq!(pg.node_count(), 3, "PG: node count");
    assert_eq!(rx.node_count(), 3, "RX: node count");
    assert_eq!(gr.node_count(), 3, "GR: node count");

    assert_eq!(nk.edge_count(), 3, "NK: edge count");
    assert_eq!(pg.edge_count(), 3, "PG: edge count");
    assert_eq!(rx.edge_count(), 3, "RX: edge count");
    assert_eq!(gr.edge_count(), 3, "GR: edge count");
}

#[test]
fn test_node_edge_counts_after_delete() {
    // Delete a node and verify counts update correctly
    let mut nk = NetworKitRustBackend::new();
    let a = cn(&mut nk, &["A"]);
    let b = cn(&mut nk, &["B"]);
    nk.create_relationship(a, b, "E", IndexMap::new()).unwrap();

    assert_eq!(nk.node_count(), 2);
    assert_eq!(nk.edge_count(), 1);

    nk.detach_delete_node(a).unwrap();

    assert_eq!(nk.node_count(), 1, "NK: node count after detach delete");
    // Edge is gone via cascading detach delete
    assert_eq!(nk.edge_count(), 0, "NK: edge count after detach delete");
}

// ─── Cypher execute consistency ───────────────────────────────────────────────

#[test]
fn test_cypher_create_match_all_backends() {
    use ocg::execute;
    use ocg::CypherValue;

    let mut nk = NetworKitRustBackend::new();
    let mut pg = PropertyGraph::new();
    let mut rx = RustworkxCoreBackend::new();
    let mut gr = GraphrsBackend::new();

    let query = "CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'}) RETURN a.name, b.name";

    let r_nk = execute(&mut nk, query).expect("NK: Cypher execute failed");
    let r_pg = execute(&mut pg, query).expect("PG: Cypher execute failed");
    let r_rx = execute(&mut rx, query).expect("RX: Cypher execute failed");
    let r_gr = execute(&mut gr, query).expect("GR: Cypher execute failed");

    assert_eq!(r_nk.rows.len(), 1, "NK: expected 1 row");
    assert_eq!(r_pg.rows.len(), 1, "PG: expected 1 row");
    assert_eq!(r_rx.rows.len(), 1, "RX: expected 1 row");
    assert_eq!(r_gr.rows.len(), 1, "GR: expected 1 row");

    for (label, result) in [("NK", &r_nk), ("PG", &r_pg), ("RX", &r_rx), ("GR", &r_gr)] {
        let row = &result.rows[0];
        assert_eq!(
            row.get("a.name"),
            Some(&CypherValue::String("Alice".into())),
            "{label}: a.name mismatch"
        );
        assert_eq!(
            row.get("b.name"),
            Some(&CypherValue::String("Bob".into())),
            "{label}: b.name mismatch"
        );
    }
}

#[test]
fn test_cypher_stats_all_backends() {
    use ocg::execute;

    let mut nk = NetworKitRustBackend::new();
    let mut pg = PropertyGraph::new();
    let mut rx = RustworkxCoreBackend::new();
    let mut gr = GraphrsBackend::new();

    let query = "CREATE (a:X), (b:Y), (a)-[:R]->(b)";

    let r_nk = execute(&mut nk, query).unwrap();
    let r_pg = execute(&mut pg, query).unwrap();
    let r_rx = execute(&mut rx, query).unwrap();
    let r_gr = execute(&mut gr, query).unwrap();

    for (label, r) in [("NK", &r_nk), ("PG", &r_pg), ("RX", &r_rx), ("GR", &r_gr)] {
        assert_eq!(r.stats.nodes_created, 2, "{label}: nodes_created");
        assert_eq!(r.stats.relationships_created, 1, "{label}: relationships_created");
    }
}

#[test]
fn test_cypher_match_filter_all_backends() {
    use ocg::execute;
    use ocg::CypherValue;

    let mut nk = NetworKitRustBackend::new();
    let mut pg = PropertyGraph::new();

    let setup = "CREATE (a:Person {name: 'Alice', age: 30}), (b:Person {name: 'Bob', age: 25})";
    execute(&mut nk, setup).unwrap();
    execute(&mut pg, setup).unwrap();

    let query = "MATCH (p:Person) WHERE p.age > 28 RETURN p.name";

    let r_nk = execute(&mut nk, query).expect("NK: filter query");
    let r_pg = execute(&mut pg, query).expect("PG: filter query");

    assert_eq!(r_nk.rows.len(), 1, "NK: should find 1 person over 28");
    assert_eq!(r_pg.rows.len(), 1, "PG: should find 1 person over 28");

    assert_eq!(
        r_nk.rows[0].get("p.name"),
        Some(&CypherValue::String("Alice".into())),
        "NK: should find Alice"
    );
    assert_eq!(
        r_pg.rows[0].get("p.name"),
        Some(&CypherValue::String("Alice".into())),
        "PG: should find Alice"
    );
}

// ─── Eulerian (Phase 1: AlgorithmEngine) ─────────────────────────────────────

#[test]
fn test_has_euler_circuit_triangle_all_backends() {
    // Directed triangle: each node has in-degree = out-degree = 1
    // As undirected: each node has degree 2 (even) → Euler circuit exists
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();
    let (rx, _, _, _) = build_triangle_rx();
    let (gr, _, _, _) = build_triangle_gr();

    assert!(nk.has_euler_circuit(), "NK: triangle should have Euler circuit");
    assert!(pg.has_euler_circuit(), "PG: triangle should have Euler circuit");
    assert!(rx.has_euler_circuit(), "RX: triangle should have Euler circuit");
    assert!(gr.has_euler_circuit(), "GR: triangle should have Euler circuit");
}

#[test]
fn test_has_euler_circuit_path_graph_false() {
    // Path 0-1-2-3: endpoints have odd degree → no Euler circuit
    let (nk, _) = build_path_nk(4);
    let (pg, _) = build_path_pg(4);
    let (rx, _) = build_path_rx(4);
    let (gr, _) = build_path_gr(4);

    assert!(!nk.has_euler_circuit(), "NK: path should NOT have Euler circuit");
    assert!(!pg.has_euler_circuit(), "PG: path should NOT have Euler circuit");
    assert!(!rx.has_euler_circuit(), "RX: path should NOT have Euler circuit");
    assert!(!gr.has_euler_circuit(), "GR: path should NOT have Euler circuit");
}

#[test]
fn test_euler_circuit_triangle_all_backends() {
    // Triangle has an Euler circuit — verify it visits 3 edges and returns to start
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();

    let circuit_nk = nk.euler_circuit().expect("NK: triangle should produce Euler circuit");
    let circuit_pg = pg.euler_circuit().expect("PG: triangle should produce Euler circuit");

    // Circuit: first == last, length = edges + 1 = 4
    assert_eq!(circuit_nk.first(), circuit_nk.last(), "NK: circuit should be closed");
    assert_eq!(circuit_pg.first(), circuit_pg.last(), "PG: circuit should be closed");
    assert_eq!(circuit_nk.len(), 4, "NK: 3-edge circuit has 4 nodes (incl. return)");
    assert_eq!(circuit_pg.len(), 4, "PG: 3-edge circuit has 4 nodes (incl. return)");
}

#[test]
fn test_has_euler_path_path_graph_all_backends() {
    // Path 0-1-2-3: 2 odd-degree nodes → Euler path exists
    let (nk, _) = build_path_nk(4);
    let (pg, _) = build_path_pg(4);
    let (rx, _) = build_path_rx(4);
    let (gr, _) = build_path_gr(4);

    assert!(nk.has_euler_path(), "NK: path graph should have Euler path");
    assert!(pg.has_euler_path(), "PG: path graph should have Euler path");
    assert!(rx.has_euler_path(), "RX: path graph should have Euler path");
    assert!(gr.has_euler_path(), "GR: path graph should have Euler path");
}

// ─── Planarity (Phase 1: AlgorithmEngine) ────────────────────────────────────

#[test]
fn test_is_planar_triangle_all_backends() {
    // Triangle is planar (K3 is a planar graph)
    let (nk, _, _, _) = build_triangle_nk();
    let (pg, _, _, _) = build_triangle_pg();
    let (rx, _, _, _) = build_triangle_rx();
    let (gr, _, _, _) = build_triangle_gr();

    assert!(nk.is_planar(), "NK: triangle is planar");
    assert!(pg.is_planar(), "PG: triangle is planar");
    assert!(rx.is_planar(), "RX: triangle is planar");
    assert!(gr.is_planar(), "GR: triangle is planar");
}

#[test]
fn test_is_planar_path_all_backends() {
    // Path graph is always planar
    let (nk, _) = build_path_nk(5);
    let (pg, _) = build_path_pg(5);

    assert!(nk.is_planar(), "NK: path graph is planar");
    assert!(pg.is_planar(), "PG: path graph is planar");
}

// ─── AlgorithmEngine generic test ────────────────────────────────────────────

/// Verify the AlgorithmEngine trait works generically across all backends.
fn run_basic_algorithms<G: GraphBackend + Default + AlgorithmEngine>(label: &str) {
    let mut g = G::default();
    let n1 = g.create_node(["A"], IndexMap::new());
    let n2 = g.create_node(["B"], IndexMap::new());
    let n3 = g.create_node(["C"], IndexMap::new());
    g.create_relationship(n1, n2, "E", IndexMap::new()).unwrap();
    g.create_relationship(n2, n3, "E", IndexMap::new()).unwrap();

    // DAG algorithms
    assert!(g.is_dag(), "{label}: path is a DAG");
    let order = g.topological_sort().expect("{label}: topo sort on path");
    assert_eq!(order.len(), 3, "{label}: topo order has 3 nodes");

    // Path algorithms
    let path = g.bfs_path(n1, n3).expect("{label}: BFS path n1→n3");
    assert_eq!(path.len(), 3, "{label}: BFS path has 3 nodes");

    let (cost, _) = g.dijkstra_path(n1, n3).expect("{label}: Dijkstra n1→n3");
    assert!((cost - 2.0).abs() < 1e-6, "{label}: Dijkstra cost should be 2.0, got {cost}");

    // Centrality
    let pr = g.pagerank(0.85, 100);
    assert_eq!(pr.len(), 3, "{label}: PageRank has 3 entries");

    // Components
    let comps = g.connected_components_list();
    assert_eq!(comps.len(), 1, "{label}: path is 1 connected component");
}

#[test]
fn test_algorithm_engine_all_backends_generic() {
    run_basic_algorithms::<NetworKitRustBackend>("NK");
    run_basic_algorithms::<PropertyGraph>("PG");
    run_basic_algorithms::<RustworkxCoreBackend>("RX");
    run_basic_algorithms::<GraphrsBackend>("GR");
}

//
// ─── NOTE: Distributed algorithm tests ───────────────────────────────────────
//
// The following tests are PLANNED for Phase 4-5 (BSP distributed algorithms).
// They verify correctness by comparing distributed results against local results:
//
// #[test]
// fn test_distributed_pagerank_matches_local() {
//     // Build same graph, partition it across 2 shards
//     // Run DistributedAlgorithmRunner::pagerank()
//     // Run local NetworKitRustBackend::pagerank() on full graph
//     // Assert results match within EPSILON for all nodes
// }
//
// #[test]
// fn test_distributed_bfs_matches_local() {
//     // Same approach: partition graph, run BSP BFS from source node
//     // Compare path lengths with local BFS
// }
//
// #[test]
// fn test_distributed_betweenness_matches_local() {
//     // Most expensive to validate — requires full Brandes over all shards
//     // Compare with local NetworKitRustBackend::betweenness_centrality()
// }
