// pub, fetch by fpr, verify no uid
// verify uid fetch by fpr fetch by uid
// verify again
// verify other uid fetch by ui1 uid2 fpr
// pub again
// pub with less uid
// pub with new uid
//
// pub & verify
// req del one
// fetch by uid & fpr
// confirm
// fetch by uid & fpr
// confirm again
// fetch by uid & fpr

use anyhow::Result;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;

use crate::types::{Email, Fingerprint, KeyID};
use crate::{Database, Query};
use sequoia_openpgp::{
    cert::{CertBuilder, UserIDRevocationBuilder},
    Cert, Packet,
    packet::{UserID, signature::*},
    parse::Parse,
    types::{KeyFlags, ReasonForRevocation, SignatureType, RevocationStatus},
};

use crate::openpgp_utils::POLICY;

use crate::EmailAddressStatus;
use crate::TpkStatus;

use crate::DatabaseTransaction;

fn check_mail_none<'a>(db: &impl Database<'a>, email: &Email) {
    assert!(db.by_email(email).is_none());
    assert!(db.by_email_wkd(email).is_none());
}

fn check_mail_some<'a>(db: &impl Database<'a>, email: &Email) {
    assert!(db.by_email(email).is_some());
    assert!(db.by_email_wkd(email).is_some());
}

pub fn test_uid_verification<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let str_uid2 = "Test B <test_b@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let uid1 = UserID::from(str_uid1);
    let uid2 = UserID::from(str_uid2);
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload key
    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);

    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::NotPublished),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    {
        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert!(key.userids().next().is_none());
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());
    }

    // fail to fetch by uid
    check_mail_none(db, &email1);
    check_mail_none(db, &email2);

    // verify 1st uid
    db.set_email_published(&fpr, &email1).unwrap();

    {
        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert!(key.userids().nth(1).is_none());
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());

        let uid = key.userids().next().unwrap().userid().clone();

        assert!((uid == uid1) ^ (uid == uid2));
        let email = Email::from_str(core::str::from_utf8(uid.value()).unwrap()).unwrap();
        assert_eq!(db.by_email(&email).unwrap(), raw);

        if email1 == email {
            assert!(db.by_email(&email2).is_none());
        } else if email2 == email {
            assert!(db.by_email(&email1).is_none());
        } else {
            unreachable!()
        }
    }

    // this operation is idempotent - let's try again!
    db.set_email_published(&fpr, &tpk_status.email_status[0].0)
        .unwrap();

    {
        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert!(key.userids().nth(1).is_none());
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());

        let uid = key.userids().next().unwrap().userid().clone();

        assert!((uid == uid1) ^ (uid == uid2));
        let email = Email::from_str(core::str::from_utf8(uid.value()).unwrap()).unwrap();
        assert_eq!(db.by_email(&email).unwrap(), raw);

        if email1 == email {
            assert!(db.by_email(&email2).is_none());
        } else if email2 == email {
            assert!(db.by_email(&email1).is_none());
        } else {
            unreachable!()
        }
    }

    // verify 2nd uid
    db.set_email_published(&fpr, &tpk_status.email_status[1].0)
        .unwrap();

    {
        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert_eq!(key.userids().len(), 2);
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());

        let myuid1 = key.userids().next().unwrap().userid().clone();
        let myuid2 = key.userids().nth(1).unwrap().userid().clone();

        assert_eq!(db.by_email(&email1).unwrap(), raw);
        assert_eq!(db.by_email(&email2).unwrap(), raw);
        assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
    }

    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::Published),
                (email2.clone(), EmailAddressStatus::Published),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // publish w/ one uid less
    {
        let short_tpk = cert_without_uid(tpk, &uid1);

        let tpk_status = db.merge(short_tpk).unwrap().into_tpk_status();
        assert_eq!(
            TpkStatus {
                is_revoked: false,
                email_status: vec!(
                    (email1.clone(), EmailAddressStatus::Published),
                    (email2.clone(), EmailAddressStatus::Published),
                ),
                unparsed_uids: 0,
            },
            tpk_status
        );

        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert_eq!(key.userids().len(), 2);
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());

        let myuid1 = key.userids().next().unwrap().userid().clone();
        let myuid2 = key.userids().nth(1).unwrap().userid().clone();

        assert_eq!(db.by_email(&email1).unwrap(), raw);
        assert_eq!(db.by_email(&email2).unwrap(), raw);
        assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
    }

    // publish w/one uid more
    // FIXME how to construct a UserIDBinding?
    /*{
        let mut packets = tpk
            .clone()
            .into_packet_pile()
            .into_children()
            .filter(|pkt| {
                match pkt {
                    Packet::UserID(ref uid) => *uid != uid1,
                    _ => true,
                }
            })
            .collect::<Vec<_>>();
        let str_uid3 = "Test C <test_c@example.com>";
        let uid3 = UserID::from(str_uid3);

        let email3 = Email::from_str(str_uid3).unwrap();
        let key = tpk.primary_key();
        let mut signer = key.clone().into_keypair().unwrap();
        let bind = UserIDBinding::default(key, uid3.clone(), &mut signer).unwrap();

        packets.push(Packet::UserID(uid3.clone()));
        packets
            .push(Packet::Signature(bind.selfsigs()[0].clone()));

        let pile : PacketPile = packets.into();
        let ext_tpk = Cert::from_packet_pile(pile).unwrap();
        let tpk_status = db.merge(ext_tpk).unwrap().into_tpk_status();

        assert_eq!(TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email2.clone(), EmailAddressStatus::Published),
                (email3.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        }, tpk_status);

        // fetch by fpr
        let raw = db.by_fpr(&fpr).unwrap();
        let key = Cert::from_bytes(raw.as_bytes()).unwrap();

        assert_eq!(key.userids().len(), 2);
        assert!(key.user_attributes().next().is_none());
        assert!(key.keys().subkeys().next().is_none());

        let myuid1 = key.userids().next().unwrap().userid().clone();
        let myuid2 = key.userids().skip(1).next().unwrap().userid().clone();

        assert_eq!(db.by_email(&email1).unwrap(), raw);
        assert_eq!(db.by_email(&email2).unwrap(), raw);
        assert!(
            ((myuid1 == uid1) & (myuid2 == uid2))
                ^ ((myuid1 == uid2) & (myuid2 == uid1))
        );
        assert!(db.by_email(&email3).is_none());
    }*/
}

