use std::{
    time::Duration,
};

use anyhow::Result;

use sequoia_openpgp as openpgp;
use openpgp::{
    cert::prelude::*,
    packet::{*, signature::{*, subpacket::*}, key::*},
    types::*,
    parse::Parse,
    policy::StandardPolicy,
    serialize::{Serialize, SerializeInto},
};

use super::super::*;

const MSG: &[u8] = b"Hello, world!";
const MSG_BAD: &[u8] = b"Hello, world?";

#[test]
#[ntest::timeout(600000)]
fn basic() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut subkey_signer =
                cert.keys().subkeys().secret().next().unwrap()
                .key().clone().into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut subkey_signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    test_detached_sig(&mut experiment, &cert, sig)
}

#[test]
#[ntest::timeout(600000)]
fn cipher_suites() -> Result<()> {
    use CipherSuite::*;
    for cs in vec![
        Cv25519,
        RSA3k,
        P256,
        P384,
        P521,
        RSA2k,
        RSA4k,
    ] {
        let mut experiment = make_experiment!(format!("{:?}", cs))?;
        let cert = experiment.artifact(
            "cert",
            || CertBuilder::new()
                .set_creation_time(Experiment::now())
                .set_cipher_suite(cs)
                .add_userid("Alice Lovelace <alice@lovelace.name>")
                .add_signing_subkey()
                .generate()
                .map(|(cert, _rev)| cert),
            |a, f| a.as_tsk().serialize(f),
            |b| Cert::from_bytes(&b))?;

        let sig = experiment.artifact(
            "sig",
            || -> Result<Packet> {
                let mut subkey_signer =
                    cert.keys().subkeys().secret().next().unwrap()
                    .key().clone().into_keypair()?;

                SignatureBuilder::new(SignatureType::Binary)
                    .set_signature_creation_time(Experiment::now())?
                    .sign_message(&mut subkey_signer, MSG)
                    .map(Into::into)
            },
            |a, f| a.serialize(f),
            |b| Packet::from_bytes(&b))?;

        test_detached_sig(&mut experiment, &cert, sig)?;
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn hash_algos() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    use HashAlgorithm::*;
    for (i, algo) in vec![
        MD5,
        // XXX: Upstream doesn't consider SHA1 weak.
        // SHA1,
        // XXX: Upstream doesn't consider RipeMD weak.
        // RipeMD,
        SHA256,
        SHA384,
        SHA512,
        SHA224,
    ].into_iter().enumerate() {
        let sig = experiment.artifact(
            &format!("sig.{}", algo),
            || -> Result<Packet> {
                let mut subkey_signer =
                    cert.keys().subkeys().secret().next().unwrap()
                    .key().clone().into_keypair()?;

                SignatureBuilder::new(SignatureType::Binary)
                    .set_signature_creation_time(Experiment::now())?
                    .set_hash_algo(algo)
                    .sign_message(&mut subkey_signer, MSG)
                    .map(Into::into)
            },
            |a, f| a.serialize(f),
            |b| Packet::from_bytes(&b))?;

        test_detached_sig_with(&mut experiment,
                               i == 0, &cert,
                               sig,
                               vec![],
                               true,
                               i > 0)?;
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn weak_hash_algos() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .set_cipher_suite(CipherSuite::P521) // Forces SHA512.
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    use HashAlgorithm::*;
    for (i, algo) in vec![
        MD5,
        SHA1,
        RipeMD,
        SHA256,
        SHA384,
        SHA224,
        // Note: We don't test for SHA512 because that is what we use
        // in the binding signatures, and the Chameleon -unlike GnuPG-
        // also considers the weak algorithm set when evaluating
        // binding signatures.
        //
        // SHA512,
    ].into_iter().enumerate() {
        let sig = experiment.artifact(
            &format!("sig.{}", algo),
            || -> Result<Packet> {
                let mut subkey_signer =
                    cert.keys().subkeys().secret().next().unwrap()
                    .key().clone().into_keypair()?;

                SignatureBuilder::new(SignatureType::Binary)
                    .set_signature_creation_time(Experiment::now())?
                    .set_hash_algo(algo)
                    .sign_message(&mut subkey_signer, MSG)
                    .map(Into::into)
            },
            |a, f| a.serialize(f),
            |b| Packet::from_bytes(&b))?;

        test_detached_sig_with(&mut experiment,
                               i == 0, &cert,
                               sig,
                               vec!["--weak-digest", &algo.to_string()],
                               true,
                               false)?;
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn signature_types() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    use SignatureType::*;
    for (i, typ) in vec![
        Binary,
        Text,
        // Trick Sequoia into making weird signatures:
        Unknown(Standalone.into()),
        Unknown(GenericCertification.into()),
        Unknown(PersonaCertification.into()),
        Unknown(CasualCertification.into()),
        Unknown(PositiveCertification.into()),
        Unknown(CertificationApproval.into()),
        Unknown(SubkeyBinding.into()),
        Unknown(PrimaryKeyBinding.into()),
        Unknown(DirectKey.into()),
        Unknown(KeyRevocation.into()),
        Unknown(SubkeyRevocation.into()),
        Unknown(CertificationRevocation.into()),
        Unknown(Timestamp.into()),
        Unknown(Confirmation.into()),
        Unknown(77),
    ].into_iter().enumerate() {
        let sig = experiment.artifact(
            &format!("sig.{}", u8::from(typ)),
            || -> Result<Packet> {
                let mut subkey_signer =
                    cert.keys().subkeys().secret().next().unwrap()
                    .key().clone().into_keypair()?;

                SignatureBuilder::new(typ)
                    .set_signature_creation_time(Experiment::now())?
                    .sign_message(&mut subkey_signer, MSG)
                    .map(Into::into)
            },
            |a, f| a.serialize(f),
            |b| Packet::from_bytes(&b))?;

        test_detached_sig_with(&mut experiment,
                               i == 0, &cert,
                               sig,
                               vec![],
                               true,
                               i < 2)?;
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn extended() -> Result<()> {
    let mut experiment = make_experiment!()?;

    let the_past = Experiment::now() - Duration::new(3600, 0);
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(the_past)
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut subkey_signer =
                cert.keys().subkeys().secret().next().unwrap()
                .key().clone().into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut subkey_signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    let primary =
        cert.primary_key().key().clone();
    let uid =
        cert.userids().next().unwrap().userid().clone();
    let uid_binding =
        cert.userids().next().unwrap().self_signatures().next().unwrap().clone();
    let subkey =
        cert.keys().subkeys().next().unwrap().key().clone();
    let subkey_binding =
        cert.keys().subkeys().next().unwrap().self_signatures().next().unwrap().clone();

    let mut primary_signer =
        primary.clone().parts_into_secret()?.into_keypair()?;

    let primary_revocation = experiment.artifact(
        "primary_revocation",
        || -> Result<Packet> {
            CertRevocationBuilder::new()
                .set_signature_creation_time(the_past)?
                .set_reason_for_revocation(ReasonForRevocation::KeyRetired,
                                           b"Revoking due to the recent \
                                             crypto vulnerabilities.")?
                .build(&mut primary_signer, &cert, None)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    let subkey_revocation = experiment.artifact(
        "subkey_revocation",
        || -> Result<Packet> {
            SubkeyRevocationBuilder::new()
                .set_signature_creation_time(the_past)?
                .set_reason_for_revocation(ReasonForRevocation::KeyRetired,
                                           b"Revoking due to the recent \
                                             crypto vulnerabilities.")?
                .build(&mut primary_signer, &cert, &subkey, None)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    let uid_binding_expired = experiment.artifact(
        "uid_binding_expired",
        || -> Result<Packet> {
            uid.bind(&mut primary_signer, &cert,
                     SignatureBuilder::from(uid_binding.clone())
                     .set_signature_creation_time(the_past)?
                     .set_key_validity_period(Duration::new(1800, 0))?)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    let subkey_binding_expired = experiment.artifact(
        "subkey_binding_expired",
        || -> Result<Packet> {
            subkey.bind(&mut primary_signer, &cert,
                        SignatureBuilder::from(subkey_binding.clone())
                        .set_signature_creation_time(the_past)?
                        .set_key_validity_period(Duration::new(1800, 0))?)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    let mut i = 0;
    for primary_revoked in vec![false, true] {
        for primary_expired in vec![false, true] {
            for subkey_revoked in vec![false, true] {
                for subkey_expired in vec![false, true] {
                    dbg!((primary_revoked, primary_expired,
                          subkey_revoked, subkey_expired));

                    let mut acc = vec![
                        Packet::from(primary.clone()),
                    ];

                    if primary_revoked {
                        acc.push(primary_revocation.clone().into());
                    }

                    acc.push(uid.clone().into());
                    acc.push(
                        if primary_expired {
                            uid_binding_expired.clone().into()
                        } else {
                            uid_binding.clone().into()
                        }
                    );

                    acc.push(subkey.clone().into());
                    if subkey_revoked {
                        acc.push(subkey_revocation.clone().into());
                    }

                    acc.push(
                        if subkey_expired {
                            subkey_binding_expired.clone().into()
                        } else {
                            subkey_binding.clone().into()
                        }
                    );

                    let cert = Cert::from_packets(acc.into_iter())?;

                    if false {
                        let name = format!(
                            "/tmp/key-{:?}-{:?}-{:?}-{:?}",
                            primary_revoked, primary_expired,
                            subkey_revoked, subkey_expired);
                        std::fs::write(name, cert.to_vec()?)?;
                    }

                    test_detached_sig_with(&mut experiment,
                                           i == 0, &cert,
                                           sig.clone(), vec![],
                                           true,
                                           true)?;
                    i += 1;
                }
            }
        }
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn wrong_key() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_cipher_suite(CipherSuite::RSA2k)
            .set_creation_time(Experiment::now() - Duration::new(3600, 0))
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .add_authentication_subkey()
            .add_storage_encryption_subkey()
            .add_transport_encryption_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let standalone: Key<SecretParts, UnspecifiedRole> =
        experiment.artifact(
            "standalone",
            || -> Result<Key<SecretParts, UnspecifiedRole>> {
                Ok(Key4::generate_ecc(true, Curve::Ed25519)?.into())
            },
            |a, f| {
                use sequoia_openpgp::serialize::Marshal;
                a.serialize(f)
            },
            |b| { Ok(Key::from_bytes(&b)?.try_into()?) })?;

    let p = StandardPolicy::new();
    let vcert = cert.with_policy(&p, Experiment::now())?;
    for (i, (mut signer, signing_capable)) in vcert
        .keys()
        .map(|ka| (ka.key().clone().parts_into_secret().unwrap(),
                   ka.for_signing()))
        .chain(std::iter::once((standalone, false)))
        .map(|(key, signing_capable)|
             (key.into_keypair().unwrap(), signing_capable))
        .enumerate()
    {
        dbg!(i);
        let sig = experiment.artifact(
            &format!("sig.{}", i),
            || -> Result<Packet> {
                SignatureBuilder::new(SignatureType::Binary)
                    .set_signature_creation_time(Experiment::now())?
                    .sign_message(&mut signer, MSG)
                    .map(Into::into)
            },
            |a, f| a.serialize(f),
            |b| Packet::from_bytes(&b))?;


        if false {
            std::fs::write("/tmp/key", cert.to_vec()?)?;
            std::fs::write("/tmp/sig", sig.clone().to_vec()?)?;
            std::fs::write("/tmp/msg", MSG)?;
        }

        test_detached_sig_with(&mut experiment,
                               i == 0, &cert,
                               sig, vec![],
                               true,
                               signing_capable)?;
    }

    Ok(())
}

fn test_detached_sig(experiment: &mut Experiment, cert: &Cert, sig: Packet)
                     -> Result<()>
{
    test_detached_sig_with(experiment, true, cert, sig, vec![], true, true)
}

fn test_detached_sig_with<'a>(experiment: &mut Experiment,
                              pristine_experiment: bool,
                              cert: &Cert,
                              sig: Packet,
                              extra_args: Vec<&'a str>,
                              try_gpgv: bool,
                              expect_success: bool)
                              -> Result<()>
{
    let data_good = vec![
        experiment.store("sig", &sig.to_vec()?)?,
        experiment.store("msg", &MSG)?,
    ];

    let data_bad = vec![
        data_good[0].clone(),
        experiment.store("msg_bad", &MSG_BAD)?,
    ];

    let mut args_good = vec![
        "--verify",
    ];
    args_good.extend_from_slice(&extra_args);
    data_good.iter().for_each(|a| args_good.push(a));

    let mut args_bad = vec![
        "--verify",
    ];
    args_bad.extend_from_slice(&extra_args);
    data_bad.iter().for_each(|a| args_bad.push(a));

    // If we reuse the `experiment` in a loop, we only import the cert
    // once, and we can only test the failures related to the missing
    // key before we import it.
    if pristine_experiment {
        // First without the cert.
        let diff = experiment.invoke(&args_good)?;
        diff.assert_failure();
        diff.assert_equal_up_to(0, 20);

        let diff = experiment.invoke(&args_bad)?;
        diff.assert_failure();
        diff.assert_equal_up_to(0, 20);

        // Now try gpgv.
        if try_gpgv {
            let empty_keyring = experiment.store("empty", b"")?;
            let mut args_good = vec![
                "gpgv",
                "--keyring", &empty_keyring,
            ];
            args_good.extend_from_slice(&extra_args);
            data_good.iter().for_each(|a| args_good.push(a));
            let diff = experiment.invoke(&args_good)?;
            diff.assert_failure();
            diff.assert_equal_up_to(0, 20);

            let mut args_bad = vec![
                "gpgv",
                "--keyring", &empty_keyring,
            ];
            args_bad.extend_from_slice(&extra_args);
            data_bad.iter().for_each(|a| args_bad.push(a));
            let diff = experiment.invoke(&args_bad)?;
            diff.assert_failure();
            diff.assert_equal_up_to(0, 20);
        }

        experiment.section("Importing cert...");
        let diff = experiment.invoke(&[
            "--import",
            &experiment.store("cert", &cert.to_vec()?)?,
        ])?;
        diff.assert_success();
        diff.assert_equal_up_to(0, 20);
    }

    let diff = experiment.invoke(&args_good)?;
    if expect_success {
        diff.assert_success();
        diff.assert_limits(0, 10, 0);
    } else {
        diff.assert_failure();
        diff.assert_equal_up_to(0, 10);
    }

    let diff = experiment.invoke(&args_bad)?;
    diff.assert_failure();
    if expect_success {
        diff.assert_limits(0, 10, 0);
    } else {
        diff.assert_equal_up_to(0, 10);
    }

    // Now try gpgv.
    if try_gpgv {
        let cert = &experiment.store("cert", &cert.to_vec()?)?;
        let mut args_good = vec![
            "gpgv",
            "--keyring", &cert,
        ];
        args_good.extend_from_slice(&extra_args);
        data_good.iter().for_each(|a| args_good.push(a));
        let diff = experiment.invoke(&args_good)?;
        if expect_success {
            diff.assert_success();
            //diff.assert_equal_up_to(0, 10);
        } else {
            diff.assert_failure();
            diff.assert_equal_up_to(0, 10);
        }

        let mut args_bad = vec![
            "gpgv",
            "--keyring", &cert,
        ];
        args_bad.extend_from_slice(&extra_args);
        data_bad.iter().for_each(|a| args_bad.push(a));
        let diff = experiment.invoke(&args_bad)?;
        diff.assert_failure();
        if expect_success {
            //diff.assert_equal_up_to(200, 10);
        } else {
            diff.assert_equal_up_to(0, 10);
        }
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn show_uid_validity() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut subkey_signer =
                cert.keys().subkeys().secret().next().unwrap()
                .key().clone().into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut subkey_signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    for without_cert in [true, false] {
        if without_cert {
            experiment.section("First try without the cert...");
        } else {
            experiment.section("Importing cert...");
            let diff = experiment.invoke(&[
                "--import",
                &experiment.store("cert", &cert.to_vec()?)?,
            ])?;
            diff.assert_success();
        }

        let diff = experiment.invoke(&[
            "--verify",
            &experiment.store("sig", &sig.to_vec()?)?,
            &experiment.store("msg", &MSG)?,
        ])?;
        if without_cert {
            diff.assert_failure();
            diff.assert_limits(0, 6, 0);
        } else {
            diff.assert_success();
            diff.assert_limits(0, 6, 0);
        }

        // Note: There is no machine-readable output (--with-colons)
        // for signature verification.

        let diff = experiment.invoke(&[
            "--verify",
            "--verify-options", "show-uid-validity",
            &experiment.store("sig", &sig.to_vec()?)?,
            &experiment.store("msg", &MSG)?,
        ])?;
        if without_cert {
            diff.assert_failure();
            diff.assert_limits(0, 6, 0);
        } else {
            diff.assert_success();
            diff.assert_limits(0, 6, 0);
        }

        let diff = experiment.invoke(&[
            "--verify",
            "--verify-options", "no-show-uid-validity",
            &experiment.store("sig", &sig.to_vec()?)?,
            &experiment.store("msg", &MSG)?,
        ])?;
        if without_cert {
            diff.assert_failure();
            diff.assert_limits(0, 6, 0);
        } else {
            diff.assert_success();
            diff.assert_limits(0, 6, 0);
        }
    }

    // Finally, try various ownertrust values.

    for ownertrust in [0, 2, 3, 4, 5, 6] {
        experiment.section(&format!("Setting ownertrust to {}...", ownertrust));

        let diff = experiment.invoke(&[
            "--import-ownertrust",
            &experiment.store(
                "ownertrust",
                format!("{}:{}:\n", cert.fingerprint(), ownertrust)
                    .as_bytes())?,
        ])?;
        diff.assert_success();

        let diff = experiment.invoke(&[
            "--check-trustdb",
        ])?;
        diff.assert_success();

        let diff = experiment.invoke(&[
            "--verify",
            "--verify-options", "show-uid-validity",
            &experiment.store("sig", &sig.to_vec()?)?,
            &experiment.store("msg", &MSG)?,
        ])?;
        diff.assert_success();
        diff.assert_limits(0, 6, 0);

        let diff = experiment.invoke(&[
            "--verify",
            "--verify-options", "no-show-uid-validity",
            &experiment.store("sig", &sig.to_vec()?)?,
            &experiment.store("msg", &MSG)?,
        ])?;
        diff.assert_success();
        diff.assert_limits(0, 6, 0);
    }

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn notations() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut subkey_signer =
                cert.keys().subkeys().secret().next().unwrap()
                .key().clone().into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .add_notation("human-readable@user.example.org",
                              "a human-readable value",
                              NotationDataFlags::empty().set_human_readable(),
                              false)?
                .add_notation("machine-readable@user.example.org",
                              "a machine-readable value",
                              NotationDataFlags::empty(),
                              false)?
                .add_notation("human-readable-fake-ietf",
                              "a human-readable value",
                              NotationDataFlags::empty().set_human_readable(),
                              false)?
                .add_notation("machine-readable-fake-ietf",
                              "a machine-readable value",
                              NotationDataFlags::empty(),
                              false)?
                .sign_message(&mut subkey_signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    test_detached_sig_with(&mut experiment, true, &cert, sig,
                           vec!["--verify-options=show-notations"],
                           false,
                           true)
}

#[test]
#[ntest::timeout(600000)]
fn no_data() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || CertBuilder::new()
            .set_creation_time(Experiment::now())
            .add_userid("Alice Lovelace <alice@lovelace.name>")
            .add_signing_subkey()
            .generate()
            .map(|(cert, _rev)| cert),
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut subkey_signer =
                cert.keys().subkeys().secret().next().unwrap()
                .key().clone().into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut subkey_signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    // Non-existent signed data.
    let diff = experiment.invoke(&[
        "--batch",
        "--verify",
        "--no-default-keyring",
        "--keyring", &experiment.store("cert", &cert.to_vec()?)?,
        &experiment.store("data.sig", &sig.to_vec()?)?,
        "doesn't-exist",
    ])?;
    diff.assert_failure();
    diff.assert_limits(0, 13, 0);

    // No signed data.
    let diff = experiment.invoke(&[
        "--batch",
        "--verify",
        "--no-default-keyring",
        "--keyring", &experiment.store("cert", &cert.to_vec()?)?,
        &experiment.store("data.sig", &sig.to_vec()?)?,
    ])?;
    diff.assert_failure();
    diff.assert_limits(0, 0, 0);

    // Zero-sized keyring, detached signature.
    let diff = experiment.invoke(&[
        "--batch",
        "--verify",
        "--no-default-keyring",
        "--keyring", &experiment.store("zero-keyring", b"")?,
        &experiment.store("data.sig", &sig.to_vec()?)?,
        &experiment.store("msg.txt", MSG)?,
    ])?;
    diff.assert_failure();
    diff.assert_limits(0, 6, 0);

    // Truncated keyring, detached signature.
    let diff = experiment.invoke(&[
        "--batch",
        "--verify",
        "--no-default-keyring",
        "--keyring", &experiment.store("trunc-keyring", &cert.to_vec()?[..100])?,
        &experiment.store("data.sig", &sig.to_vec()?)?,
        &experiment.store("msg.txt", MSG)?,
    ])?;
    diff.assert_failure();
    // Why the big difference?  We emit a rather helpful error message.
    diff.assert_limits(0, 150, 0);

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn revoked_cert() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || -> Result<Cert> {
            let (cert, rev) = CertBuilder::new()
                .set_creation_time(Experiment::now() - Duration::new(3600, 0))
                .add_userid("Alice Lovelace <alice@lovelace.name>")
                .add_signing_subkey()
                .generate()?;
            Ok(cert.insert_packets(rev)?.0)
        },
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut signer = cert.keys().subkeys()
                .nth(0).unwrap().key().clone()
                .parts_into_secret()?.into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    test_detached_sig_with(&mut experiment,
                           true, &cert,
                           sig, vec![],
                           true,
                           true)?;

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn revoked_subkey() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || -> Result<Cert> {
            let cert = CertBuilder::new()
                .set_creation_time(Experiment::now() - Duration::new(3600, 0))
                .add_userid("Alice Lovelace <alice@lovelace.name>")
                .add_signing_subkey()
                .generate()
                .map(|(cert, _rev)| cert)?;

            let mut signer = cert.primary_key().key().clone()
                .parts_into_secret()?.into_keypair()?;
            let subkey = cert.keys().subkeys().nth(0).unwrap();
            let sig = SubkeyRevocationBuilder::new()
                .set_signature_creation_time(
                    Experiment::now() - Duration::new(1800, 0))?
                .set_reason_for_revocation(ReasonForRevocation::KeyRetired,
                                           b"don't use it anymore")?
                .build(&mut signer, &cert, subkey.key(), None)?;

            Ok(cert.insert_packets(sig)?.0)
        },
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut signer = cert.keys().subkeys()
                .nth(0).unwrap().key().clone()
                .parts_into_secret()?.into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    test_detached_sig_with(&mut experiment,
                           true, &cert,
                           sig, vec![],
                           true,
                           true)?;

    Ok(())
}

#[test]
#[ntest::timeout(600000)]
fn revoked_cert_and_subkey() -> Result<()> {
    let mut experiment = make_experiment!()?;
    let cert = experiment.artifact(
        "cert",
        || -> Result<Cert> {
            let (cert, rev) = CertBuilder::new()
                .set_creation_time(Experiment::now() - Duration::new(3600, 0))
                .add_userid("Alice Lovelace <alice@lovelace.name>")
                .add_signing_subkey()
                .generate()?;

            let mut signer = cert.primary_key().key().clone()
                .parts_into_secret()?.into_keypair()?;
            let subkey = cert.keys().subkeys().nth(0).unwrap();
            let sig = SubkeyRevocationBuilder::new()
                .set_signature_creation_time(
                    Experiment::now() - Duration::new(1800, 0))?
                .set_reason_for_revocation(ReasonForRevocation::KeyRetired,
                                           b"don't use it anymore")?
                .build(&mut signer, &cert, subkey.key(), None)?;

            Ok(cert.insert_packets(vec![sig, rev])?.0)
        },
        |a, f| a.as_tsk().serialize(f),
        |b| Cert::from_bytes(&b))?;

    let sig = experiment.artifact(
        "sig",
        || -> Result<Packet> {
            let mut signer = cert.keys().subkeys()
                .nth(0).unwrap().key().clone()
                .parts_into_secret()?.into_keypair()?;

            SignatureBuilder::new(SignatureType::Binary)
                .set_signature_creation_time(Experiment::now())?
                .sign_message(&mut signer, MSG)
                .map(Into::into)
        },
        |a, f| a.serialize(f),
        |b| Packet::from_bytes(&b))?;

    test_detached_sig_with(&mut experiment,
                           true, &cert,
                           sig, vec![],
                           true,
                           true)?;

    Ok(())
}
