use std::path::Path;
use std::process::ExitStatus;
use std::sync::{Mutex, OnceLock};

use tempfile::TempDir;
use assert_cmd::Command;


use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::Cert;
use openpgp::parse::Parse;

fn artifact(filename: &str) -> String {
    format!("tests/data/{}", filename)
}

// We are going to replace certifications, and we want to make sure
// that the newest one is the active one.  This means ensuring that
// the newer one has a newer timestamp.  To avoid sleeping for a
// second, the resolution of the time stamp, we pass an explicit time
// to each operation.
//
// This function drives the clock forward, and ensures that every
// operation "happens" at a different point in time.
static TIME: OnceLock<Mutex<chrono::DateTime<chrono::Utc>>> = OnceLock::new();

fn tick() -> String {
    let t = TIME.get_or_init(|| Mutex::new(chrono::Utc::now()));
    let mut t = t.lock().unwrap();
    *t = *t + chrono::Duration::seconds(10);
    t.format("%Y-%m-%dT%H:%M:%SZ").to_string()
}

// Returns the "current" time.
fn now() -> chrono::DateTime<chrono::Utc> {
    *TIME.get_or_init(|| Mutex::new(chrono::Utc::now())).lock().unwrap()
}

// Imports a certificate.
fn sq_import(cert_store: &str, files: &[&str], stdin: Option<&str>)
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    cmd.args(["--cert-store", cert_store, "cert", "import"]);
    for file in files {
        cmd.arg(file);
    }
    eprintln!("{:?}", cmd);
    if let Some(stdin) = stdin {
        cmd.write_stdin(stdin);
    }
    cmd.assert().success();
}

// Generates a key.
//
// If cert_store is not `None`, then the resulting certificate is also
// imported.
fn sq_gen_key(cert_store: Option<&str>, userids: &[&str], file: &str) -> Cert
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    cmd.args(["--no-cert-store",
              "key", "generate",
              "--time", &tick(),
              "--expiry", "never",
              "--output", file]);
    for userid in userids.iter() {
        cmd.args(["--userid", userid]);
    }
    eprintln!("{:?}", cmd);
    cmd.assert().success();

    if let Some(cert_store) = cert_store {
        sq_import(cert_store, &[ file ], None);
    }

    Cert::from_file(file).expect("valid certificate")
}

// Verifies a signed message.
fn sq_verify(cert_store: Option<&str>,
             time: Option<chrono::DateTime<chrono::Utc>>,
             trust_roots: &[&str],
             signer_files: &[&str],
             msg_pgp: &str,
             good_sigs: usize, good_checksums: usize)
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    if let Some(cert_store) = cert_store {
        cmd.args(&["--cert-store", cert_store]);
    } else {
        cmd.arg("--no-cert-store");
    }
    for trust_root in trust_roots {
        cmd.args(&["--trust-root", trust_root]);
    }
    let time = if let Some(time) = time {
        time.format("%Y-%m-%dT%H:%M:%SZ").to_string()
    } else {
        tick()
    };
    cmd.args(["verify", "--time", &time]);
    for signer_file in signer_files {
        cmd.args(&["--signer-file", signer_file]);
    }
    cmd.arg(msg_pgp);
    eprintln!("{:?}", cmd);
    let output = cmd.output().expect("can run");

    let status = output.status;
    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();

    if good_sigs > 0 {
        assert!(status.success(),
                "\nstdout:\n{}\nstderr:\n{}",
                stdout, stderr);
        assert!(stderr.contains(&format!("{} good signature",
                                         good_sigs)),
                "stdout:\n{}\nstderr:\n{}",
                stdout, stderr);
    } else {
        assert!(! status.success(),
                "\nstdout:\n{}\nstderr:\n{}",
                stdout, stderr);
    }

    if good_checksums > 0 {
        assert!(stderr.contains(&format!("{} unauthenticated checksum",
                                         good_checksums)),
                "stdout:\n{}\nstderr:\n{}", stdout, stderr);
    }
}