pub fn test_regenerate<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_signing_subkey()
        .add_transport_encryption_subkey()
        .generate()
        .unwrap()
        .0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();
    let fpr_sign: Fingerprint = tpk
        .keys()
        .with_policy(&POLICY, None)
        .for_signing()
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();
    let fpr_encrypt: Fingerprint = tpk
        .keys()
        .with_policy(&POLICY, None)
        .key_flags(KeyFlags::empty().set_transport_encryption())
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();

    // upload key
    db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);

    db.regenerate_links(&fpr).unwrap();
    check_mail_none(db, &email1);
    assert!(db.by_fpr(&fpr).is_some());
    assert!(db.by_fpr(&fpr_sign).is_some());
    assert!(db.by_fpr(&fpr_encrypt).is_none());

    db.set_email_published(&fpr, &email1).unwrap();

    {
        let lock = db.transaction().unwrap();
        lock.unlink_email(&email1, &fpr).unwrap();
        lock.commit().unwrap();
    }
    assert!(db.check_consistency().is_err());
    db.regenerate_links(&fpr).unwrap();
    db.check_consistency().expect("consistency must return Ok");

    {
        let lock = db.transaction().unwrap();
        lock.unlink_fpr(&fpr, &fpr).unwrap();
        lock.commit().unwrap();
    }
    assert!(db.check_consistency().is_err());
    db.regenerate_links(&fpr).unwrap();
    db.check_consistency().expect("consistency must return Ok");

    {
        let lock = db.transaction().unwrap();
        lock.unlink_fpr(&fpr_sign, &fpr).unwrap();
        lock.commit().unwrap();
    }
    assert!(db.check_consistency().is_err());
    db.regenerate_links(&fpr).unwrap();
    db.check_consistency().expect("consistency must return Ok");
}

pub fn test_reupload<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let str_uid2 = "Test B <test_b@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();

    // upload key
    db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);

    // verify 1st uid
    db.set_email_published(&fpr, &email1).unwrap();
    assert!(db.by_email(&email2).is_none() ^ db.by_email(&email1).is_none());

    // reupload
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();

    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::Published),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );
    assert!(db.by_email(&email2).is_none() ^ db.by_email(&email1).is_none());
}

pub fn test_uid_replacement<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let tpk1 = CertBuilder::new()
        .add_userid(str_uid1)
        .generate()
        .unwrap()
        .0;
    let fpr1 = Fingerprint::try_from(tpk1.fingerprint()).unwrap();

    let tpk2 = CertBuilder::new()
        .add_userid(str_uid1)
        .generate()
        .unwrap()
        .0;
    let fpr2 = Fingerprint::try_from(tpk2.fingerprint()).unwrap();

    let pgp_fpr1 = tpk1.fingerprint();
    let pgp_fpr2 = tpk2.fingerprint();

    let email1 = Email::from_str(str_uid1).unwrap();

    // upload both keys
    db.merge(tpk1).unwrap().into_tpk_status();
    check_log_entry(db, &fpr1);
    db.merge(tpk2).unwrap().into_tpk_status();
    check_log_entry(db, &fpr2);

    // verify 1st uid
    db.set_email_published(&fpr1, &email1).unwrap();
    check_mail_some(db, &email1);
    assert_eq!(
        Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes())
            .unwrap()
            .fingerprint(),
        pgp_fpr1
    );

    assert_eq!(
        Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes())
            .unwrap()
            .userids()
            .len(),
        1
    );
    assert_eq!(
        Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes())
            .unwrap()
            .userids()
            .len(),
        0
    );

    // verify uid on other key
    db.set_email_published(&fpr2, &email1).unwrap();
    check_mail_some(db, &email1);
    assert_eq!(
        Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes())
            .unwrap()
            .fingerprint(),
        pgp_fpr2
    );

    assert_eq!(
        Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes())
            .unwrap()
            .userids()
            .len(),
        0
    );
    assert_eq!(
        Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes())
            .unwrap()
            .userids()
            .len(),
        1
    );
}

