// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0

//! Algorithm implementations for `GraphrsBackend`.
//!
//! Implements `PetgraphAlgorithms` — giving the Graphrs backend access to all
//! 40+ algorithms ported to NetworKit, in addition to Graphrs's own community
//! detection algorithms (Louvain, Leiden, Label Propagation).
//!
//! All algorithms are reflected via the shared petgraph → NetworKit conversion.

use petgraph::stable_graph::StableDiGraph;

use crate::graph::types::{GraphEdge, GraphNode};
use crate::graph::backends::petgraph_algorithms::PetgraphAlgorithms;

use super::backend::GraphrsBackend;

impl PetgraphAlgorithms for GraphrsBackend {
    fn petgraph_inner(&self) -> &StableDiGraph<GraphNode, GraphEdge> {
        &self.graph
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use indexmap::IndexMap;

    fn build_two_community_graph() -> (GraphrsBackend, Vec<u64>) {
        let mut g = GraphrsBackend::new();

        // Community 1: tightly connected triangle (nodes 0-2)
        let n0 = g.create_node(["A"], IndexMap::new()).id;
        let n1 = g.create_node(["A"], IndexMap::new()).id;
        let n2 = g.create_node(["A"], IndexMap::new()).id;
        // Community 2: tightly connected triangle (nodes 3-5)
        let n3 = g.create_node(["B"], IndexMap::new()).id;
        let n4 = g.create_node(["B"], IndexMap::new()).id;
        let n5 = g.create_node(["B"], IndexMap::new()).id;

        // Dense within community 1
        g.create_relationship(n0, n1, "KNOWS", IndexMap::new()).unwrap();
        g.create_relationship(n1, n2, "KNOWS", IndexMap::new()).unwrap();
        g.create_relationship(n0, n2, "KNOWS", IndexMap::new()).unwrap();

        // Dense within community 2
        g.create_relationship(n3, n4, "KNOWS", IndexMap::new()).unwrap();
        g.create_relationship(n4, n5, "KNOWS", IndexMap::new()).unwrap();
        g.create_relationship(n3, n5, "KNOWS", IndexMap::new()).unwrap();

        // Single bridge between communities
        g.create_relationship(n2, n3, "BRIDGE", IndexMap::new()).unwrap();

        (g, vec![n0, n1, n2, n3, n4, n5])
    }

    // ── Community Detection (primary Graphrs algorithms) ───────────────────

    #[test]
    fn test_graphrs_louvain_all_nodes_assigned() {
        let (g, nodes) = build_two_community_graph();
        let result = g.louvain_communities(None);
        // Every node must be assigned to a community
        assert_eq!(result.node_to_community.len(), nodes.len());
    }

    #[test]
    fn test_graphrs_louvain_community_count() {
        let (g, _) = build_two_community_graph();
        let result = g.louvain_communities(None);
        // Two clear communities: should find at least 1, likely 2
        assert!(result.num_communities() >= 1);
    }

    #[test]
    fn test_graphrs_louvain_modularity_nonneg() {
        let (g, _) = build_two_community_graph();
        let result = g.louvain_communities(None);
        assert!(result.modularity >= 0.0);
    }

    #[test]
    fn test_graphrs_label_propagation_all_nodes() {
        let (g, nodes) = build_two_community_graph();
        let result = g.label_propagation_communities();
        assert_eq!(result.node_to_community.len(), nodes.len());
    }

    #[test]
    fn test_graphrs_girvan_newman_two_communities() {
        let (g, nodes) = build_two_community_graph();
        let result = g.girvan_newman_communities(2);
        assert_eq!(result.num_communities(), 2);
        assert_eq!(result.node_to_community.len(), nodes.len());
    }

    // ── Pathfinding (reflected from NetworKit) ───────────────────────────────

    #[test]
    fn test_graphrs_dijkstra_path() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        let mut w = IndexMap::new();
        w.insert("weight".to_string(), crate::graph::types::PropertyValue::Float(3.0));
        g.create_relationship(a, b, "E", w.clone()).unwrap();
        g.create_relationship(b, c, "E", w.clone()).unwrap();

        let result = g.dijkstra_path(a, c);
        assert!(result.is_some());
        let (cost, path) = result.unwrap();
        assert_eq!(cost, 6.0); // 3 + 3
        assert_eq!(path, vec![a, b, c]);
    }

    #[test]
    fn test_graphrs_astar_zero_heuristic() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();