// Links a User ID and a certificate.
fn sq_link(cert_store: &str,
           cert: &str, userids: &[&str], more_args: &[&str],
           success: bool)
    -> (ExitStatus, String, String)
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    cmd.args(&["--cert-store", cert_store]);
    cmd.args(&["pki", "link", "add", "--time", &tick(), cert]);
    cmd.args(userids);
    cmd.args(more_args);
    eprintln!("{:?}", cmd);
    let output = cmd.output().expect("can run");

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();

    if success {
        assert!(output.status.success(),
                "'sq pki link add' failed unexpectedly\
                 \nstdout:\n{}\nstderr:\n{}",
                stdout, stderr);
    } else {
        assert!(! output.status.success(),
                "'sq pki link add' succeeded unexpectedly\
                 \nstdout:\n{}\nstderr:\n{}",
                stdout, stderr);
    }

    (output.status, stdout, stderr)
}

fn sq_retract(cert_store: &str, cert: &str, userids: &[&str])
    -> (ExitStatus, String, String)
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    cmd.args(&["--cert-store", cert_store]);
    cmd.args(&["pki", "link", "retract", "--time", &tick(), cert]);
    cmd.args(userids);
    eprintln!("{:?}", cmd);
    let output = cmd.output().expect("can run");

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
    assert!(output.status.success(),
            "sq pki link retract\nstdout:\n{}\nstderr:\n{}",
            stdout, stderr);

    (output.status, stdout, stderr)
}

// Certifies a binding.
//
// The certification is imported into the cert store.
fn sq_certify(cert_store: &str,
              key: &str, cert: &str, userid: &str,
              trust_amount: Option<usize>, depth: Option<usize>)
{
    let mut cmd = Command::cargo_bin("sq").expect("have sq");
    cmd.args(&["--cert-store", cert_store]);
    cmd.args(&["pki", "certify", "--time", &tick(), key, cert, userid]);
    if let Some(trust_amount) = trust_amount {
        cmd.args(&["--amount", &trust_amount.to_string()[..]]);
    }
    if let Some(depth) = depth {
        cmd.args(&["--depth", &depth.to_string()[..]]);
    }
    eprintln!("{:?}", cmd);
    let output = cmd.output().expect("can run");

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
    assert!(output.status.success(),
            "sq pki certify\nstdout:\n{}\nstderr:\n{}",
            stdout, stderr);

    // Import the certification.
    sq_import(cert_store, &[], Some(&stdout));
}

