use std::fs;
mod common;
use common::Environment;

#[test]
fn verify_tag_vanilla() {
    verify_tag(&[]).unwrap();
}

#[test]
fn verify_tag_keep_going() {
    verify_tag(&["--keep-going"]).unwrap();
}

#[test]
fn verify_tag_json() {
    verify_tag(&["--output-format=json"]).unwrap();
}

fn verify_tag(extra: &[&str]) -> anyhow::Result<()> {
    // Append `args` to the list.
    fn add<'a>(args: &[&'a str], extra: &[&'a str]) -> Vec<&'a str> {
        let mut args = args.to_vec();
        args.extend_from_slice(extra);
        args
    }

    // Check that sq-git verify-tag works.

    let e = Environment::new()?;
    let p = e.git_state();
    e.sq_git(&[
        "policy",
        "authorize",
        e.willow.petname,
        &e.willow.fingerprint.to_string(),
        "--project-maintainer"
    ])?;
    e.sq_git(&[
        "policy",
        "authorize",
        e.xander.petname,
        &e.xander.fingerprint.to_string(),
        "--committer"
    ])?;
    e.git(&["add", "openpgp-policy.toml"])?;
    e.git(&[
        "commit",
        "-m", "Initial commit.",
        &format!("-S{}", e.willow.fingerprint),
    ])?;
    let root = e.git_current_commit()?;

    fs::write(p.join("a"), "a.")?;
    e.git(&["add", "a"])?;
    e.git(&[
        "commit",
        "-m", "(willow).",
        &format!("-S{}", e.willow.fingerprint),
    ])?;

    e.sq_git(&["log", "--trust-root", &root])?;


    // Willow (a signer) tags and signs a release.
    {
        let commit = e.git_commit(
            &[("b", Some(b"1"))],
            "(willow)",
            Some(&e.willow))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])?;

        e.git(&["tag", "v1.0.0",
                "-m", "Release v1.0.0 (willow)",
                "-s", &format!("-u{}", e.willow.fingerprint)])?;

        e.git(&["verify-tag", "v1.0.0"])
            .expect("can authenticate tag");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v1.0.0"], extra))
            .expect("can authenticate tag");

        // verify-tag should fail for commits.
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, &commit], extra))
            .expect_err("can't use verify-tag with a commit");

        // verify-tag should fail for branches.
        e.git(&["branch", "v1.0.0-branch", "v1.0.0"])?;

        e.git(&["verify-tag", "v1.0.0-branch"])
            .expect_err("cannot use verify-tag to authenticate a branch");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v1.0.0-branch"], extra))
            .expect_err("cannot use verify-tag to authenticate a branch");
    }

    // Willow (a signer) tags, but does not sign a release.
    {
        let _commit = e.git_commit(
            &[("b", Some(b"2"))],
            "(willow)",
            Some(&e.willow))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])?;

        e.git(&["tag", "v2.0.0",
                "-m", "Release v2.0.0 (willow)",
                "--no-sign"])?;

        e.git(&["verify-tag", "v2.0.0"])
            .expect_err("no signature");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v2.0.0"], extra))
            .expect_err("no signature");
    }

    // Willow signs the release commit, but Xander (NOT a signer, just
    // a committer) tags.  This should not work.
    {
        let _commit = e.git_commit(
            &[("b", Some(b"3"))],
            "(willow)",
            Some(&e.willow))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])?;

        e.git(&["tag", "v3.0.0",
                "-m", "Release v3.0.0 (xander)",
                "-s", &format!("-u{}", e.xander.fingerprint)])?;

        e.git(&["verify-tag", "v3.0.0"])
            .expect("valid signature");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v3.0.0"], extra))
            .expect_err("signer not authorized");
    }

    // Willow signs the release commit, but Riley (NOT in the policy
    // file) tags.  This should not work.
    {
        let _commit = e.git_commit(
            &[("b", Some(b"4"))],
            "(willow)",
            Some(&e.willow))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])?;

        e.git(&["tag", "v4.0.0",
                "-m", "Release v4.0.0 (riley)",
                "-s", &format!("-u{}", e.riley.fingerprint)])?;

        e.git(&["verify-tag", "v4.0.0"])
            .expect("valid signature");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v4.0.0"], extra))
            .expect_err("signer not authorized");
    }

    // Riley (not authorized) adds a commit.  Willow (a signer) tags a
    // release.  This should not work.
    {
        let _commit = e.git_commit(
            &[("b", Some(b"5"))],
            "(riley)",
            Some(&e.riley))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])
            .expect_err("unauthorized commit");

        e.git(&["tag", "v5.0.0",
                "-m", "Release v5.0.0 (willow)",
                "-s", &format!("-u{}", e.willow.fingerprint)])?;

        e.git(&["verify-tag", "v5.0.0"])
            .expect("valid signature");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v5.0.0"], extra))
            .expect_err("unauthorized intermediate commit");
    }

    // Willow (authorized) adds a commit.  Willow (a signer) tags a
    // release.  This should still not work due to the intermediate
    // unauthorized commit.
    {
        let _commit = e.git_commit(
            &[("b", Some(b"6"))],
            "(willow)",
            Some(&e.willow))
            .expect("can commit");

        e.sq_git(&["log", "--trust-root", &root])
            .expect_err("previous commit is unauthorized");
        e.sq_git(&["log", "--trust-root", "HEAD"])?;

        e.git(&["tag", "v6.0.0",
                "-m", "Release v6.0.0 (willow)",
                "-s", &format!("-u{}", e.willow.fingerprint)])?;

        e.git(&["verify-tag", "v6.0.0"])
            .expect("valid signature");
        e.sq_git(&add(&["verify-tag", "--trust-root", &root, "v6.0.0"], extra))
            .expect_err("unauthorized intermediate commit");

        // But if we use v6.0.0 as the trust root, then it should work.
        e.sq_git(&add(&["verify-tag", "--trust-root", "HEAD", "v6.0.0" ], extra))
            .expect("should authenticate");
    }

    Ok(())
}