pub fn test_uid_deletion<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let str_uid2 = "Test B <test_b@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .add_signing_subkey()
        .add_transport_encryption_subkey()
        .generate()
        .unwrap()
        .0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let n_subkeys = tpk.keys().subkeys().count();
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();

    // upload key and verify uids
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::NotPublished),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    db.set_email_published(&fpr, &email1).unwrap();
    db.set_email_published(&fpr, &email2).unwrap();

    // Check that both Mappings are there, and that the Cert is
    // otherwise intact.
    let tpk = db.lookup(&Query::ByEmail(email2.clone())).unwrap().unwrap();
    assert_eq!(tpk.userids().count(), 2);
    assert_eq!(tpk.keys().subkeys().count(), n_subkeys);

    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // Delete second UID.
    db.set_email_unpublished(&fpr, &email2).unwrap();

    // Check that the second is still there, and that the Cert is
    // otherwise intact.
    let tpk = db.lookup(&Query::ByEmail(email1.clone())).unwrap().unwrap();
    assert_eq!(tpk.userids().count(), 1);
    assert_eq!(tpk.keys().subkeys().count(), n_subkeys);

    // Delete first UID.
    db.set_email_unpublished(&fpr, &email1).unwrap();

    // Check that the second is still there, and that the Cert is
    // otherwise intact.
    let tpk = db
        .lookup(&Query::ByFingerprint(tpk.fingerprint().try_into().unwrap()))
        .unwrap()
        .unwrap();
    assert_eq!(tpk.userids().count(), 0);
    assert_eq!(tpk.keys().subkeys().count(), n_subkeys);
}

pub fn test_subkey_lookup<'a>(db: &'a mut impl Database<'a>) {
    let tpk = CertBuilder::new()
        .add_userid("Testy <test@example.com>")
        .add_signing_subkey()
        .add_transport_encryption_subkey()
        .generate()
        .unwrap()
        .0;

    // upload key
    let _ = db.merge(tpk.clone()).unwrap().into_tpk_status();

    let fpr_primray = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let fpr_sign: Fingerprint = tpk
        .keys()
        .with_policy(&POLICY, None)
        .for_signing()
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();
    let fpr_encrypt: Fingerprint = tpk
        .keys()
        .with_policy(&POLICY, None)
        .key_flags(KeyFlags::empty().set_transport_encryption())
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();

    let raw1 = db
        .by_fpr(&fpr_primray)
        .expect("primary fpr must be linked!");
    let raw2 = db
        .by_fpr(&fpr_sign)
        .expect("signing subkey fpr must be linked!");
    // encryption subkey key id must not be linked!
    assert!(db.by_fpr(&fpr_encrypt).is_none());

    assert_eq!(raw1, raw2);
}

pub fn test_kid_lookup<'a>(db: &'a mut impl Database<'a>) {
    let tpk = CertBuilder::new()
        .add_userid("Testy <test@example.com>")
        .add_signing_subkey()
        .add_transport_encryption_subkey()
        .generate()
        .unwrap()
        .0;

    // upload key
    let _ = db.merge(tpk.clone()).unwrap().into_tpk_status();
    let kid_primray = KeyID::try_from(tpk.fingerprint()).unwrap();
    let kid_sign: KeyID = tpk
        .keys()
        .with_policy(&POLICY, None)
        .for_signing()
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();
    let kid_encrypt: KeyID = tpk
        .keys()
        .with_policy(&POLICY, None)
        .key_flags(KeyFlags::empty().set_transport_encryption())
        .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap())
        .next()
        .unwrap();

    let raw1 = db
        .by_kid(&kid_primray)
        .expect("primary key id must be linked!");
    let raw2 = db
        .by_kid(&kid_sign)
        .expect("signing subkey key id must be linked!");
    // encryption subkey key id must not be linked!
    assert!(db.by_kid(&kid_encrypt).is_none());

    assert_eq!(raw1, raw2);
}

