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

use crate::packet::{
    Key,
    key,
};

use crate::Result;
use crate::Packet;
use crate::packet::signature::{
    SignatureBuilder,
    SIG_BACKDATE_BY,
    subpacket::SubpacketTag,
};
use crate::cert::prelude::*;
use crate::Error;
use crate::Profile;
use crate::crypto::{Password, Signer};
use crate::types::{
    HashAlgorithm,
    KeyFlags,
    SignatureType,
};

/// A Key builder.
///
/// A `KeyBuilder` is used to create a key, which can then be attached
/// to an existing certificate as a subkey using
/// [`KeyBuilder::subkey`].
///
/// # Examples
///
/// Generate a signing key and attach it to a certificate:
///
/// ```
/// use sequoia_openpgp as openpgp;
/// use openpgp::cert::prelude::*;
/// use openpgp::policy::StandardPolicy;
/// use openpgp::types::KeyFlags;
///
/// # fn main() -> openpgp::Result<()> {
/// let p = &StandardPolicy::new();
///
/// # let (cert, _) =
/// #     CertBuilder::general_purpose(Some("alice@example.org"))
/// #         .generate()?;
/// #
/// let vc = cert.with_policy(p, None)?;
/// # let vc1 = vc.clone();
/// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing())
///     .subkey(vc)?
///     .attach_cert()?;
/// # let vc2 = cert_new.with_policy(p, None)?;
/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count());
/// # Ok(())
/// # }
/// ```
pub struct KeyBuilder {
    flags: KeyFlags,
    cipher_suite: CipherSuite,
    password: Option<Password>,
    creation_time: Option<SystemTime>,
}
assert_send_and_sync!(KeyBuilder);

impl KeyBuilder {
    /// Returns a new `KeyBuilder`.
    ///
    /// Use [`KeyBuilder::subkey`] to generate a subkey and get a
    /// [`SubkeyBuilder`], which can be used to add the subkey to a
    /// certificate.
    pub fn new(flags: KeyFlags) -> Self {
        KeyBuilder {
            flags,
            cipher_suite: Default::default(),
            creation_time: None,
            password: None,
        }
    }

    /// Returns the selected cipher suite.
    pub fn cipher_suite(&self) -> CipherSuite {
        self.cipher_suite
    }

    /// Sets the cipher suite.
    pub fn set_cipher_suite(mut self, cipher_suite: CipherSuite) -> Self {
        self.cipher_suite = cipher_suite;
        self
    }

    /// Returns the creation time.
    ///
    /// Returns `None` if the creation time hasn't been specified.  In
    /// that case, the creation time will be set to the current time
    /// when the key material is generated by [`KeyBuilder::subkey`].
    pub fn creation_time(&self) -> Option<SystemTime> {
        self.creation_time
    }

    /// Sets the creation time.
    ///
    /// If `None`, then the creation time will be set to the current
    /// time when the key material is generated by
    /// [`KeyBuilder::subkey`].
    pub fn set_creation_time<T>(mut self, creation_time: T) -> Self
    where T: Into<Option<SystemTime>>
    {
        self.creation_time = creation_time.into();
        self
    }

    /// Returns the password, if any.
    pub fn password(&self) -> Option<&Password> {
        self.password.as_ref()
    }

    /// Sets the password.
    pub fn set_password<T>(mut self, password: T) -> Self
    where T: Into<Option<Password>>
    {
        self.password = password.into();
        self
    }

    /// Generates a key, and returns a `SubkeyBuilder`.
    ///
    /// The [`SubkeyBuilder`] will add the key to the specified
    /// certificate.
    ///
    /// If the key creation time has not been explicitly set using
    /// [`KeyBuilder::set_creation_time`], then the key's creation
    /// time is set to the current time minus a few seconds.
    ///
    /// Setting the creation time to a short time in the past solves
    /// two problems.  First, when a new binding signature is created,
    /// it must have a newer time than the previous binding signature.
    /// This policy ensures that if a second binding signature is
    /// immediately created after the key is created it does not need
    /// to be postdated and thus can be used immediately.  Second, if
    /// the key is immediately transferred to another computer and its
    /// clock is not quite synchronized, the key may appear to have
    /// been created in the future and will thus be ignored.  Although
    /// NTP is widely used, empirically it seems that some virtual
    /// machines have laggy clocks.
    pub fn subkey(self, vc: ValidCert) -> Result<SubkeyBuilder<'_>> {
        let profile = match vc.primary_key().key().version() {
            4 => Profile::RFC4880,
            6 => Profile::RFC9580,
            n => return Err(Error::InvalidOperation(format!(
                "Cannot generate a subkey for version {} primary key", n))
                            .into()),
        };