// Verify signatures using the acceptance machinery.
#[test]
fn sq_link_add_retract() -> Result<()> {
    let dir = TempDir::new()?;

    let certd = dir.path().join("cert.d").display().to_string();
    std::fs::create_dir(&certd).expect("mkdir works");

    struct Data {
        key_file: String,
        cert: Cert,
        sig_file: String,
    }

    // Four certificates.
    let alice_pgp = dir.path().join("alice.pgp").display().to_string();
    let alice_userid = "<alice@example.org>";
    let alice = Data {
        key_file: alice_pgp.clone(),
        cert: sq_gen_key(Some(&certd), &[ alice_userid ], &alice_pgp),
        sig_file: dir.path().join("alice.sig").display().to_string(),
    };
    let alice_fpr = alice.cert.fingerprint().to_string();

    let bob_pgp = dir.path().join("bob.pgp").display().to_string();
    let bob_userid = "<bob@example.org>";
    let bob = Data {
        key_file: bob_pgp.clone(),
        cert: sq_gen_key(Some(&certd), &[ bob_userid ], &bob_pgp),
        sig_file: dir.path().join("bob.sig").display().to_string(),
    };
    let bob_fpr = bob.cert.fingerprint().to_string();

    let carol_pgp = dir.path().join("carol.pgp").display().to_string();
    let carol_userid =  "<carol@example.org>";
    let carol = Data {
        key_file: carol_pgp.clone(),
        cert: sq_gen_key(Some(&certd), &[ carol_userid ], &carol_pgp),
        sig_file: dir.path().join("carol.sig").display().to_string(),
    };
    let carol_fpr = carol.cert.fingerprint().to_string();

    let dave_pgp = dir.path().join("dave.pgp").display().to_string();
    let dave_userid =  "<dave@other.org>";
    let dave = Data {
        key_file: dave_pgp.clone(),
        cert: sq_gen_key(Some(&certd), &[ dave_userid ], &dave_pgp),
        sig_file: dir.path().join("dave.sig").display().to_string(),
    };
    let dave_fpr = dave.cert.fingerprint().to_string();

    let data: &[&Data] = &[ &alice, &bob, &carol, &dave ][..];

    // Have each certificate sign a message.
    for data in data.iter() {
        Command::cargo_bin("sq")
            .unwrap()
            .arg("--no-cert-store")
            .arg("sign")
            .args(["--signer-file", &data.key_file])
            .args(["--output", &data.sig_file])
            .args(["--time", &tick()])
            .arg(&artifact("messages/a-cypherpunks-manifesto.txt"))
            .assert()
            .success();
    }

    // None of the certificates can be authenticated so verifying the
    // messages should fail.
    for data in data.iter() {
        sq_verify(Some(&certd), None, &[], &[], &data.sig_file, 0, 1);
    }

    // Have Alice certify Bob as a trusted introducer and have Bob
    // certify Carol.
    sq_certify(&certd, &alice.key_file,
               &bob.cert.fingerprint().to_string(), bob_userid,
               None, Some(1));
    sq_certify(&certd, &bob.key_file,
               &carol.cert.fingerprint().to_string(), carol_userid,
               None, None);

    // We should be able to verify Alice's, Bob's and Carol's
    // signatures Alice as the trust root.  And Bob's and Carols' with
    // Bob as the trust root.

    sq_verify(Some(&certd), None, &[&alice_fpr], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&alice_fpr], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&alice_fpr], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&alice_fpr], &[], &dave.sig_file, 0, 1);

    sq_verify(Some(&certd), None, &[&bob_fpr], &[], &alice.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&bob_fpr], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&bob_fpr], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&bob_fpr], &[], &dave.sig_file, 0, 1);

    sq_verify(Some(&certd), None, &[&carol_fpr], &[], &alice.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&carol_fpr], &[], &bob.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&carol_fpr], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[&carol_fpr], &[], &dave.sig_file, 0, 1);

    sq_verify(Some(&certd), None, &[&dave_fpr], &[], &alice.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&dave_fpr], &[], &bob.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&dave_fpr], &[], &carol.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[&dave_fpr], &[], &dave.sig_file, 1, 0);

    // Let's accept Alice, but not (yet) as a trusted introducer.  We
    // should now be able to verify Alice's signature, but not Bob's.
    sq_link(&certd, &alice_fpr, &[ &alice_userid ], &[], true);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 0, 1);

    // Accept Alice as a trusted introducer.  We should be able to
    // verify Alice, Bob, and Carol's signatures.
    sq_link(&certd, &alice_fpr, &[ &alice_userid ], &["--ca", "*"], true);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &dave.sig_file, 0, 1);

    // Retract the acceptance for Alice.  If we don't specify a trust
    // root, none of the signatures should verify.
    sq_retract(&certd, &alice_fpr, &[ &alice_userid ]);

    for data in data.iter() {
        sq_verify(Some(&certd), None, &[], &[], &data.sig_file, 0, 1);
    }

    // Accept Alice as a trusted introducer again.  We should be able
    // to verify Alice, Bob, and Carol's signatures.
    sq_link(&certd, &alice_fpr, &[ &alice_userid ], &["--ca", "*"], true);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &dave.sig_file, 0, 1);

    // Have Bob certify Dave.  Now Dave's signature should also
    // verify.
    sq_certify(&certd, &bob.key_file,
               &dave.cert.fingerprint().to_string(), dave_userid,
               None, None);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &dave.sig_file, 1, 0);

    // Change Alice's acceptance to just be a normal certification.
    // We should only be able to verify her signature.
    sq_link(&certd, &alice_fpr, &[ &alice_userid ], &[], true);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[], &[], &carol.sig_file, 0, 1);
    sq_verify(Some(&certd), None, &[], &[], &dave.sig_file, 0, 1);

    // Change Alice's acceptance to be a ca, but only for example.org,
    // i.e., not for Dave.
    sq_link(&certd, &alice_fpr, &[ &alice_userid ], &["--ca", "example.org"],
            true);

    sq_verify(Some(&certd), None, &[], &[], &alice.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &bob.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &carol.sig_file, 1, 0);
    sq_verify(Some(&certd), None, &[], &[], &dave.sig_file, 0, 1);



    // Four certificates.
    let ed_pgp = dir.path().join("ed.pgp").display().to_string();
    let ed = sq_gen_key(
        Some(&certd),
        &[
            "Ed <ed@example.org>",
            "Eddie <ed@example.org>",
            // This is not considered to be an email address as
            // it is not wrapped in angle brackets.
            "ed@some.org",
            // But this is.
            "<ed@other.org>",
        ],
        &ed_pgp);
    let ed_fpr = ed.fingerprint().to_string();
    let ed_sig_file = dir.path().join("ed.sig").display().to_string();

    Command::cargo_bin("sq")
        .unwrap()
        .arg("--no-cert-store")
        .arg("sign")
        .args(["--signer-file", &ed_pgp])
        .args(["--output", &ed_sig_file])
        .args(["--time", &tick()])
        .arg(&artifact("messages/a-cypherpunks-manifesto.txt"))
        .assert()
        .success();

    // If we don't use --petname, than a self-signed User ID must
    // exist.
    sq_link(&certd, &ed_fpr, &[ "--userid", "bob@example.com" ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    sq_link(&certd, &ed_fpr, &[ "--email", "bob@example.com" ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    sq_link(&certd, &ed_fpr, &[ "bob@example.com" ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    // We should only create links if all the supplied User IDs are
    // valid.
    sq_link(&certd, &ed_fpr, &[
        "--userid", "ed@some.org", "--userid", "bob@example.com"
    ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    sq_link(&certd, &ed_fpr, &[
        "--userid", "ed@some.org", "--email", "bob@example.com"
    ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    sq_link(&certd, &ed_fpr, &[
        "--userid", "ed@some.org", "bob@example.com"
    ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    // Pass an email address to --userid.  This shouldn't match
    // either.
    sq_link(&certd, &ed_fpr, &[
        "--userid", "ed@other.org"
    ], &[], false);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    // Link all User IDs individually.
    sq_link(&certd, &ed_fpr, &[
        "--email", "ed@other.org",
        "--email", "ed@example.org",
        "--userid", "ed@some.org",
    ], &[], true);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 1, 0);

    // Retract the links one at a time.
    sq_retract(&certd, &ed_fpr, &[ "ed@other.org" ]);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 1, 0);

    sq_retract(&certd, &ed_fpr, &[ "Ed <ed@example.org>" ]);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 1, 0);

    sq_retract(&certd, &ed_fpr, &[ "Eddie <ed@example.org>" ]);
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 1, 0);

    sq_retract(&certd, &ed_fpr, &[ "ed@some.org" ]);
    // Now the certificate should no longer be authenticated.
    sq_verify(Some(&certd), None, &[], &[], &ed_sig_file, 0, 1);

    Ok(())
}

// Set the different parameters.  When the parameters are the same,
// make sure no certifications are written; when they are different
// make sure the file changed.
#[test]
fn sq_link_update_detection() -> Result<()> {
    let dir = TempDir::new()?;

    let certd = dir.path().join("cert.d").display().to_string();
    std::fs::create_dir(&certd).expect("mkdir works");

    let alice_pgp = dir.path().join("alice.pgp").display().to_string();
    let alice_userid = "<alice@example.org>";
    let alice = sq_gen_key(Some(&certd), &[ alice_userid ], &alice_pgp);
    let alice_fpr = alice.fingerprint().to_string();
    let alice_cert_pgp = dir.path().join("cert.d")
        .join(&alice_fpr[0..2].to_ascii_lowercase())
        .join(&alice_fpr[2..].to_ascii_lowercase());

    // Reads and returns file.  Asserts that old and the new contexts
    // are the same (or not).
    let compare = |old: Vec<u8>, file: &Path, same: bool| -> Vec<u8> {
        let new = std::fs::read(file).unwrap();
        if same {
            assert_eq!(old, new, "file unexpectedly changed");
        } else {
            assert_ne!(old, new, "file unexpectedly stayed the same");
        }
        new
    };
    let bytes = std::fs::read(&alice_cert_pgp).unwrap();

    // Retract it.  There is nothing to retract (but this doesn't fail).
    let output = sq_retract(&certd, &alice_fpr, &[]);
    assert!(output.2.contains("You never linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // Link it.
    sq_link(&certd, &alice_fpr, &[], &["--all"], true);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    // As no parameters changed, this should succeeded, but no
    // certification should be written.
    let output = sq_link(&certd, &alice_fpr, &[], &["--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // Make Alice a CA.
    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--ca", "*", "--all"], true);
    assert!(output.2.contains("was already linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--ca", "*", "--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // Make her a partially trusted CA.
    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--amount", "30", "--all"], true);
    assert!(output.2.contains("was already linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--amount", "30", "--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // Retract the link.
    let output = sq_retract(&certd, &alice_fpr, &[]);
    assert!(output.2.contains("was linked at"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_retract(&certd, &alice_fpr, &[]);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);


    // Link it again.
    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--depth", "10", "--amount", "10", "--all"], true);
    assert!(output.2.contains("was retracted"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--depth", "10", "--amount", "10", "--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // Use a notation.
    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--notation", "foo", "10", "--all"], true);
    assert!(output.2.contains("was already linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_link(&certd, &alice_fpr, &[],
                         &["--notation", "foo", "10", "--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    // The default link again.
    let output = sq_link(&certd, &alice_fpr, &[], &["--all"], true);
    assert!(output.2.contains("was already linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    let output = sq_link(&certd, &alice_fpr, &[], &["--all"], true);
    assert!(output.2.contains("Link parameters are unchanged, no update needed"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, true);

    let _ = bytes;
    Ok(())
}

// Check that sq link add --temporary works.
#[test]
fn sq_link_add_temporary() -> Result<()> {
    let dir = TempDir::new()?;

    let certd = dir.path().join("cert.d").display().to_string();
    std::fs::create_dir(&certd).expect("mkdir works");

    let alice_pgp = dir.path().join("alice.pgp").display().to_string();
    let alice_userid = "<alice@example.org>";
    let alice = sq_gen_key(Some(&certd), &[ alice_userid ], &alice_pgp);
    let alice_fpr = alice.fingerprint().to_string();
    let alice_cert_pgp = dir.path().join("cert.d")
        .join(&alice_fpr[0..2].to_ascii_lowercase())
        .join(&alice_fpr[2..].to_ascii_lowercase());

    let alice_sig_file = dir.path().join("alice.sig").display().to_string();
    Command::cargo_bin("sq")
        .unwrap()
        .arg("--no-cert-store")
        .arg("sign")
        .args(["--signer-file", &alice_pgp])
        .args(["--output", &alice_sig_file])
        .args(["--time", &tick()])
        .arg(&artifact("messages/a-cypherpunks-manifesto.txt"))
        .assert()
        .success();

    // Reads and returns file.  Asserts that old and the new contexts
    // are the same (or not).
    let compare = |old: Vec<u8>, file: &Path, same: bool| -> Vec<u8> {
        let new = std::fs::read(file).unwrap();
        if same {
            assert_eq!(old, new, "file unexpectedly changed");
        } else {
            assert_ne!(old, new, "file unexpectedly stayed the same");
        }
        new
    };
    let bytes = std::fs::read(&alice_cert_pgp).unwrap();

    sq_verify(Some(&certd), None, &[], &[], &alice_sig_file, 0, 1);

    let output = sq_link(&certd, &alice_fpr, &[], &["--temporary", "--all"], true);
    assert!(output.2.contains("Linking "),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    // Now it is fully trusted.
    sq_verify(Some(&certd), None, &[], &[], &alice_sig_file, 1, 0);

    // In 6 days, too.
    sq_verify(Some(&certd),
              Some(now() + chrono::Duration::seconds(6 * 24 * 60 * 60)),
              &[], &[], &alice_sig_file, 1, 0);

    // But in 8 days it will only be partially trusted.
    sq_verify(Some(&certd),
              Some(now() + chrono::Duration::seconds(8 * 24 * 60 * 60)),
              &[], &[], &alice_sig_file, 0, 1);


    // Now mark it as fully trusted.  It should be trusted now, in 6
    // days and in 8 days.
    let output = sq_link(&certd, &alice_fpr, &[], &["--all"], true);
    assert!(output.2.contains("was already linked"),
            "stdout:\n{}\nstderr:\n{}", output.1, output.2);
    eprintln!("{:?}", output);
    let bytes = compare(bytes, &alice_cert_pgp, false);

    sq_verify(Some(&certd), None, &[], &[], &alice_sig_file, 1, 0);

    sq_verify(Some(&certd),
              Some(now() + chrono::Duration::seconds(6 * 24 * 60 * 60)),
              &[], &[], &alice_sig_file, 1, 0);

    sq_verify(Some(&certd),
              Some(now() + chrono::Duration::seconds(8 * 24 * 60 * 60)),
              &[], &[], &alice_sig_file, 1, 0);

    let _bytes = bytes;

    Ok(())
}