pub fn test_upload_revoked_tpk<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "Test A <test_a@example.com>";
    let str_uid2 = "Test B <test_b@example.com>";
    let (mut tpk, revocation) = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload and publish one of the email addresses. those should be
    // automatically depublished when we upload the revoked key!
    db.merge(tpk.clone()).unwrap();
    db.set_email_published(&fpr, &email1).unwrap();

    check_mail_some(db, &email1);
    check_mail_none(db, &email2);

    tpk = tpk.insert_packets(revocation).unwrap();
    match tpk.revocation_status(&POLICY, None) {
        RevocationStatus::Revoked(_) => (),
        _ => panic!("expected Cert to be revoked"),
    }

    // upload key
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: true,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::NotPublished),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    check_mail_none(db, &email1);
    check_mail_none(db, &email2);
}

pub fn test_uid_revocation<'a>(db: &'a mut impl Database<'a>) {
    use std::{thread, time};

    let str_uid1 = "Test A <test_a@example.com>";
    let str_uid2 = "Test B <test_b@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let uid2 = UserID::from(str_uid2);
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload key
    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::NotPublished),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // verify uid
    db.set_email_published(&fpr, &tpk_status.email_status[0].0)
        .unwrap();
    db.set_email_published(&fpr, &tpk_status.email_status[1].0)
        .unwrap();

    // fetch both uids
    check_mail_some(db, &email1);
    check_mail_some(db, &email2);

    thread::sleep(time::Duration::from_secs(2));

    // revoke one uid
    let sig = {
        let policy = &POLICY;
        let uid = tpk
            .userids()
            .with_policy(policy, None)
            .find(|b| *b.userid() == uid2)
            .unwrap();
        assert_eq!(
            RevocationStatus::NotAsFarAsWeKnow,
            uid.revocation_status(&POLICY, None)
        );

        let mut keypair = tpk
            .primary_key()
            .bundle()
            .key()
            .clone()
            .parts_into_secret()
            .unwrap()
            .into_keypair()
            .unwrap();
        UserIDRevocationBuilder::new()
            .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/")
            .unwrap()
            .build(&mut keypair, &tpk, uid.userid(), None)
            .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    let tpk = tpk.insert_packets(sig).unwrap();
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::Published),
                (email2.clone(), EmailAddressStatus::Revoked),
            ),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // Fail to fetch by the revoked uid, ok by the non-revoked one.
    check_mail_some(db, &email1);
    check_mail_none(db, &email2);
}

/* FIXME I couldn't get this to work.
pub fn test_uid_revocation_fake(db: &mut D) {
    use std::{thread, time};

    let str_uid = "Test A <test_a@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid)
        .generate()
        .unwrap()
        .0;
    let tpk_fake = CertBuilder::new()
        .generate()
        .unwrap()
        .0;
    let uid = UserID::from(str_uid);
    let email = Email::from_str(str_uid).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload key
    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    assert_eq!(TpkStatus {
        is_revoked: false,
        email_status: vec!(
            (email.clone(), EmailAddressStatus::NotPublished),
        ),
        unparsed_uids: 0,
    }, tpk_status);

    // verify uid
    db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap();

    // fetch both uids
    assert!(db.by_email(&email).is_some());

    thread::sleep(time::Duration::from_secs(2));

    // revoke one uid
    let uid = tpk.userids().find(|b| *b.userid() == uid).cloned().unwrap();
    let sig = {
        assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None));

        let mut keypair = tpk.primary_key().clone().into_keypair().unwrap();
        uid.userid().revoke(
            &mut keypair,
            &tpk_fake,
            ReasonForRevocation::UIDRetired,
            b"It was the maid :/",
            None,
            None,
        )
        .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    // XXX how to get the bad revocation into the packet pile?
    let pile: PacketPile = tpk
        .into_packet_pile()
        .replace(&[ 0 ], 3, [
                 uid.userid().clone().into(),
                 uid.binding_signature().unwrap().clone().into(),
                 // sig.into(),
        ].to_vec())
        .unwrap()
        .into();
    println!("{:?}", pile);
    let tpk = Cert::from_packet_pile(pile).unwrap();
    println!("{:?}", tpk);
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    assert_eq!(TpkStatus {
        is_revoked: false,
        email_status: vec!(
            (email.clone(), EmailAddressStatus::Published),
        ),
        unparsed_uids: 0,
    }, tpk_status);

    // Fail to fetch by the revoked uid, ok by the non-revoked one.
    assert!(db.by_email(&email).is_some());
}
*/