        let mut key: Key<key::SecretParts, key::SubordinateRole>
            = self.cipher_suite.generate_key(&self.flags, profile)?
            .role_into_subordinate();
        let ct = self.creation_time.unwrap_or_else(|| {
            crate::now() - Duration::new(SIG_BACKDATE_BY, 0)
        });
        key.set_creation_time(ct)?;

        let signer = key.clone().into_keypair().unwrap();

        if let Some(ref password) = self.password {
            let (k, mut secret) = key.take_secret();
            secret.encrypt_in_place(&k, password)?;
            key = k.add_secret(secret).0;
        }

        let mut builder = SubkeyBuilder::new(
            vc, key.parts_into_unspecified(), self.flags)?;
        builder = builder.set_signature_creation_time(ct)?;
        Ok(builder.set_subkey_signer(signer))
    }
}

/// A Subkey builder.
///
/// This builder simplifies attaching a subkey to a certificate, or
/// updating an existing subkey's binding signature.  It is a more
/// high-level variant of [`SignatureBuilder`], which should be used
/// if more control is needed than this builder provides.
///
/// # Security Considerations: Key Expiration
///
/// **It is essential that keys have reasonable expiration times.** If
/// a binding signature is accidentally published without an
/// expiration time, it is effectively impossible to retract this by
/// publishing a new binding signature that has an expiration.  This
/// is because an attacker may be able to withhold the newer binding
/// signature thereby causing a victim to use a key that is actually
/// expired.
///
/// The heuristic described below takes this security consideration
/// into account.  However, because the heuristic never extends a
/// key's expiration on its own, there are still cases where it is
/// necessary to set the expiration manually.
///
/// # Binding Signature
///
/// To attach a subkey to a certificate, the primary key needs to
/// issue a [subkey binding signature].  This binding signature
/// provides information about the key including its validity period
/// (i.e., when it expires), and may contain auxiliary information
/// like notations.  A subkey binding signature usually contains the
/// following information:
///
///   [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1
///
///   - [Signature creation time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.4)
///
///   - [Key flags](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.21)
///
///   - [Issuer](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5) and Issuer Fingerprint.
///
///   - [Primary key binding signature](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.26) (if the key is signing capable)
///
/// The following information is also meaningful in the context of
/// a subkey binding signature:
///
///   - [Key expiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.6)
///     (relative to the key's creation time, not the signature's
///     creation time!)
///
///   - [Signature exiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.10)
///
///   - [Exportable certification](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.11)
///
///   - [Notations](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.16)
///
/// Because a `SubkeyBuilder` is just a wrapper around a
/// [`SignatureBuilder`], refer [`SignatureBuilder`]'s documentation
/// about to understand how some of these subpackets are automatically
/// set.
///
/// It is possible to change the signature's creation time and key
/// expiration time using the
/// [`SubkeyBuilder::set_signature_creation_time`] and
/// [`SubkeyBuilder::set_key_expiration_time`] methods.  Other
/// subpackets can be modified using
/// [`SubkeyBuilder::with_signature_template`].
///
/// ## Heuristic
///
/// This builder uses a heuristic to select a binding signature to use
/// as a template and to select a key expiration.  It is possible to
/// use your own binding signature by calling
/// [`SubkeyBuilder::set_signature_template`], and override the key
/// expiration time using [`SubkeyBuilder::set_key_expiration_time`].
/// In general, you should use an existing binding signature as a
/// template to preserve any customizations that the user may have
/// made.
///
/// Because forgetting to set an expiration time can be security
/// relevant, this heuristic acts conservatively.  If possible, the
/// user interface should show the expiration time, and allow the user
/// to adjust it manually.
///
/// The heuristic is:
///
/// - If the subkey is already present on the certificate, the default
///   binding signature is based on the subkey's active binding
///   signature, and the key expiration time is reused.
///
///   If the key would expire before the binding signature becomes
///   valid then [`SubkeyBuilder::attach`] will fail.
///
///   Note: if the subkey is present, but it does not have a valid
///   binding signature, then the subkey is treated as a new subkey.
///
/// - If the subkey is new, then the active binding signature of the
///   newest live, non-revoked, valid subkey is used as the binding
///   signature template.  Newest means the key with the latest
///   Key Creation Time and not necessarily the newest binding
///   signature.  (If multiple keys have the same key creation time,
///   the key to use is chosen in an undefined, but deterministic
///   manner.)
///
///   If the certificate does not have a subkey, then a default
///   binding signature is created.  In this case, the default
///   expiration is set to the same expiration as the primary key, if
///   any.
///
///   As above, if the key would expire before the binding signature
///   becomes valid then [`SubkeyBuilder::attach`] will fail.
///
/// # Examples
///
/// Add a new, signing-capable subkey to a certificate:
///
/// ```
/// use sequoia_openpgp as openpgp;
/// use openpgp::cert::prelude::*;
/// use openpgp::policy::StandardPolicy;
/// use openpgp::types::KeyFlags;
///
/// # fn main() -> openpgp::Result<()> {
/// let p = &StandardPolicy::new();
///
/// # let (cert, _) =
/// #     CertBuilder::general_purpose(Some("alice@example.org"))
/// #         .generate()?;
/// #
/// let vc = cert.with_policy(p, None)?;
/// # let vc1 = vc.clone();
/// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing())
///     .subkey(vc)?
///     .attach_cert()?;
/// # let vc2 = cert_new.with_policy(p, None)?;
/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count());
/// # Ok(())
/// # }
/// ```
///
/// Import a raw encryption key:
///
/// ```
/// use std::time::SystemTime;
///
/// use sequoia_openpgp as openpgp;
/// use openpgp::cert::prelude::*;
/// use openpgp::packet::Key;
/// use openpgp::packet::key::Key4;
/// use openpgp::policy::StandardPolicy;
/// use openpgp::types::KeyFlags;
///
/// # fn main() -> openpgp::Result<()> {
/// let p = &StandardPolicy::new();
///
/// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F";
/// let k: Key<_, _>
///     = Key4::import_public_ed25519(q, SystemTime::now())?.into();
///
/// # let (cert, _) = CertBuilder::new().generate()?;
/// #
/// let vc = cert.with_policy(p, None)?;
/// # let vc1 = vc.clone();
/// let mut cert2 = SubkeyBuilder::new(
///     vc, k.parts_into_unspecified(),
///     KeyFlags::empty().set_transport_encryption())?
///     .attach_cert()?;
/// #
/// # let vc2 = cert2.with_policy(p, None)?;
/// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count());
/// # Ok(())
/// # }
/// ```
///
/// Change all valid, non-revoked subkeys to expire in a year from now:
///
/// ```
/// use std::time::{SystemTime, Duration};
///
/// use sequoia_openpgp as openpgp;
/// use openpgp::cert::prelude::*;
/// use openpgp::Packet;
/// use openpgp::policy::StandardPolicy;
/// use openpgp::types::KeyFlags;
///
/// # fn main() -> openpgp::Result<()> {
/// let p = &StandardPolicy::new();
///
/// let now = SystemTime::now();
/// let e = now + Duration::new(365 * 24 * 60 * 60, 0);
///
/// # let v = Duration::new(24 * 60 * 60, 0);
/// # let (cert, _) =
/// #     CertBuilder::new()
/// #         .set_creation_time(now - Duration::new(60, 0))
/// #         .add_subkey(KeyFlags::empty().set_storage_encryption(),
/// #                     v, None)
/// #         .add_subkey(KeyFlags::empty().set_signing(),
/// #                     v, None)
/// #         .generate()?;
/// # assert_eq!(cert.keys().subkeys().count(), 2);
/// let vc = cert.with_policy(p, None)?;
/// # assert_eq!(vc.keys().subkeys().count(), 2);
/// # for ka in vc.keys().subkeys() {
/// #     assert_eq!(ka.key_validity_period(), Some(v));
/// # }
///
/// // If you only want to extend non-expired keys, then add .alive().
/// let packets = vc.keys().subkeys().revoked(false)
///     .map(|ka| {
///         SubkeyBuilder::from(ka)
///             .set_signature_creation_time(now)?
///             .set_key_expiration_time(e)?
///             .attach()
///     })
///     .collect::<Result<Vec<Vec<Packet>>, _>>()?;
/// let cert = cert.insert_packets(packets.into_iter().flatten())?.0;
///
/// let vc = cert.with_policy(p, now)?;
/// # assert_eq!(vc.keys().subkeys().count(), 2);
/// for ka in vc.keys().subkeys().revoked(false) {
///     // Check that the key's expiration time is really e.  Note: We
///     // need to take into account that SystemTime has a subsecond
///     // resolution, but OpenPGP's timestamps only have a 1-second
///     // resolution.
///     assert!(e.duration_since(ka.key_expiration_time().unwrap()).unwrap()
///             < Duration::new(1, 0));
/// }
/// # Ok(())
/// # }
/// ```
pub struct SubkeyBuilder<'a> {
    vc: ValidCert<'a>,
    primary_signer: Option<Box<dyn Signer + Send + Sync + 'a>>,

    subkey: Key<key::UnspecifiedParts, key::SubordinateRole>,
    subkey_signer: Option<Box<dyn Signer + Send + Sync + 'a>>,

    template: SignatureBuilder,
}
assert_send_and_sync!(SubkeyBuilder<'_>);

impl<'a> SubkeyBuilder<'a> {
    /// Returns a SubkeyBuilder that will add the key to the specified
    /// certificate.
    ///
    /// If the subkey is already present on the certificate, then the
    /// `SubkeyBuilder` effectively adds a new binding signature to
    /// the certificate.
    pub fn new<P>(vc: ValidCert<'a>,
                  subkey: Key<P, key::SubordinateRole>,
                  subkey_flags: KeyFlags)
        -> Result<Self>
    where P: key::KeyParts,
    {
        // If the key is already present on the certificate, then we
        // use the current self signature on that subkey as the
        // template.
        let (template, key_expiration): (SignatureBuilder, Option<SystemTime>)
            = vc.keys().subkeys()
            .filter_map(|ka| {
                if ka.key().parts_as_unspecified().public_eq(&subkey) {
                    let sig = ka.binding_signature().clone();
                    let e = sig.key_validity_period().map(|v| {
                        ka.key().creation_time() + v
                    });
                    Some((sig.into(), e))
                } else {
                    None
                }
            })
            .next()
            .or_else(|| {
                // The key is completely new.  Use the active self
                // signature on the newest, non-revoked, non-expired
                // subkey.
                vc.keys().subkeys().revoked(false).alive()
                    // Fallback to sorting by fingerprint to ensure
                    // this is deterministic.
                    .max_by_key(|ka| (ka.key().creation_time(), ka.key().fingerprint()))
                    .map(|ka| {
                        let sig = ka.binding_signature().clone();
                        let e = sig.key_validity_period().map(|v| {
                            ka.key().creation_time() + v
                        });
                        (sig.into(), e)
                    })
            })
            .unwrap_or_else(|| {
                // The certificate doesn't have any valid subkeys, so
                // we don't have existing signatures that we can use
                // as a template.  In this case, we use a default
                // binding signature, and the primary key's expiration
                // time.
                (SignatureBuilder::new(SignatureType::SubkeyBinding),
                 vc.primary_key().key_validity_period().map(|v| {
                     vc.primary_key().key().creation_time() + v
                 }))
            });

        let template = template.set_key_flags(subkey_flags)?;

        let mut builder = SubkeyBuilder {
            vc,
            primary_signer: None,
            subkey: subkey.parts_into_unspecified(),
            subkey_signer: None,
            template: SignatureBuilder::new(SignatureType::SubkeyBinding),
        };
        builder = builder.set_signature_template(template);
        builder = builder.set_key_expiration_time(key_expiration)?;

        Ok(builder)
    }

    /// Like SubkeyBuilder::new, but the binding signature is supplied.
    ///
    /// # Security Considerations
    ///
    /// The key validity period (i.e., the [Key Expiration Time
    /// subpacket]) is left as is.  **The Key Expiration Time
    /// subpacket contains a relative time.** Thus, if you are using a
    /// signature from another key with a different key creation time
    /// as a template, the effective key expiration time will be
    /// different!  In this case, you should set the key expiration
    /// time explicitly by calling
    /// [`SubkeyBuilder::set_key_expiration_time`] or
    /// [`SubkeyBuilder::set_key_validity_period`].
    ///
    ///   [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13
    ///
    /// # Examples
    ///
    /// Adjusting the key expiration time:
    ///
    /// ```
    /// use std::time::{SystemTime, Duration};
    ///
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::cert::prelude::*;
    /// use openpgp::packet::Key;
    /// use openpgp::packet::key::Key4;
    /// use openpgp::policy::StandardPolicy;
    /// use openpgp::types::KeyFlags;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let p = &StandardPolicy::new();
    ///
    /// let now = SystemTime::now();
    /// let year = Duration::new(365 * 24 * 60 * 60, 0);
    /// let last_year = now - year;
    /// // cert was created last year and expires after two years.
    /// let (cert, _) =
    ///     CertBuilder::new()
    ///         .set_creation_time(now - year)
    ///         .add_subkey(KeyFlags::empty().set_transport_encryption(),
    ///                     2 * year, None)
    ///         .generate()?;
    ///
    /// // Import a raw key and add it to the certificate.  We
    /// // explicitly reuse the existing subkey's signature, and adjust
    /// // the key expiration time.
    ///
    /// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F";
    /// let k: Key<_, _> = Key4::import_public_ed25519(q, now)?.into();
    ///
    /// let vc = cert.with_policy(p, now)?;
    /// let template
    ///     = vc.keys().subkeys().next().unwrap().binding_signature().clone();
    /// # let vc1 = vc.clone();
    /// let cert2 = SubkeyBuilder::new_with(vc, k, template)
    ///     .set_key_validity_period(year)?
    ///     .attach_cert()?;
    /// let vc2 = cert2.with_policy(p, now)?;
    /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count());
    ///
    /// // Observe that both keys expire one year from now.  If we
    /// // hadn't adjusted the validity period of the new key, it would
    /// // have expired in two years from now, because the key validity
    /// // period is relative to the key's creation time!
    /// vc2.keys().subkeys().for_each(|sig| {
    ///     // SystemTime has a subsection resolution.
    ///     assert!((now + year)
    ///                 .duration_since(sig.key_expiration_time().unwrap())
    ///                 .unwrap()
    ///             < Duration::new(1, 0));
    /// });
    /// # Ok(())
    /// # }
    /// ```
    pub fn new_with<P, T>(vc: ValidCert<'a>,
                          subkey: Key<P, key::SubordinateRole>,
                          template: T)
        -> Self
    where P: key::KeyParts,
          T: Into<SignatureBuilder>,
    {
        let template = template.into();

        let mut builder = SubkeyBuilder {
            vc,
            primary_signer: None,
            subkey: subkey.parts_into_unspecified(),
            subkey_signer: None,
            template: SignatureBuilder::new(SignatureType::SubkeyBinding),
        };
        builder = builder.set_signature_template(template);
        builder
    }

    /// Sets the signature template that will be used for the binding
    /// signature.
    ///
    /// This effectively discards any previous calls to
    /// [`SubkeyBuilder::set_signature_creation_time`],
    /// [`SubkeyBuilder::set_key_expiration_time`], etc.
    ///
    /// This function modifies the template as follows:
    ///
    ///   - The hash algorithm is set to a safe default.
    ///
    /// These changes can be overridden by using
    /// [`SubkeyBuilder::with_signature_template`].
    ///
    /// # Security Considerations
    ///
    /// The key validity period (i.e., the [Key Expiration Time
    /// subpacket]) is left as is.  **This packet contains a relative
    /// time.** Thus, if you are using a Signature from another key
    /// with a different key creation time as a template, the
    /// effective key expiration time will be different!  In this
    /// case, you should set the key expiration time explicitly by
    /// calling [`SubkeyBuilder::set_key_expiration_time`] or
    /// [`SubkeyBuilder::set_key_validity_period`].
    ///
    ///   [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13
    pub fn set_signature_template<T>(mut self, template: T) -> Self
    where T: Into<SignatureBuilder>,
    {
        self.template = template.into();

        // GnuPG wants at least a 512-bit hash for P521 keys.
        self.template = self.template.set_hash_algo(HashAlgorithm::SHA512);

        self
    }

    /// Allows a function to directly modify the signature template.
    ///
    /// This function does not fail; it returns the result of the
    /// callback function.
    ///
    /// # Examples
    ///
    /// Add a notation to an existing key:
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::cert::prelude::*;
    /// use openpgp::packet::signature::subpacket::NotationDataFlags;
    /// use openpgp::policy::StandardPolicy;
    /// use openpgp::types::KeyFlags;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let p = &StandardPolicy::new();
    ///
    /// # let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?;
    /// let vc = cert.with_policy(p, None)?;
    /// let cert2 = SubkeyBuilder::from(vc.keys().subkeys().next().unwrap())
    ///     .with_signature_template(|sig| {
    ///         sig.add_notation("policy@example.org", b"1",
    ///                          NotationDataFlags::empty().set_human_readable(),
    ///                          false /* critical */)
    ///     })?
    ///     .attach_cert()?;
    /// # let vc2 = cert2.with_policy(p, None)?;
    /// # assert_eq!(vc2.keys().count(), 2);
    /// # let ka = vc2.keys().subkeys().next().unwrap();
    /// # assert_eq!(ka.self_signatures().count(), 2);
    /// # assert_eq!(
    /// #     ka.binding_signature().notation("policy@example.org")
    /// #         .collect::<Vec<_>>(),
    /// #     vec![ b"1" ]);
    /// # Ok(())
    /// # }
    /// ```
    pub fn with_signature_template<F>(mut self, f: F) -> Result<Self>
    where F: FnOnce(SignatureBuilder) -> Result<SignatureBuilder>
    {
        self.template = f(self.template.clone())?;

        Ok(self)
    }

    /// Sets the binding signature's creation time.
    ///
    /// This directly modifies the current signature template.
    ///
    /// This just calls
    /// [`SignatureBuilder::set_signature_creation_time`] on the
    /// signature template.
    pub fn set_signature_creation_time<T>(mut self, creation_time: T)
        -> Result<Self>
    where T: Into<SystemTime>
    {
        self.template = self.template.set_signature_creation_time(
            creation_time.into())?;
        Ok(self)
    }

    /// Preserves the signature creation time set in the template.
    ///
    /// This directly modifies the current signature template.
    ///
    /// This just calls
    /// [`SignatureBuilder::preserve_signature_creation_time`] on the
    /// signature template.
    pub fn preserve_signature_creation_time(mut self) -> Result<Self>
    {
        self.template
            = self.template.preserve_signature_creation_time()?;
        Ok(self)
    }

    /// Sets the key's expiration time.
    ///
    /// This directly modifies the current signature template.
    ///
    /// This returns an error if the expiration time is before the
    /// key's creation time.
    pub fn set_key_expiration_time<T>(mut self, key_expiration_time: T)
        -> Result<Self>
    where T: Into<Option<SystemTime>>
    {
        let key_expiration_time = key_expiration_time.into();
        let validity_period = key_expiration_time
            .map(|e| {
                e.duration_since(self.subkey.creation_time())
                    .map_err(|_| {
                        Error::InvalidArgument(
                            "expiration time precedes creation time".into())
                    })
            })
            .transpose()?;

        self = self.with_signature_template(|sig| {
            sig.set_key_validity_period(validity_period)
        })?;

        Ok(self)
    }

    /// Sets the key's validity period.
    ///
    /// The validity period is the amount of time after the key's
    /// creation time that the key is considered fresh (i.e., not
    /// expired).
    ///
    /// This directly modifies the current signature template.
    pub fn set_key_validity_period<T>(mut self, validity: T)
        -> Result<Self>
    where T: Into<Option<Duration>>
    {
        self = self.with_signature_template(|sig| {
            sig.set_key_validity_period(validity.into())
        })?;

        Ok(self)
    }

    /// Returns a reference to the subkey.
    pub fn key(&self) -> &Key<key::UnspecifiedParts, key::SubordinateRole> {
        &self.subkey
    }

    /// Adds a signer for the primary key.
    ///
    /// In order to attach a subkey to a certificate one or more
    /// signatures need to be issued.  First, the primary key needs to
    /// issue a [subkey binding signature].  If the subkey is signing
    /// capable, then it also needs to issue a [primary key binding
    /// signature].  By default, [`SubkeyBuilder::attach`] will
    /// automatically derive the signers from the key material.  This
    /// only works, however, if the key material is present, and it is
    /// unencrypted.  This method allows you to explicitly provide a
    /// signer for the primary key.
    ///
    ///   [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1
    ///   [primary binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1
    pub fn set_primary_key_signer<S>(mut self, signer: S) -> Self
    where S: Signer + Send + Sync + 'a,
    {
        self.primary_signer = Some(Box::new(signer));
        self
    }

    /// Adds a signer for the subkey.
    ///
    /// In order to attach a subkey to a certificate one or more
    /// signatures need to be issued.  First, the primary key needs to
    /// issue a [subkey binding signature].  If the subkey is signing
    /// capable, then it also needs to issue a [primary key binding
    /// signature].  By default, [`SubkeyBuilder::attach`] will
    /// automatically derive the signers from the key material.  This
    /// only works, however, if the key material is present, and it is
    /// unencrypted.  This method allows you to explicitly provide a
    /// signer for the subkey.
    ///
    ///   [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1
    ///   [primary binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1
    pub fn set_subkey_signer<S>(mut self, signer: S) -> Self
    where S: Signer + Send + Sync + 'a,
    {
        self.subkey_signer = Some(Box::new(signer));
        self
    }

    /// Attaches the subkey to the certificate.
    ///
    /// This method generates the appropriate signatures to attach the
    /// subkey to the certificate.
    ///
    /// This function returns an error if the expiration time would
    /// cause the key to expire before the binding signature's
    /// expiration time.
    ///
    /// This method returns a number of packets, which need to be
    /// merged into the cert.  This can be done using
    /// [`Cert::insert_packets`].
    pub fn attach(self) -> Result<Vec<Packet>> {
        let SubkeyBuilder {
            vc,
            primary_signer,
            subkey,
            subkey_signer,
            template,
        } = self;

        if template.typ() != SignatureType::SubkeyBinding {
            return Err(Error::InvalidArgument(
                format!("Expected a SubkeyBinding signature, got a {}",
                        template.typ())).into());
        }

        let mut builder = template;

        let creation_time = builder.effective_signature_creation_time()?;

        // creation_time is only None if
        // preserve_signature_creation_time is done and that's a
        // Highly Advanced Interface that doesn't need sanity checks.
        if let Some(sig_ct) = creation_time {
            if let Some(v) = builder.key_validity_period() {
                let e = subkey.creation_time() + v;
                if let Err(err) = e.duration_since(sig_ct) {
                    return Err(Error::InvalidArgument(
                        format!(
                            "key expiration precedes signature creation time \
                             (by {:?})",
                            err.duration())).into());
                }
            }
        }

        if let Some(flags) = builder.key_flags() {
            if flags.for_certification() || flags.for_signing()
                || flags.for_authentication()
            {
                // We need to create a primary key binding signature.
                let mut subkey_signer = if let Some(signer) = subkey_signer {
                    signer
                } else {
                    Box::new(
                        subkey.clone().parts_into_secret()?.into_keypair()?)
                };

                let mut backsig =
                    SignatureBuilder::new(
                        SignatureType::PrimaryKeyBinding)
                    // GnuPG wants at least a 512-bit hash for P521 keys.
                    .set_hash_algo(HashAlgorithm::SHA512)
                    .set_reference_time(creation_time)?;
                if let Some(t) = creation_time {
                    backsig = backsig.set_reference_time(t)?;
                } else {
                    backsig = backsig.preserve_signature_creation_time()?;
                }
                let backsig = backsig.sign_primary_key_binding(
                    &mut *subkey_signer, vc.primary_key().key(), &subkey)?;
                builder = builder.set_embedded_signature(backsig)?;
            } else {
                // We don't need the embedded signature, remove it.
                builder.hashed_area_mut()
                    .remove_all(SubpacketTag::EmbeddedSignature);
                builder.unhashed_area_mut()
                    .remove_all(SubpacketTag::EmbeddedSignature);
            }
        }

        let mut primary_signer = if let Some(signer) = primary_signer {
            signer
        } else {
            Box::new(
                vc.primary_key().key().clone()
                    .parts_into_secret()?.into_keypair()?)
        };

        let signature = subkey.bind(
            &mut *primary_signer, vc.cert(), builder)?;

        let subkey = if subkey.has_secret() {
            Packet::SecretSubkey(subkey.parts_into_secret().unwrap())
        } else {
            Packet::PublicSubkey(subkey.parts_into_public())
        };

        Ok(vec![subkey, signature.into()])
    }

    /// Attaches the subkey directly to the certificate.
    ///
    /// This function is like [`SubkeyBuilder::attach`], but it also
    /// merges the resulting packets into the certificate.
    ///
    /// Note: if you are adding multiple subkeys to a certificate or
    /// updating multiple subkeys, it is usually more efficient to use
    /// [`SubkeyBuilder::attach`], and then merge all the packets
    /// at once.
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::cert::prelude::*;
    /// use openpgp::policy::StandardPolicy;
    /// use openpgp::types::KeyFlags;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let p = &StandardPolicy::new();
    ///
    /// # let (cert, _) =
    /// #     CertBuilder::general_purpose(Some("alice@example.org"))
    /// #         .generate()?;
    /// #
    /// let vc = cert.with_policy(p, None)?;
    /// # let vc1 = vc.clone();
    /// let cert2 = KeyBuilder::new(KeyFlags::empty().set_signing())
    ///     .subkey(vc)?
    ///     .attach_cert()?;
    /// # let vc2 = cert2.with_policy(p, None)?;
    /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count());
    /// # Ok(())
    /// # }
    /// ```
    pub fn attach_cert(self) -> Result<Cert> {
        let cert = self.vc.cert().clone();
        let packets = self.attach()?;
        Ok(cert.insert_packets(packets)?.0)
    }
}

