extern crate debcargo;

use std::{fs, path::Path};
use walkdir::WalkDir;

fn copy_tree(oldtree: &Path, newtree: &Path) -> Result<(), std::io::Error> {
    for entry in WalkDir::new(oldtree) {
        let entry = entry?;
        if entry.depth() == 0 {
            continue;
        }
        let oldpath = entry.path();
        let newpath = newtree.join(oldpath.strip_prefix(oldtree).unwrap());
        let ftype = entry.file_type();
        match ftype {
            f if f.is_dir() => {
                fs::create_dir(newpath)?;
            }
            f if f.is_file() => {
                fs::copy(oldpath, newpath)?;
            }
            #[cfg(unix)]
            f if f.is_symlink() => {
                use std::os::unix::fs::symlink;

                symlink(fs::read_link(oldpath)?, newpath)?;
            }
            _ => {}
        }
    }
    Ok(())
}

fn local_package_test(
    tmpdir: &Path,
    crate_name: &str,
    version: &str,
    success: bool,
) -> Option<tempfile::TempDir> {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));

    let crate_dir = Path::new(manifest_dir).join("tests").join(crate_name);
    let tempdir = tempfile::Builder::new()
        .prefix("debcargo")
        .tempdir_in(tmpdir)
        .expect("Should be able to create temporary directory");
    let crate_dir_copy = tempdir.path().join(crate_name);
    std::fs::create_dir(&crate_dir_copy).expect("Should be able to create temp. crate dir");
    copy_tree(&crate_dir, &crate_dir_copy).expect("Should be able to copy crate source dir");
    std::fs::rename(
        crate_dir_copy.join("Ograc.toml"),
        crate_dir_copy.join("Cargo.toml"),
    )
    .expect("Should be able to rename crate's Ograc.toml to Cargo.toml");
    let overlay_dir = tempdir.path().join(format!("{crate_name}-overlay"));
    std::fs::create_dir(&overlay_dir).expect("Should be able to create overlay dir");
    copy_tree(
        &manifest_dir
            .join("tests")
            .join(format!("{crate_name}-overlay")),
        &overlay_dir,
    )
    .expect("Should be able to copy overlay input to test dir");

    let output = std::process::Command::new(env!("CARGO_BIN_EXE_debcargo"))
        .env("DEBFULLNAME", "Debcargo Test")
        .env("DEBEMAIL", "debcargo@example.com")
        .arg("package")
        .arg("--config")
        .arg(overlay_dir.join("debian").join("debcargo.toml"))
        .arg("--directory")
        .arg(tempdir.path().join("output"))
        .arg("--no-overlay-write-back")
        .arg(crate_name)
        .arg(version)
        .output()
        .expect("Should be able to run `debcargo package`");

    if success {
        if !output.status.success() {
            eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
            eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
        }
        assert!(output.status.success());

        Some(tempdir)
    } else {
        assert!(!output.status.success());
        None
    }
}

#[test]
fn generate_package_with_crate_src() {
    let out_dir = Path::new(env!("CARGO_TARGET_TMPDIR"));
    if let Some(tempdir) = local_package_test(out_dir, "foobar", "0.1.0", true) {
        let actual = std::fs::read_to_string(tempdir.path().join("output/debian/control"))
            .expect("Should be able to read generated debian/control file");
        std::fs::write(out_dir.join("foobar.actual"), &actual)
            .expect("Should be able to write out generate control contents");
        let expected = include_str!("foobar.expected");
        assert_eq!(actual, expected);
    }
}

#[test]
fn generate_package_with_semver_crate_src() {
    let out_dir = Path::new(env!("CARGO_TARGET_TMPDIR"));
    if let Some(tempdir) = local_package_test(out_dir, "foobar-semver", "0.1.0", true) {
        let actual = std::fs::read_to_string(tempdir.path().join("output/debian/control"))
            .expect("Should be able to read generated debian/control file");
        std::fs::write(out_dir.join("foobar-semver.actual"), &actual)
            .expect("Should be able to write out generate control contents");
        let expected = include_str!("foobar-semver.expected");
        assert_eq!(actual, expected);
    }
}

#[test]
fn generate_package_with_filters() {
    let out_dir = Path::new(env!("CARGO_TARGET_TMPDIR"));
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    if let Some(tempdir) = local_package_test(out_dir, "sequoia-openpgp", "2.3.0", true) {
        let actual = std::fs::read_to_string(tempdir.path().join("output/debian/control"))
            .expect("Should be able to read generated debian/control file");
        std::fs::write(out_dir.join("sequoia-openpgp.actual"), &actual)
            .expect("Should be able to write out generate control contents");
        let expected = include_str!("sequoia-openpgp.expected");
        assert_eq!(actual, expected);

        let actual = std::fs::read_to_string(tempdir.path().join("output/debian/patches/series"))
            .expect("Should be able to read generated debian/patches/series file");
        std::fs::write(out_dir.join("sequoia-openpgp.actual_series"), &actual)
            .expect("Should be able to write out generate patch series contents");
        let expected = include_str!("sequoia-openpgp.expected_series");
        assert_eq!(actual, expected);

        for patch in expected.lines() {
            if patch.starts_with('#') {
                continue;
            }
            let mangled_patch = patch.replace('/', "_");
            let actual = std::fs::read_to_string(
                tempdir
                    .path()
                    .join(format!("output/debian/patches/{patch}")),
            )
            .expect("Should be able to read generated debian/patches/{patch} file");
            std::fs::write(
                out_dir.join(format!("sequoia-openpgp.actual_patch_{mangled_patch}")),
                &actual,
            )
            .unwrap_or_else(|_| {
                panic!("Should be able to write out generate patch contents - {mangled_patch}")
            });
            let expected_path = manifest_dir
                .join("tests")
                .join(format!("sequoia-openpgp.expected_patch_{mangled_patch}"));
            let expected = std::fs::read_to_string(&expected_path).unwrap_or_else(|_| {
                panic!("Should be able to read expected patch file at {expected_path:?}")
            });
            assert_eq!(actual, expected);
        }

        let metadata =
            std::fs::read_to_string(tempdir.path().join("output/debian/upstream/metadata"))
                .expect("Should generate debian/upstream/metadata");
        assert_eq!(
            metadata,
            "Repository-Browse: https://gitlab.com/sequoia-pgp/sequoia\n\
             Repository: https://gitlab.com/sequoia-pgp/sequoia.git\n\
             Documentation: https://docs.rs/sequoia-openpgp\n"
        );
    }
}