pub fn test_unlink_uid<'a>(db: &'a mut impl Database<'a>) {
    let uid = "Test A <test_a@example.com>";
    let email = Email::from_str(uid).unwrap();

    // Upload key and verify it.
    let tpk = CertBuilder::new().add_userid(uid).generate().unwrap().0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    db.merge(tpk.clone()).unwrap().into_tpk_status();
    db.set_email_published(&fpr, &email).unwrap();
    check_mail_some(db, &email);

    // Create a 2nd key with same uid, and revoke the uid.
    let tpk_evil = CertBuilder::new().add_userid(uid).generate().unwrap().0;
    let fpr_evil = Fingerprint::try_from(tpk_evil.fingerprint()).unwrap();
    let sig = {
        let policy = &POLICY;
        let uid = tpk_evil
            .userids()
            .with_policy(policy, None)
            .find(|b| b.userid().value() == uid.as_bytes())
            .unwrap();
        assert_eq!(
            RevocationStatus::NotAsFarAsWeKnow,
            uid.revocation_status(&POLICY, None)
        );

        let mut keypair = tpk_evil
            .primary_key()
            .bundle()
            .key()
            .clone()
            .parts_into_secret()
            .unwrap()
            .into_keypair()
            .unwrap();

        UserIDRevocationBuilder::new()
            .set_reason_for_revocation(
                ReasonForRevocation::UIDRetired,
                b"I just had to quit, I couldn't bear it any longer",
            )
            .unwrap()
            .build(&mut keypair, &tpk_evil, uid.userid(), None)
            .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    let tpk_evil = tpk_evil.insert_packets(sig).unwrap();
    let tpk_status = db.merge(tpk_evil).unwrap().into_tpk_status();
    check_log_entry(db, &fpr_evil);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::Revoked),),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // Check that when looking up by email, we still get the former
    // Cert.
    assert_eq!(
        db.lookup(&Query::ByEmail(email))
            .unwrap()
            .unwrap()
            .fingerprint(),
        tpk.fingerprint()
    );
}

pub fn get_userids(armored: &str) -> Vec<UserID> {
    let tpk = Cert::from_bytes(armored.as_bytes()).unwrap();
    tpk.userids()
        .map(|binding| binding.userid().clone())
        .collect()
}

// If multiple keys have the same email address, make sure things work
// as expected.
pub fn test_same_email_1<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "A <test@example.com>";
    let tpk1 = CertBuilder::new()
        .add_userid(str_uid1)
        .generate()
        .unwrap()
        .0;
    let fpr1 = Fingerprint::try_from(tpk1.fingerprint()).unwrap();
    let uid1 = UserID::from(str_uid1);
    let email1 = Email::from_str(str_uid1).unwrap();

    let str_uid2 = "B <test@example.com>";
    let tpk2 = CertBuilder::new()
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let fpr2 = Fingerprint::try_from(tpk2.fingerprint()).unwrap();
    let uid2 = UserID::from(str_uid2);
    let email2 = Email::from_str(str_uid2).unwrap();

    // upload keys.
    let tpk_status1 = db.merge(tpk1).unwrap().into_tpk_status();
    check_log_entry(db, &fpr1);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email1.clone(), EmailAddressStatus::NotPublished),),
            unparsed_uids: 0,
        },
        tpk_status1
    );
    let tpk_status2 = db.merge(tpk2.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr2);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email2.clone(), EmailAddressStatus::NotPublished),),
            unparsed_uids: 0,
        },
        tpk_status2
    );

    // verify tpk1
    db.set_email_published(&fpr1, &tpk_status1.email_status[0].0)
        .unwrap();

    // fetch by both user ids.  Even though we didn't verify uid2, the
    // email is the same, and both should return tpk1.
    assert_eq!(
        get_userids(&db.by_email(&email1).unwrap()[..]),
        vec![uid1.clone()]
    );
    assert_eq!(get_userids(&db.by_email(&email2).unwrap()[..]), vec![uid1]);

    // verify tpk2
    db.set_email_published(&fpr2, &tpk_status2.email_status[0].0)
        .unwrap();

    // fetch by both user ids.  We should now get tpk2.
    assert_eq!(
        get_userids(&db.by_email(&email1).unwrap()[..]),
        vec![uid2.clone()]
    );
    assert_eq!(
        get_userids(&db.by_email(&email2).unwrap()[..]),
        vec![uid2.clone()]
    );

    // revoke tpk2's uid
    let sig = {
        let policy = &POLICY;
        let uid = tpk2
            .userids()
            .with_policy(policy, None)
            .find(|b| *b.userid() == uid2)
            .unwrap();
        assert_eq!(
            RevocationStatus::NotAsFarAsWeKnow,
            uid.revocation_status(&POLICY, None)
        );

        let mut keypair = tpk2
            .primary_key()
            .bundle()
            .key()
            .clone()
            .parts_into_secret()
            .unwrap()
            .into_keypair()
            .unwrap();

        UserIDRevocationBuilder::new()
            .set_reason_for_revocation(ReasonForRevocation::KeyRetired, b"It was the maid :/")
            .unwrap()
            .build(&mut keypair, &tpk2, uid.userid(), None)
            .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    let tpk2 = tpk2.insert_packets(sig).unwrap();
    let tpk_status2 = db.merge(tpk2).unwrap().into_tpk_status();
    check_log_entry(db, &fpr2);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email2.clone(), EmailAddressStatus::Revoked),),
            unparsed_uids: 0,
        },
        tpk_status2
    );

    // fetch by both user ids.  We should get nothing.
    check_mail_none(db, &email1);
    check_mail_none(db, &email2);
}