impl<'a, P> From<ValidPrimaryKeyAmalgamation<'a, P>> for SubkeyBuilder<'a>
where
    P: key::KeyParts + Clone,
{
    fn from(ka: ValidPrimaryKeyAmalgamation<'a, P>) -> Self {
        ValidErasedKeyAmalgamation::from(ka).into()
    }
}

impl<'a, P> From<ValidSubordinateKeyAmalgamation<'a, P>> for SubkeyBuilder<'a>
where
    P: key::KeyParts + Clone,
{
    fn from(ka: ValidSubordinateKeyAmalgamation<'a, P>) -> Self {
        ValidErasedKeyAmalgamation::from(ka).into()
    }
}

impl<'a, P> From<ValidErasedKeyAmalgamation<'a, P>> for SubkeyBuilder<'a>
where
    P: key::KeyParts + Clone,
{
    fn from(ka: ValidErasedKeyAmalgamation<'a, P>) -> SubkeyBuilder<'a> {
        let key = ka.key().clone().role_into_subordinate();
        SubkeyBuilder::new_with(
            ka.valid_cert().clone(), key, ka.binding_signature().clone())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::time::{Duration, UNIX_EPOCH};

    use crate::policy::StandardPolicy;

    #[test]
    fn expiry() -> Result<()> {
        let p = &StandardPolicy::new();

        // t0: Create certificate, keys expire at t2.
        // t1: Add a new key, heuristic should have it expire at t2.
        // t2: All keys expire.

        // We do it all in the past to make sure the current time is
        // never used.

        // Avoid milliseconds.
        let t1 = crate::now() - Duration::new(7 * 24 * 60 * 60, 0);
        let t1 = t1.duration_since(UNIX_EPOCH)?.as_secs();
        let t1 = UNIX_EPOCH + Duration::new(t1, 0);

        let t0 = t1 - Duration::new(60 * 60, 0);
        let t2 = t1 + Duration::new(60 * 60, 0);
        let validity = t2.duration_since(t0).unwrap();

        let (pre, _) =
            CertBuilder::general_purpose(Some("alice@example.org"))
            .set_creation_time(t0)
            .set_validity_period(validity)
            .generate()?;

        let vc_pre = pre.with_policy(p, t1)?;
        let post = KeyBuilder::new(KeyFlags::empty().set_signing())
            .set_creation_time(t1)
            .subkey(vc_pre)?
            .set_signature_creation_time(t1)?
            .attach_cert()?;

        let vc_post = post.with_policy(p, t1).unwrap();

        // We should have one more key.
        assert_eq!(pre.keys().count() + 1, post.keys().count());

        // Make sure the signature and backsig are valid.
        assert_eq!(post.keys().count(), vc_post.keys().count());

        // And the new key should have inherited the other keys'
        // expiration.
        eprintln!("t0: {:?}", t0);
        eprintln!("t1: {:?}", t1);
        eprintln!("t2: {:?}", t2);
        assert!(vc_post.keys().all(|ka| {
            eprintln!("{}: {:?} -> {:?}",
                      ka.key().fingerprint(),
                      ka.key().creation_time(),
                      ka.key_expiration_time());
            ka.key_expiration_time() == Some(t2)
        }));

        Ok(())
    }
}
