// Copyright (C) 2025  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 std::sync::Arc;

use anyhow::Result;
use swh_graph::graph_builder::GraphBuilder;
use swh_graph::labels::Permission;
use swh_graph::swhid;
use swh_graph_stdlib::connectivity::SubgraphWccs;

use swh_osv::traversals::list_reachable_contents_from_subgraph;

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

#[test]
fn test_list_reachable_contents_single_revision() -> Result<()> {
    let graph = Arc::new(graphs::get_10rev_linear_graph());

    let wccs = SubgraphWccs::build_from_nodes(graph.clone(), vec![1], false)?;

    let contents = list_reachable_contents_from_subgraph(graph.clone(), &wccs)?;

    // Should find content node 21 (cnt1)
    let content_nodes: Vec<_> = contents.iter().map(|n| *n).collect();
    assert_eq!(content_nodes, vec![21]);

    Ok(())
}

#[test]
fn test_list_reachable_contents_multiple_revisions() -> Result<()> {
    let graph = Arc::new(graphs::get_10rev_linear_graph());

    let wccs = SubgraphWccs::build_from_nodes(graph.clone(), vec![1, 2, 3], false)?;

    let contents = list_reachable_contents_from_subgraph(graph.clone(), &wccs)?;

    // Should find content nodes for cnt1, cnt2, and cnt3
    let mut content_nodes: Vec<_> = contents.iter().map(|n| *n).collect();
    content_nodes.sort();
    assert_eq!(content_nodes, vec![21, 22, 23]);

    Ok(())
}

#[test]
fn test_list_reachable_contents_nested_directories() -> Result<()> {
    let mut builder = GraphBuilder::default();

    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .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 cnt1 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000021))
        .unwrap()
        .done();
    let cnt2 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000022))
        .unwrap()
        .done();

    // rev1 -> dir1 -> dir2 -> cnt1
    //              -> cnt2
    builder.arc(rev1, dir1);
    builder.dir_arc(dir1, dir2, Permission::ExecutableContent, b"subdir");
    builder.dir_arc(dir1, cnt2, Permission::Content, b"file2.txt");
    builder.dir_arc(dir2, cnt1, Permission::Content, b"file1.txt");

    let graph = Arc::new(builder.done()?);

    let wccs = SubgraphWccs::build_from_nodes(graph.clone(), vec![0], false)?;

    let contents = list_reachable_contents_from_subgraph(graph.clone(), &wccs)?;

    let mut content_nodes: Vec<_> = contents.iter().map(|n| *n).collect();
    content_nodes.sort();
    // Should find both cnt1 and cnt2
    assert_eq!(content_nodes, vec![3, 4]);

    Ok(())
}

#[test]
fn test_list_reachable_contents_empty_revision() -> Result<()> {
    let mut builder = GraphBuilder::default();

    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();
    let dir1 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000011))
        .unwrap()
        .done();

    // Revision points to an empty directory
    builder.arc(rev1, dir1);

    let graph = Arc::new(builder.done()?);

    let wccs = SubgraphWccs::build_from_nodes(graph.clone(), vec![0], false)?;

    let contents = list_reachable_contents_from_subgraph(graph.clone(), &wccs)?;

    // Should find no contents
    let content_nodes: Vec<_> = contents.iter().map(|n| *n).collect();
    assert_eq!(content_nodes, Vec::<usize>::new());

    Ok(())
}

#[test]
fn test_list_reachable_contents_subset_of_revisions() -> Result<()> {
    let graph = Arc::new(graphs::get_10rev_linear_graph());

    // Only include rev2 in the subgraph, not rev1 or rev3
    let wccs = SubgraphWccs::build_from_nodes(graph.clone(), vec![2], false)?;

    let contents = list_reachable_contents_from_subgraph(graph.clone(), &wccs)?;

    // Should only find cnt2, not cnt1 or cnt3
    let content_nodes: Vec<_> = contents.iter().map(|n| *n).collect();
    assert_eq!(content_nodes, vec![22]);

    Ok(())
}