// If a key has multiple user ids with the same email address, make
// sure things still work. We do this twice (see above), to
// make sure the order isn't relevant when revoking one user id
// but leaving the other.
pub fn test_same_email_2<'a>(db: &'a mut impl Database<'a>) {
    use std::{thread, time};

    let str_uid1 = "A <test@example.com>";
    let str_uid2 = "B <test@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let uid1 = UserID::from(str_uid1);
    let uid2 = UserID::from(str_uid2);
    let email = Email::from_str(str_uid1).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload key
    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);

    // verify uid1
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::NotPublished),),
            unparsed_uids: 0,
        },
        tpk_status
    );
    db.set_email_published(&fpr, &tpk_status.email_status[0].0)
        .unwrap();

    // fetch by both user ids.
    assert_eq!(
        get_userids(&db.by_email(&email).unwrap()[..]),
        vec![uid1.clone(), uid2.clone()]
    );

    thread::sleep(time::Duration::from_secs(2));

    // revoke one uid
    let sig = {
        let policy = &POLICY;
        let uid = tpk
            .userids()
            .with_policy(policy, None)
            .find(|b| *b.userid() == uid2)
            .unwrap();
        assert_eq!(
            RevocationStatus::NotAsFarAsWeKnow,
            uid.revocation_status(&POLICY, None)
        );

        let mut keypair = tpk
            .primary_key()
            .bundle()
            .key()
            .clone()
            .parts_into_secret()
            .unwrap()
            .into_keypair()
            .unwrap();
        UserIDRevocationBuilder::new()
            .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/")
            .unwrap()
            .build(&mut keypair, &tpk, uid.userid(), None)
            .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    let tpk = tpk.insert_packets(sig).unwrap();
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::Published),),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // fetch by both user ids.  We should still get both user ids.
    assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), vec![uid1]);
}

// If a key has multiple user ids with the same email address, make
// sure things still work. We do this twice (see above), to
// make sure the order isn't relevant when revoking one user id
// but leaving the other.
pub fn test_same_email_3<'a>(db: &'a mut impl Database<'a>) {
    use std::{thread, time};

    let str_uid1 = "A <test@example.com>";
    let str_uid2 = "B <test@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let uid1 = UserID::from(str_uid1);
    let uid2 = UserID::from(str_uid2);
    let email = Email::from_str(str_uid1).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // upload key
    let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);

    // verify uid1
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::NotPublished),),
            unparsed_uids: 0,
        },
        tpk_status
    );
    db.set_email_published(&fpr, &tpk_status.email_status[0].0)
        .unwrap();

    // fetch by both user ids.
    assert_eq!(
        get_userids(&db.by_email(&email).unwrap()[..]),
        vec![uid1.clone(), uid2.clone()]
    );

    thread::sleep(time::Duration::from_secs(2));

    // revoke one uid
    let sig = {
        let policy = &POLICY;
        let uid = tpk
            .userids()
            .with_policy(policy, None)
            .find(|b| *b.userid() == uid1)
            .unwrap();
        assert_eq!(
            RevocationStatus::NotAsFarAsWeKnow,
            uid.revocation_status(&POLICY, None)
        );

        let mut keypair = tpk
            .primary_key()
            .bundle()
            .key()
            .clone()
            .parts_into_secret()
            .unwrap()
            .into_keypair()
            .unwrap();
        UserIDRevocationBuilder::new()
            .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/")
            .unwrap()
            .build(&mut keypair, &tpk, uid.userid(), None)
            .unwrap()
    };
    assert_eq!(sig.typ(), SignatureType::CertificationRevocation);
    let tpk = tpk.insert_packets(sig).unwrap();
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::Published),),
            unparsed_uids: 0,
        },
        tpk_status
    );

    assert_eq!(
        get_userids(&db.by_email(&email).unwrap()[..]),
        vec![uid2.clone()]
    );

    // make sure this survives depulication and publication of that same email address
    db.set_email_unpublished(&fpr, &email).unwrap();
    db.set_email_published(&fpr, &email).unwrap();
    assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), vec![uid2]);
}