        let result = g.astar_path(a, b, |_| 0.0);
        assert!(result.is_some());
        assert_eq!(result.unwrap().1, vec![a, b]);
    }

    // ── Centrality (reflected from NetworKit) ────────────────────────────────

    #[test]
    fn test_graphrs_degree_centrality() {
        let (g, nodes) = build_two_community_graph();
        let scores = g.degree_centrality();
        // Verify all nodes have a score
        for &n in &nodes {
            assert!(scores.contains_key(&n), "Node {} missing from centrality", n);
        }
    }

    #[test]
    fn test_graphrs_pagerank() {
        let (g, nodes) = build_two_community_graph();
        let scores = g.pagerank(0.85, 100);
        for &n in &nodes {
            assert!(scores.contains_key(&n));
            assert!(*scores.get(&n).unwrap() > 0.0);
        }
    }

    #[test]
    fn test_graphrs_betweenness_centrality() {
        let (g, nodes) = build_two_community_graph();
        let scores = g.betweenness_centrality(true);
        for &n in &nodes {
            assert!(scores.contains_key(&n));
        }
        // Bridge nodes (n2, n3) should have higher betweenness
        // (they lie on all cross-community paths)
    }

    // ── Spanning Trees (reflected from NetworKit) ────────────────────────────

    #[test]
    fn test_graphrs_mst() {
        let (g, nodes) = build_two_community_graph();
        let mst = g.minimum_spanning_tree();
        // n nodes → MST has n-1 edges
        assert_eq!(mst.len(), nodes.len() - 1);
    }

    // ── DAG Algorithms (reflected from NetworKit) ────────────────────────────

    #[test]
    fn test_graphrs_is_dag_false() {
        // A graph with a directed cycle is NOT a DAG
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, c, "E", IndexMap::new()).unwrap();
        g.create_relationship(c, a, "E", IndexMap::new()).unwrap(); // back edge → cycle
        assert!(!g.is_dag());
    }

    #[test]
    fn test_graphrs_is_dag_true() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, c, "E", IndexMap::new()).unwrap();
        assert!(g.is_dag());
    }

    #[test]
    fn test_graphrs_topological_sort_dag() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, c, "E", IndexMap::new()).unwrap();

        let order = g.topological_sort().unwrap();
        let pa = order.iter().position(|&x| x == a).unwrap();
        let pc = order.iter().position(|&x| x == c).unwrap();
        assert!(pa < pc);
    }

    // ── Coloring (reflected from NetworKit) ───────────────────────────────────

    #[test]
    fn test_graphrs_node_coloring() {
        let (g, nodes) = build_two_community_graph();
        let colors = g.node_coloring();
        assert_eq!(colors.len(), nodes.len());
    }

    #[test]
    fn test_graphrs_chromatic_number() {
        let (g, _) = build_two_community_graph();
        let chi = g.chromatic_number();
        // Triangle graph needs at minimum 2 colors for directed; 3 for undirected triangle
        assert!(chi >= 1);
    }

    // ── Matching (reflected from NetworKit) ───────────────────────────────────

    #[test]
    fn test_graphrs_max_cardinality_matching() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        let d = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, c, "E", IndexMap::new()).unwrap();
        g.create_relationship(c, d, "E", IndexMap::new()).unwrap();

        let matching = g.max_cardinality_matching_edges();
        assert!(!matching.is_empty());
    }

    // ── Network Flow (reflected from NetworKit) ────────────────────────────────

    #[test]
    fn test_graphrs_max_flow() {
        let mut g = GraphrsBackend::new();
        let s = g.create_node(["N"], IndexMap::new()).id;
        let m = g.create_node(["N"], IndexMap::new()).id;
        let t = g.create_node(["N"], IndexMap::new()).id;
        let mut w = IndexMap::new();
        w.insert("weight".to_string(), crate::graph::types::PropertyValue::Float(5.0));
        g.create_relationship(s, m, "E", w.clone()).unwrap();
        g.create_relationship(m, t, "E", w.clone()).unwrap();

        let (flow, _) = g.max_flow(s, t).unwrap();
        assert_eq!(flow, 5.0);
    }

    // ── Components (reflected from NetworKit) ─────────────────────────────────

    #[test]
    fn test_graphrs_strongly_connected() {
        let (g, _) = build_two_community_graph();
        let sccs = g.strongly_connected_components_list();
        // graph has bidirectional edges → cycles → some multi-node SCCs
        assert!(!sccs.is_empty());
    }

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

    #[test]
    fn test_graphrs_transitive_closure() {
        let mut g = GraphrsBackend::new();
        let a = g.create_node(["N"], IndexMap::new()).id;
        let b = g.create_node(["N"], IndexMap::new()).id;
        let c = g.create_node(["N"], IndexMap::new()).id;
        g.create_relationship(a, b, "E", IndexMap::new()).unwrap();
        g.create_relationship(b, c, "E", IndexMap::new()).unwrap();

        let tc = g.transitive_closure();
        assert!(tc.contains(&(a, c))); // a can reach c transitively
        assert!(tc.contains(&(a, b)));
        assert!(!tc.contains(&(c, a))); // c cannot reach a
    }
}
