// 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::num::NonZeroUsize;

use anyhow::Result;
use epserde::ser::Serialize;
use rayon::prelude::*;
use tempfile::TempDir;

use swh_digestmap::build::{
    build_sha1_git_table_from_keys, build_sha1_table_from_pairs, build_vfunc,
};
use swh_digestmap::{DigestMap, Sha1, Sha1Git};

fn test_data() -> Result<Vec<(Sha1Git, Sha1)>> {
    vec![
        (
            "0000000000000000000000000000000000000001",
            "1111111111111111111111111111111111111117",
        ),
        (
            "0000000000000000000000000000000000000002",
            "1111111111111111111111111111111111111118",
        ),
        (
            "0000000000000000000000000000000000000003",
            "1111111111111111111111111111111111111119",
        ),
    ]
    .into_iter()
    .map(|(sha1_git_hex, sha1_hex)| {
        let sha1_git = Sha1Git::from_hex(sha1_git_hex.as_bytes())?;
        let sha1 = Sha1::from_hex(sha1_hex.as_bytes())?;
        Ok((sha1_git, sha1))
    })
    .collect()
}

#[test]
fn test_digestmap() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let tables_dir = temp_dir.path();

    let test_data = test_data()?;
    let num_rows = test_data.len();
    let parallelism = NonZeroUsize::new(1).unwrap();

    let sha1_git_table = build_sha1_git_table_from_keys(
        test_data.iter().map(|(sha1_git, _)| *sha1_git).collect(),
        num_rows,
        tables_dir,
        parallelism,
    )?;

    let vfunc = build_vfunc(&sha1_git_table)?;

    build_sha1_table_from_pairs(
        test_data
            .par_iter()
            .map(|(sha1_git, sha1)| Ok((*sha1_git, *sha1))),
        &vfunc,
        sha1_git_table.len(),
        tables_dir,
        parallelism,
    )?;

    let vfunc_path = tables_dir.join(swh_digestmap::VFUNC_FILENAME);
    vfunc.store(&vfunc_path)?;

    let digestmap = DigestMap::new(tables_dir)?;

    for (sha1_git, expected_sha1) in &test_data {
        let sha1 = digestmap
            .sha1_from_sha1_git(*sha1_git)?
            .expect("sha1 should be found");
        assert_eq!(sha1, *expected_sha1);
    }

    let not_found = digestmap.sha1_from_sha1_git(Sha1Git::from_hex(
        b"0000000000000000000000000000000000000000",
    )?)?;
    assert!(not_found.is_none(), "unknown sha1_git should return None");

    Ok(())
}

#[test]
fn test_build_digestmap_with_duplicates() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let tables_dir = temp_dir.path();

    // Create test data with duplicates
    let mut test_data = test_data()?;
    let duplicate = test_data[0];
    test_data.push(duplicate);

    let num_rows = test_data.len();
    let parallelism = NonZeroUsize::new(1).unwrap();

    let sha1_git_table = build_sha1_git_table_from_keys(
        test_data.iter().map(|(sha1_git, _)| *sha1_git).collect(),
        num_rows,
        tables_dir,
        parallelism,
    )?;

    assert_ne!(sha1_git_table.len(), 4, "duplicate not removed");
    assert_eq!(sha1_git_table.len(), 3);

    let vfunc = build_vfunc(&sha1_git_table)?;

    let pairs_iter = test_data
        .par_iter()
        .map(|(sha1_git, sha1)| Ok((*sha1_git, *sha1)));

    build_sha1_table_from_pairs(
        pairs_iter,
        &vfunc,
        sha1_git_table.len(),
        tables_dir,
        parallelism,
    )?;

    let vfunc_path = tables_dir.join(swh_digestmap::VFUNC_FILENAME);
    vfunc.store(&vfunc_path)?;

    let digestmap = DigestMap::new(tables_dir)?;

    let (sha1_git, expected_sha1) = duplicate;
    let sha1 = digestmap
        .sha1_from_sha1_git(sha1_git)?
        .expect("sha1 should be found");
    assert_eq!(sha1, expected_sha1);

    Ok(())
}