// If a key has a verified email address, make sure newly uploaded user
// ids with the same email are published as well.
pub fn test_same_email_4<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "A <test@example.com>";
    let str_uid2 = "B <test@example.com>";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let uid1 = UserID::from(str_uid1);
    let uid2 = UserID::from(str_uid2);
    let email = Email::from_str(str_uid1).unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    let cert_uid_1 = cert_without_uid(tpk.clone(), &uid2);
    let cert_uid_2 = cert_without_uid(tpk, &uid1);

    // upload key
    let tpk_status = db.merge(cert_uid_1).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    db.set_email_published(&fpr, &tpk_status.email_status[0].0)
        .unwrap();
    assert_eq!(
        get_userids(&db.by_email(&email).unwrap()[..]),
        vec![uid1.clone()]
    );

    let tpk_status = db.merge(cert_uid_2).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email.clone(), EmailAddressStatus::Published),),
            unparsed_uids: 0,
        },
        tpk_status
    );

    // fetch by both user ids.  We should still get both user ids.
    assert_eq!(
        get_userids(&db.by_email(&email).unwrap()[..]),
        vec![uid1, uid2]
    );
}

pub fn test_bad_uids<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "foo@bar.example <foo@bar.example>";
    let str_uid2 = "A <test@example.com>";
    let str_uid3 = "lalalalaaaaa";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .add_userid(str_uid3)
        .generate()
        .unwrap()
        .0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();

    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1.clone(), EmailAddressStatus::NotPublished),
                (email2.clone(), EmailAddressStatus::NotPublished),
            ),
            unparsed_uids: 1,
        },
        tpk_status
    );

    db.set_email_published(&fpr, &email2).unwrap();

    let tpk_status = db
        .get_tpk_status(&fpr, &[email1.clone(), email2.clone()])
        .unwrap();
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!(
                (email1, EmailAddressStatus::NotPublished),
                (email2, EmailAddressStatus::Published),
            ),
            unparsed_uids: 1,
        },
        tpk_status
    );
}

fn cert_without_signature_at(cert: Cert, mut index: i32) -> Cert {
    let packets = cert
        .into_packet_pile()
        .into_children()
        .filter(|pkt| match pkt {
            Packet::Signature(_) => {
                let result = index == 0;
                index -= 1;
                result
            }
            _ => true,
        });
    Cert::from_packets(packets).unwrap()
}

pub fn test_unsigned_uids<'a>(db: &'a mut impl Database<'a>) {
    let str_uid1 = "test1@example.com";
    let str_uid2 = "test2@example.com";
    let tpk = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid(str_uid2)
        .generate()
        .unwrap()
        .0;
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();

    let tpk = cert_without_signature_at(tpk, 1);

    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: false,
            email_status: vec!((email1.clone(), EmailAddressStatus::NotPublished),),
            unparsed_uids: 0,
        },
        tpk_status
    );
}

pub fn test_no_selfsig<'a>(db: &'a mut impl Database<'a>) {
    let (mut tpk, revocation) = CertBuilder::new().generate().unwrap();
    let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();

    // don't allow upload of naked key
    assert!(db.merge(tpk.clone()).is_err());

    // with revocation, it's ok
    tpk = tpk.insert_packets(revocation).unwrap();
    let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
    check_log_entry(db, &fpr);
    assert_eq!(
        TpkStatus {
            is_revoked: true,
            email_status: vec!(),
            unparsed_uids: 0
        },
        tpk_status
    );
}

