// Copyright (C) 2025-2026  The Software Heritage developers
// See the AUTHORS file at the top-level directory of this distribution
// License: GNU General Public License version 3, or any later version
// See top-level LICENSE file for more information

use rapidhash::RapidHashMap;
use swh_graph::graph_builder::GraphBuilder;
use swh_graph::swhid;

use swh_osv::cherrypicks;

#[path = "./graphs.rs"]
mod graphs;

fn build_test_graph() -> GraphBuilder {
    let mut builder = GraphBuilder::default();

    // Create 6 revisions for testing
    let rev0 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000000))
        .unwrap()
        .done();
    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();
    let rev2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000002))
        .unwrap()
        .done();
    let rev3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000003))
        .unwrap()
        .done();
    let rev4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000004))
        .unwrap()
        .done();
    let rev5 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000005))
        .unwrap()
        .done();

    // Add directories for each revision (required by graph structure)
    let dir0 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000010))
        .unwrap()
        .done();
    let dir1 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000011))
        .unwrap()
        .done();
    let dir2 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000012))
        .unwrap()
        .done();
    let dir3 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000013))
        .unwrap()
        .done();
    let dir4 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000014))
        .unwrap()
        .done();
    let dir5 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000015))
        .unwrap()
        .done();

    builder.arc(rev0, dir0);
    builder.arc(rev1, dir1);
    builder.arc(rev2, dir2);
    builder.arc(rev3, dir3);
    builder.arc(rev4, dir4);
    builder.arc(rev5, dir5);

    builder
}

#[test]
fn test_reduce_transitive_cherrypicks() {
    let builder = build_test_graph();
    let graph = builder.done().unwrap();

    // Rev 0 is cherry-picked from rev 1, rev 2, and rev 3
    // Rev 1 is cherry-picked from rev 2
    // Rev 3 is independent
    // So rev 0 should reduce to [1, 3]
    let mut cherrypicks_to_originals = RapidHashMap::default();
    cherrypicks_to_originals.insert(0, vec![1, 2, 3]);
    cherrypicks_to_originals.insert(1, vec![2]);

    let (stats, reduced) =
        cherrypicks::reduce_transitive_cherrypicks(&graph, &cherrypicks_to_originals);

    let expected_stats = cherrypicks::ReductionStats {
        total_cherrypicks: 2,
        total_nonsingleton_cherrypicks: 1,
        reducible_cherrypickedfrom: 0,
        partially_reducible_cherrypickedfrom: 1,
    };
    assert_eq!(stats, expected_stats);

    // Sort vectors for deterministic comparison
    let reduced_sorted: RapidHashMap<_, _> = reduced
        .into_iter()
        .map(|(k, mut v)| {
            v.sort();
            (k, v)
        })
        .collect();

    let mut expected = RapidHashMap::default();
    expected.insert(0, vec![1, 3]);
    expected.insert(1, vec![2]);
    assert_eq!(reduced_sorted, expected);
}

#[test]
fn test_reduce_transitive_cherrypicks_chain() {
    let builder = build_test_graph();
    let graph = builder.done().unwrap();

    // Chain: Rev 0 <- Rev 1 <- Rev 2 <- Rev 3
    // Rev 0 is cherry-picked from all of them
    // Should reduce to just [1]
    let mut cherrypicks_to_originals = RapidHashMap::default();
    cherrypicks_to_originals.insert(0, vec![1, 2, 3]);
    cherrypicks_to_originals.insert(1, vec![2, 3]);
    cherrypicks_to_originals.insert(2, vec![3]);

    let (stats, reduced) =
        cherrypicks::reduce_transitive_cherrypicks(&graph, &cherrypicks_to_originals);

    let expected_stats = cherrypicks::ReductionStats {
        total_cherrypicks: 3,
        total_nonsingleton_cherrypicks: 2,
        reducible_cherrypickedfrom: 2,
        partially_reducible_cherrypickedfrom: 0,
    };
    assert_eq!(stats, expected_stats);

    let mut expected = RapidHashMap::default();
    expected.insert(0, vec![1]);
    expected.insert(1, vec![2]);
    expected.insert(2, vec![3]);
    assert_eq!(reduced, expected);
}
