// 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

#![allow(dead_code)]

use swh_graph::graph_builder::{BuiltGraph, GraphBuilder};
use swh_graph::labels::Permission;
use swh_graph::swhid;
use swh_graph::{NodeType, SWHID};

/// Creates a graph builder with n revisions, each with a directory and content.
/// Returns the builder (not built) and the vector of revision NodeIds.
fn get_n_rev_graph_builder(n: usize) -> (GraphBuilder, Vec<usize>) {
    let mut builder = GraphBuilder::default();
    let snp = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000000))
        .unwrap()
        .done();

    let mut rev_nodes = Vec::new();
    for i in 1..=n {
        let mut hash = [0u8; 20];
        hash[19] = i as u8;
        let rev = builder
            .node(SWHID {
                namespace_version: 1,
                node_type: NodeType::Revision,
                hash,
            })
            .unwrap()
            .done();
        rev_nodes.push(rev);
    }

    let mut dir_nodes = Vec::new();
    for i in 1..=n {
        let mut dir_hash = [0u8; 20];
        dir_hash[19] = 0x10 + i as u8;
        let dir = builder
            .node(SWHID {
                namespace_version: 1,
                node_type: NodeType::Directory,
                hash: dir_hash,
            })
            .unwrap()
            .done();
        dir_nodes.push(dir);
    }

    for i in 1..=n {
        let mut cnt_hash = [0u8; 20];
        cnt_hash[19] = 0x20 + i as u8;
        let cnt = builder
            .node(SWHID {
                namespace_version: 1,
                node_type: NodeType::Content,
                hash: cnt_hash,
            })
            .unwrap()
            .done();

        let idx = i - 1;
        builder.arc(rev_nodes[idx], dir_nodes[idx]);
        builder.dir_arc(dir_nodes[idx], cnt, Permission::Content, b"test.txt");
        builder.arc(snp, rev_nodes[idx]);
    }

    (builder, rev_nodes)
}

/// Creates a linear graph with n revisions where rev[n] -> rev[n-1] -> ... -> rev[1]
pub fn get_n_rev_linear_graph(n: usize) -> BuiltGraph {
    let (mut builder, rev_nodes) = get_n_rev_graph_builder(n);

    for i in (1..rev_nodes.len()).rev() {
        builder.arc(rev_nodes[i], rev_nodes[i - 1]);
    }

    builder.done().unwrap()
}

pub fn get_4rev_graph() -> GraphBuilder {
    let (builder, _) = get_n_rev_graph_builder(4);
    builder
}

pub fn get_merge_graph() -> BuiltGraph {
    let mut builder = get_4rev_graph();

    // rev3 is a merge of rev1 and rev2
    builder.arc(3, 1);
    builder.arc(3, 2);

    builder.done().unwrap()
}

pub fn get_3_way_merge_graph() -> BuiltGraph {
    let mut builder = get_4rev_graph();

    // rev4 is a merge of rev1, rev2, and rev3
    builder.arc(4, 1);
    builder.arc(4, 2);
    builder.arc(4, 3);

    builder.done().unwrap()
}

pub fn get_10rev_linear_graph() -> BuiltGraph {
    get_n_rev_linear_graph(10)
}

/// Creates a graph with 4 revisions where rev3 is a merge of rev2 and rev1 (shortcut),
/// and rev4 has rev3 as its only parent.
///
/// ```text
/// rev1 <- rev2 <- rev3 (merge) <- rev4 (fix)
///      <----------/  (shortcut: rev3 also has rev1 as parent)
/// ```
///
/// rev_distance(rev1, rev4) = 2 via rev4->rev3->rev1, not 3 via rev4->rev3->rev2->rev1.
pub fn get_merge_shortcut_graph() -> BuiltGraph {
    let mut builder = get_4rev_graph();

    builder.arc(4, 3); // rev4 -> rev3 (single parent, non-merge fix)
    builder.arc(3, 2); // rev3 -> rev2 (merge parent 1)
    builder.arc(3, 1); // rev3 -> rev1 (merge parent 2, shortcut)
    builder.arc(2, 1); // rev2 -> rev1

    builder.done().unwrap()
}

/// Creates a graph where two independent root commits each create a file, which is then merged.
///
/// ```text
/// rev1 (root)  \
///               -> rev3 (merge) <- rev4 (fix)
/// rev2 (root)  /
/// ```
///
/// Both rev1 and rev2 create the file independently (no common ancestors),
/// so find_file_creation_revs returns [rev1, rev2].
pub fn get_merge_2roots_graph() -> BuiltGraph {
    let mut builder = get_4rev_graph();

    builder.arc(4, 3); // rev4 -> rev3 (fix's parent is the merge)
    builder.arc(3, 1); // rev3 -> rev1 (merge parent 1)
    builder.arc(3, 2); // rev3 -> rev2 (merge parent 2)
    // rev1 and rev2 have no parents (root commits)

    builder.done().unwrap()
}

pub fn get_linear_2files_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();
    let snp = builder
        .node(swhid!(swh:1:snp: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 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 cnt_ignored1 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000021))
        .unwrap()
        .done();
    let cnt_ignored2 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000022))
        .unwrap()
        .done();
    let cnt_ignored3 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000023))
        .unwrap()
        .done();
    let cnt_code1 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000024))
        .unwrap()
        .done();
    let cnt_code2 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000025))
        .unwrap()
        .done();
    let cnt_code3 = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000026))
        .unwrap()
        .done();

    // snapshot
    builder.arc(snp, rev1);
    builder.arc(snp, rev2);
    builder.arc(snp, rev3);
    builder.arc(snp, rev4);

    // rev1: both files at v1
    builder.arc(rev1, dir1);
    builder.dir_arc(dir1, cnt_ignored1, Permission::Content, b"ignored.txt");
    builder.dir_arc(dir1, cnt_code1, Permission::Content, b"code.rs");

    // rev2: update ignored.txt to v2
    builder.arc(rev2, dir2);
    builder.dir_arc(dir2, cnt_ignored2, Permission::Content, b"ignored.txt");
    builder.dir_arc(dir2, cnt_code1, Permission::Content, b"code.rs");

    // rev3: update code.rs to v2
    builder.arc(rev3, dir3);
    builder.dir_arc(dir3, cnt_ignored2, Permission::Content, b"ignored.txt");
    builder.dir_arc(dir3, cnt_code2, Permission::Content, b"code.rs");

    // rev4: update both to v3
    builder.arc(rev4, dir4);
    builder.dir_arc(dir4, cnt_ignored3, Permission::Content, b"ignored.txt");
    builder.dir_arc(dir4, cnt_code3, Permission::Content, b"code.rs");

    // Linear chain: rev1 <- rev2 <- rev3 <- rev4
    builder.arc(4, 3);
    builder.arc(3, 2);
    builder.arc(2, 1);

    builder.done().unwrap()
}

#[allow(unused_macros)]
macro_rules! get_content_retriever {
    ($graph:expr, [$($content:expr),* $(,)?]) => {{
        use swh_contents::retriever::{ContentCache, InMemoryContentCache};

        let mut swhid = SWHID {
            namespace_version: 1,
            node_type: NodeType::Content,
            hash: [0; 20],
        };
        let content_retriever = InMemoryContentCache::default();

        let i = 0x20;
        $(
            let i = i+1;
            swhid.hash[19] = i;
            let node_id = $graph.properties().node_id(swhid).unwrap();
            content_retriever.set(node_id, $content).await.unwrap();
        )*

        std::sync::Arc::new(content_retriever)
    }}
}

#[allow(unused_imports)]
pub(crate) use get_content_retriever;