/// Makes sure that attested key signatures are correctly handled.
pub fn attested_key_signatures<'a>(db: &'a mut impl Database<'a>) -> Result<()> {
    use sequoia_openpgp::types::*;
    use std::time::{Duration, SystemTime};
    let t0 = SystemTime::now() - Duration::new(5 * 60, 0);
    let t1 = SystemTime::now() - Duration::new(4 * 60, 0);

    let (alice, _) = CertBuilder::new()
        .set_creation_time(t0)
        .add_userid("alice@foo.com")
        .generate()?;
    let mut alice_signer = alice
        .primary_key()
        .key()
        .clone()
        .parts_into_secret()?
        .into_keypair()?;

    let (bob, _) = CertBuilder::new()
        .set_creation_time(t0)
        .add_userid("bob@bar.com")
        .generate()?;
    let bobs_fp = Fingerprint::try_from(bob.fingerprint())?;
    let mut bob_signer = bob
        .primary_key()
        .key()
        .clone()
        .parts_into_secret()?
        .into_keypair()?;

    // Have Alice certify the binding between "bob@bar.com" and
    // Bob's key.
    let alice_certifies_bob = bob.userids().next().unwrap().userid().bind(
        &mut alice_signer,
        &bob,
        SignatureBuilder::new(SignatureType::GenericCertification)
            .set_signature_creation_time(t1)?,
    )?;

    // Have Bob attest that certification.
    let attestations = bob.userids().next().unwrap().attest_certifications(
        &POLICY,
        &mut bob_signer,
        vec![&alice_certifies_bob],
    )?;
    assert_eq!(attestations.len(), 1);
    let attestation = attestations[0].clone();

    // Now for the test.  First, import Bob's cert as is.
    db.merge(bob.clone())?;
    check_log_entry(db, &bobs_fp);

    // Confirm the email so that we can inspect the userid component.
    db.set_email_published(&bobs_fp, &Email::from_str("bob@bar.com")?)?;

    // Then, add the certification, merge into the db, check that the
    // certification is stripped.
    let bob = bob.insert_packets(vec![alice_certifies_bob.clone()])?;
    db.merge(bob.clone())?;
    check_log_entry(db, &bobs_fp);
    let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
    assert_eq!(bob_.bad_signatures().count(), 0);
    assert_eq!(bob_.userids().next().unwrap().certifications().count(), 0);

    // Add the attestation, merge into the db, check that the
    // certification is now included.
    let bob_attested = bob.clone().insert_packets(vec![attestation])?;
    db.merge(bob_attested.clone())?;
    check_log_entry(db, &bobs_fp);
    let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
    assert_eq!(bob_.bad_signatures().count(), 0);
    assert_eq!(bob_.userids().next().unwrap().certifications().count(), 1);
    assert_eq!(
        bob_.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attestation_key_signatures()
            .count(),
        1
    );
    assert_eq!(
        bob_.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attested_certifications()
            .count(),
        1
    );

    // Make a random merge with Bob's unattested cert, demonstrating
    // that the attestation still works.
    db.merge(bob.clone())?;
    check_log_entry(db, &bobs_fp);
    let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
    assert_eq!(bob_.bad_signatures().count(), 0);
    assert_eq!(bob_.userids().next().unwrap().certifications().count(), 1);

    // Finally, withdraw consent by overriding the attestation, merge
    // into the db, check that the certification is now gone.
    let attestations = bob_attested
        .userids()
        .next()
        .unwrap()
        .attest_certifications(&POLICY, &mut bob_signer, &[])?;
    assert_eq!(attestations.len(), 1);
    let clear_attestation = attestations[0].clone();

    let bob = bob.insert_packets(vec![clear_attestation])?;
    assert_eq!(bob.userids().next().unwrap().certifications().count(), 1);
    assert_eq!(
        bob.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attestation_key_signatures()
            .count(),
        1
    );
    assert_eq!(
        bob.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attested_certifications()
            .count(),
        0
    );

    db.merge(bob)?;
    check_log_entry(db, &bobs_fp);
    let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
    assert_eq!(bob_.bad_signatures().count(), 0);
    assert_eq!(bob_.userids().next().unwrap().certifications().count(), 0);
    assert_eq!(
        bob_.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attestation_key_signatures()
            .count(),
        1
    );
    assert_eq!(
        bob_.with_policy(&POLICY, None)?
            .userids()
            .next()
            .unwrap()
            .attested_certifications()
            .count(),
        0
    );

    Ok(())
}

fn check_log_entry<'a>(db: &impl Database<'a>, fpr: &Fingerprint) {
    let last_entry = db.get_last_log_entry().expect("must have log entry");
    assert_eq!(last_entry.to_string(), fpr.to_string());
}

fn cert_without_uid(cert: Cert, removed_uid: &UserID) -> Cert {
    let packets = cert
        .into_packet_pile()
        .into_children()
        .filter(|pkt| match pkt {
            Packet::UserID(uid) => uid != removed_uid,
            _ => true,
        });
    Cert::from_packets(packets).unwrap()
}

pub fn nonexportable_sigs<'a>(db: &'a mut impl Database<'a>) -> Result<()> {
    let str_uid1 = "Test A <test_a@example.org>";
    let str_uid2 = "Test B <test_b@example.org>";

    // Generate a cert with two User IDs, the second being bound by a
    // non-exportable binding signature.
    let (cert, _revocation) = CertBuilder::new()
        .add_userid(str_uid1)
        .add_userid_with(
            str_uid2,
            SignatureBuilder::new(SignatureType::PositiveCertification)
                .set_exportable_certification(false)?,
        )?
        .generate()
        .unwrap();
    let email1 = Email::from_str(str_uid1).unwrap();
    let email2 = Email::from_str(str_uid2).unwrap();
    let fpr = Fingerprint::try_from(cert.fingerprint()).unwrap();

    db.merge(cert).unwrap();

    // email1 is exportable, expect success.
    db.set_email_published(&fpr, &email1).unwrap();
    check_mail_some(db, &email1);

    // email2 is non-exportable, expect failure.
    db.set_email_published(&fpr, &email2).unwrap_err();
    check_mail_none(db, &email2);

    Ok(())
}
