use anyhow::Context;

use chrono::{
    DateTime,
    Utc,
};

use sequoia_openpgp as openpgp;
use openpgp::Profile;
use openpgp::crypto::{PublicKeyAlgorithm, mpi};
use openpgp::packet::key::{Key4, Key6};
use openpgp::packet::key::UnspecifiedRole;
use openpgp::packet::key::PublicParts;
use openpgp::packet::key;
use openpgp::packet;
use openpgp::types::HashAlgorithm;
use openpgp::types::SymmetricAlgorithm;

use cryptoki::object::Attribute;
use cryptoki::object::AttributeType;
use cryptoki::object::KeyType;
use cryptoki::object::ObjectClass;
use cryptoki::object::ParameterSetType;

use crate::Result;
use crate::keyid::Bundling;
use crate::objects::Key;
use crate::oids::oid_to_str;

const TRACE: bool = false;

/// Although PKCS#11 says that the EC_POINT for ECDSA and EDDSA keys
/// should be "Public key bytes in little endian order", some HSMs
/// return the public key in DER encoding.
///
/// When DER encoded, the payload looks like this:
///
/// ```text
/// 0x04 (payload len byte) payload bytes
/// ```
///
/// This is easy to distinguish from byte in little endian order,
/// since we know the expected length length.
fn decode_ec_point(value: &[u8], expected_len: u8)
    -> Result<&[u8]>
{
    if value.len() == expected_len as usize {
        Ok(value)
    } else if value.len() == expected_len as usize + 2
        && value[0] == 0x04
        && value[1] == expected_len
    {
        Ok(&value[2..])
    } else {
        Err(anyhow::anyhow!("HSM returned an invalid ec parameter (got {} bytes, {})",
                            value.len(),
                            value
                                .iter()
                                .map(|k| format!("{:02X}", k))
                                .collect::<Vec<_>>()
                                .join("")))
    }
}

impl Key {
    // Convert the key object to an OpenPGP key object.
    pub fn to_openpgp_key(&self)
        -> Result<packet::Key<PublicParts, UnspecifiedRole>>
    {
        tracer!(TRACE, "Key::to_openpgp_key");

        let mut openpgp_key = self.openpgp_key.lock().unwrap();
        if let Some(key) = openpgp_key.as_ref() {
            return Ok(key.clone());
        }

        struct KeyInfo {
            key_id: Option<Vec<u8>>,
            object_class: Option<ObjectClass>,
            key_type: Option<KeyType>,
            key_value: Option<Vec<u8>>,
            ec_params: Option<Vec<u8>>,
            ec_point: Option<Vec<u8>>,
            modulus: Option<Vec<u8>>,
            exponent: Option<Vec<u8>>,
            opaque_parameters: Option<ParameterSetType>,
        }

        fn get_key_info(key: &Key) -> Result<KeyInfo> {
            let typs = &[
                AttributeType::Id,
                AttributeType::Class,
                AttributeType::KeyType,
                // Generic key value:
                AttributeType::Value,
                // ECC:
                AttributeType::EcParams,
                AttributeType::EcPoint,
                // RSA:
                AttributeType::PublicExponent,
                AttributeType::Modulus,
                // ML-DSA:
                AttributeType::ParameterSet,
            ][..];

            let (session, object) = key.open_ro_session()?;
            let attributes = session.get_attributes(object.clone(), &typs)?;

            let mut key_id = None;
            let mut object_class = None;
            let mut key_type = None;
            let mut key_value = None;
            let mut ec_params = None;
            let mut ec_point = None;
            let mut modulus = None;
            let mut exponent = None;
            let mut opaque_parameters = None;

            for attr in attributes.into_iter() {
                match attr {
                    Attribute::Id(value) => key_id = Some(value),
                    Attribute::Class(value) => object_class = Some(value),
                    Attribute::KeyType(value) => key_type = Some(value),
                    Attribute::Value(value) => key_value = Some(value),
                    Attribute::EcParams(value) => ec_params = Some(value),
                    Attribute::EcPoint(value) => ec_point = Some(value),
                    Attribute::PublicExponent(value) => exponent = Some(value),
                    Attribute::Modulus(value) => modulus = Some(value),
                    Attribute::ParameterSet(v) => opaque_parameters = Some(v),
                    _ => {
                        panic!("PKCS#11 driver returned unrequested attribute: {:?}",
                               attr);
                    }
                }
            }

            match object_class {
                None => {
                    return Err(anyhow::anyhow!(
                        "Token appears to be broken: key is missing object class \
                         attribute"));
                }
                Some(ObjectClass::PUBLIC_KEY) => (),
                Some(cls) => {
                    return Err(anyhow::anyhow!(
                        "Unsupported object class: {:?}", cls));
                }
            }

            Ok(KeyInfo {
                key_id,
                object_class,
                key_type,
                key_value,
                ec_params,
                ec_point,
                exponent,
                modulus,
                opaque_parameters,
            })
        }

        let key_info = get_key_info(self)?;
        let composite_info = if let Some(key) = self.composite_key() {
            Some(get_key_info(key)?)
        } else {
            None
        };

        macro_rules! value {
            ($key:expr, $name:expr, $attr:expr) => {{
                let attr: &Option<_> = &$attr;
                if let Some(value) = attr {
                    Ok(value)
                } else {
                    Err(anyhow::anyhow!(
                        "Can't export the {} key: the {} attribute is missing",
                        $key, $name))
                }
            }};
        }

        // Extract the parameters from the key ID.
	let key_id = if let Some(key_id) = key_info.key_id.as_ref() {
	    key_id
	} else {
	    return Err(anyhow::anyhow!("Key is missing a key ID"));
	};

	let key_id = match String::from_utf8(key_id.to_vec()) {
	    Ok(key_id) => key_id,
	    Err(err) => {
		return Err(err).context(format!(
		    "Key ID {:?} does not identify an OpenPGP key",
		    String::from_utf8_lossy(key_id)));
	    }
	};

	t!("key id: {}", key_id);

	let invalid_key_id = |err: Option<anyhow::Error>| -> Result<_> {
	    if let Some(err) = err {
		Err(err).context(format!(
		    "Key ID {:?} does not identify an OpenPGP key",
		    key_id))
	    } else {
		Err(anyhow::anyhow!(
		    "Key ID {:?} does not identify an OpenPGP key",
		    key_id))
	    }
	};

        let id = if let Some(id) = key_id.strip_prefix("pgp:v") {
            id
        } else {
            t!("Invalid key ID: Missing pgp:v prefix");
            return invalid_key_id(Some(anyhow::anyhow!("Missing pgp:v prefix")));
        };
        let mut parts = id.split(':');

        // Recover the profile.
        let Some(version) = parts.next() else {
	    return invalid_key_id(Some(anyhow::anyhow!("Missing version")));
	};
	t!("version component: {}", version);
        let (profile, bundling) = match version {
            "6" => (openpgp::Profile::RFC9580,
                    Bundling::Standalone),
            "6t" => (openpgp::Profile::RFC9580,
                     Bundling::TraditionalHalf),
            "6pq" => (openpgp::Profile::RFC9580,
                      Bundling::PostQuantumHalf),

            "4" => (openpgp::Profile::RFC4880,
                    Bundling::Standalone),
            "4t" => (openpgp::Profile::RFC4880,
                     Bundling::TraditionalHalf),
            "4pq" => (openpgp::Profile::RFC4880,
                      Bundling::PostQuantumHalf),

            v => {
                t!("Invalid key ID: Unsupported version ({})", v);
		return invalid_key_id(Some(
		    anyhow::anyhow!("Unsupported version ({})", v)));
            }
        };

	// Recover the algorithm.
	let Some(algo_parts) = parts.next() else {
	    return invalid_key_id(Some(anyhow::anyhow!("Missing algorithm")));
	};
	t!("algo component: {}", algo_parts);
	let mut algo_parts = algo_parts.split("-");
	let Some(algo) = algo_parts.next() else {
	    return invalid_key_id(Some(anyhow::anyhow!("Empty algorithm")));
	};
	let algo = if let Ok(algo) = algo.parse::<crate::PublicKeyAlgorithm>() {
	    algo
	} else {
            t!("Invalid algorithm ({})", algo);
	    return invalid_key_id(Some(
		anyhow::anyhow!("Invalid algorithm ({})", algo)));
	};

        // Extract the hash algorithm and the symmetric algorithm.
        // This is only used needed for v4 ECDH keys.
	let mut hash_algo = None;
	let mut sym_algo = None;
        if profile == openpgp::Profile::RFC4880 {
	    if let Some(part) = algo_parts.next() {
                hash_algo = match part {
		    "sha256" => Some(HashAlgorithm::SHA256),
		    "sha384" => Some(HashAlgorithm::SHA384),
		    "sha512" => Some(HashAlgorithm::SHA512),
		    _ => None,
                };
	    }

	    if let Some(part) = algo_parts.next() {
                sym_algo = match part {
		    "aes128" => Some(SymmetricAlgorithm::AES128),
		    "aes192" => Some(SymmetricAlgorithm::AES192),
		    "aes256" => Some(SymmetricAlgorithm::AES256),
		    _ => None,
                };
	    }
        } else if let Some(part) = algo_parts.next() {
	    return invalid_key_id(Some(
		anyhow::anyhow!("Unknown algorithm parameter ({})", part)));
	}

        // Recover the creation time, which we encode using
        // ISO 8601-1:2019's basic format.
        let Some(time) = parts.next() else {
	    return invalid_key_id(Some(anyhow::anyhow!("Missing creation time")));
	};
	t!("creation time component: {}", time);
        let l = "19700101T000000Z".len();
        if time.len() < l {
            t!("Invalid key ID: Invalid creation time: string is too short");
	    return invalid_key_id(Some(anyhow::anyhow!(
		"Invalid creation time: string is too short")));
        }
        let creation_time: DateTime<Utc> = match DateTime::parse_from_str(
	    &time[..l], "%Y%m%dT%H%M%S%#z")
	{
	    Ok(time) => time.into(),
	    Err(err) => {
                t!("Invalid key ID: Invalid time: {}", err);
                return invalid_key_id(Some(err.into()));
            }
	};
	let creation_time: std::time::SystemTime = creation_time.into();

        t!("Key ID => profile: {:?}, bundling: {:?}, algo: {:?}, creation time: {}, \
            hash: {:?}, sym: {:?}",
           profile, bundling, algo,
           DateTime::<Utc>::from(creation_time.clone()).format("%Y%m%dT%H%M%SZ"),
           hash_algo, sym_algo);

        let ec_edwards_to_key = |key_info: &KeyInfo|
            -> Result<key::Key<key::PublicParts, key::UnspecifiedRole>>
        {
            // PKCS#11, v3.2, Section 6.3.5. Edwards Elliptic
            // Curve public key objects
            //
            // DER encoding of the parameters.
            let ec_params = value!("ECC", "EC params", key_info.ec_params)?;

            // PKCS#11, v3.2, Section 6.3.5. Edwards Elliptic
            // Curve public key objects:
            //
            //   Public key bytes in little endian order as defined
            //   in [RFC 8032].
            //
            // Unfortunately, reality is a bit more complicated: some
            // HSMs DER encode the result.

            let key = match (algo.clone(), profile, &ec_params[..]) {
                (crate::PublicKeyAlgorithm::Ed25519
		 | crate::PublicKeyAlgorithm::MLDSA65_Ed25519,
		 Profile::RFC9580,
		 crate::oids::Ed25519) => {
                    let ec_point = decode_ec_point(
                        value!("ECC", "EC point", key_info.ec_point)?,
                        32)?;

                    Key6::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_ed25519(&ec_point, creation_time)?
                        .into()
                },

                (crate::PublicKeyAlgorithm::Ed448
		 | crate::PublicKeyAlgorithm::MLDSA87_Ed448,
		 Profile::RFC9580,
		 crate::oids::Ed448) => {
                    let ec_point = decode_ec_point(
                        value!("ECC", "EC point", key_info.ec_point)?,
                        57)?;

                    Key6::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_ed448(&ec_point, creation_time)?
                        .into()
                }

                (crate::PublicKeyAlgorithm::EdDSA,
		 Profile::RFC4880,
		 crate::oids::Ed25519) => {
                    let ec_point = decode_ec_point(
                        value!("ECC", "EC point", key_info.ec_point)?,
                        32)?;

		    // import_public_ed25519 imports a legacy Ed25519
		    // key.
                    Key4::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_ed25519(&ec_point, creation_time)?
                        .into()
		}

                (crate::PublicKeyAlgorithm::Ed25519,
		 Profile::RFC4880,
		 crate::oids::Ed25519) => {
                    let ec_point = decode_ec_point(
                        value!("ECC", "EC point", key_info.ec_point)?,
                        32)?;

		    // Despite its name, `Key4::import_public_ed25519`
		    // imports a legacy Ed25519 key, i.e., an EdDSA
		    // key, not an Ed25519 key.  There is no function
		    // to import a v4 Ed25519, but we can hack it
		    // together.
		    Key4::new(creation_time,
			      PublicKeyAlgorithm::Ed25519,
			      mpi::PublicKey::Ed25519 {
				  a: ec_point.try_into()?,
			      })?.into()
                }

                (crate::PublicKeyAlgorithm::Ed448,
		 Profile::RFC4880,
		 crate::oids::Ed448) => {
                    let ec_point = decode_ec_point(
                        value!("ECC", "EC point", key_info.ec_point)?,
                        57)?;

                    Key4::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_ed448(&ec_point, creation_time)?
                        .into()
                }

                _ => {
                    return Err(anyhow::anyhow!(
                        "unsupported key (algo: {}, profile: {:?}, curve: {})",
                        algo, profile, oid_to_str(&ec_params[..])));
                }
            };

            Ok(key)
        };

        let ec_montgomery_to_key = |key_info: &KeyInfo|
            -> Result<key::Key<key::PublicParts, key::UnspecifiedRole>>
        {
            // PKCS#11, v3.2, Section 6.3.7. Montgomery Elliptic
            // Curve public key objects
            //
            // DER encoding of the parameters.
            let ec_params = value!("ECC", "EC params", key_info.ec_params)?;

            // PKCS#11, v3.2, Section 6.3.7. Montgomery Elliptic
            // Curve public key objects
            //
            // Public key bytes in little endian order as defined
            // in [RFC 7748].
            let ec_point = value!("ECC", "EC point", key_info.ec_point)?;

            // RFC 9580, Section 5.5.5.6. Algorithm-Specific Part
            // for ECDH Keys
            //
            // An MPI of an EC point representing a public key, in
            // the point format associated with the curve, as
            // specified in Section 9.2.1.
            //
            // https://www.rfc-editor.org/rfc/rfc9580.html#name-algorithm-specific-part-for-ecd

            let key = match (algo.clone(), profile, &ec_params[..]) {
                (crate::PublicKeyAlgorithm::X25519
		 | crate::PublicKeyAlgorithm::MLKEM768_X25519,
		 Profile::RFC9580,
		 crate::oids::X25519) =>
                    Key6::<key::PublicParts, key::UnspecifiedRole>
                    ::import_public_x25519(&ec_point, creation_time)?
                    .into(),

                (crate::PublicKeyAlgorithm::X448
		 | crate::PublicKeyAlgorithm::MLKEM1024_X448,
		 Profile::RFC9580,
		 crate::oids::X448) =>
                    Key6::<key::PublicParts, key::UnspecifiedRole>
                    ::import_public_x448(&ec_point, creation_time)?
                    .into(),

                (crate::PublicKeyAlgorithm::Cv25519,
		 Profile::RFC4880,
		 crate::oids::X25519) => {
                    // These parameters are part of the OpenPGP
                    // public key.  If not `None`, then we were
                    // able to derive them from the token.
                    // Otherwise, we use reasonable defaults, as
                    // per RFC 9580, section 11.5.1. ECDH
                    // Parameters.
                    //
                    // https://www.rfc-editor.org/rfc/rfc9580.html#ecdh
                    let hash_algo
                        = hash_algo.unwrap_or(HashAlgorithm::SHA256);
                    let sym_algo
                        = sym_algo.unwrap_or(SymmetricAlgorithm::AES128);

                    Key4::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_cv25519(&ec_point, hash_algo,
                                                sym_algo, creation_time)?
                        .into()
		}

                (crate::PublicKeyAlgorithm::X25519
		 | crate::PublicKeyAlgorithm::MLKEM768_X25519,
		 Profile::RFC4880,
		 crate::oids::X25519) => {
                    Key4::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_x25519(&ec_point, creation_time)?
                        .into()
                },

                (crate::PublicKeyAlgorithm::X448
		 | crate::PublicKeyAlgorithm::MLKEM1024_X448,
		 Profile::RFC4880,
		 crate::oids::X448) =>
                    Key4::<key::PublicParts, key::UnspecifiedRole>
                    ::import_public_x448(&ec_point, creation_time)?
                    .into(),

                _=> {
                    return Err(anyhow::anyhow!(
                        "unsupported key (algo: {}, profile: {:?}, curve: {})",
                        algo, profile, oid_to_str(&ec_params[..])));
                }
            };

            Ok(key)
        };

        let key = match (key_info.key_type, composite_info.as_ref().map(|ki| ki.key_type)) {
            (None, _) => {
                return Err(anyhow::anyhow!(
                    "Token appears to be broken: key is missing key type \
                     attribute"));
            }
            (Some(_), Some(None)) => {
                return Err(anyhow::anyhow!(
                    "Token appears to be broken: key is missing key type \
                     attribute"));
            }

            (Some(KeyType::EC_EDWARDS), None) => {
                ec_edwards_to_key(&key_info)?
            }

            (Some(KeyType::EC_MONTGOMERY), None) => {
                ec_montgomery_to_key(&key_info)?
            },

            (Some(KeyType::RSA), None) => {
                let modulus = value!("RSA", "modulus", key_info.modulus)?;
                let exponent = value!("RSA", "exponent", key_info.exponent)?;

                match profile {
                    Profile::RFC9580 =>
                        Key6::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_rsa(&exponent, &modulus, creation_time)?
                            .into(),

                    Profile::RFC4880 =>
                        Key4::<key::PublicParts, key::UnspecifiedRole>
                        ::import_public_rsa(&exponent, &modulus, creation_time)?
                            .into(),

                    _ => return Err(anyhow::anyhow!("unsupported profile")),
                }
            },

            (Some(KeyType::ML_DSA), Some(Some(KeyType::EC_EDWARDS)))
                | (Some(KeyType::EC_EDWARDS), Some(Some(KeyType::ML_DSA))) =>
            {
                let (mldsa_info, eddsa_info)
                    = if key_info.key_type == Some(KeyType::ML_DSA)
                {
                    (key_info, composite_info.unwrap())
                } else {
                    (composite_info.unwrap(), key_info)
                };

                let eddsa = ec_edwards_to_key(&eddsa_info)?;

                use cryptoki::object::MlDsaParameterSetType;
                let mldsa = mldsa_info.key_value
                    .ok_or(anyhow::anyhow!("key value not returned by token"))?;
                let ml_dsa_parameters: Option<MlDsaParameterSetType>
                    = mldsa_info.opaque_parameters.map(|p| p.into());

                match ml_dsa_parameters {
                    Some(MlDsaParameterSetType::ML_DSA_65) => {
                        let eddsa = match eddsa.mpis() {
                            mpi::PublicKey::Ed25519 { a } =>
                                Box::new(a.clone()),
                            _ => return Err(anyhow::anyhow!(
                                "expected an Ed25519 key in \
                                 composite construction")),
                        };

                        let mldsa = mldsa.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "ML-DSA key has wrong size"))?;

                        Key6::new(creation_time,
                                  PublicKeyAlgorithm::MLDSA65_Ed25519,
                                  mpi::PublicKey::MLDSA65_Ed25519 {
                                      eddsa,
                                      mldsa,
                                  })?.into()
                    },

                    Some(MlDsaParameterSetType::ML_DSA_87) => {
                        let eddsa = match eddsa.mpis() {
                            mpi::PublicKey::Ed448 { a } =>
                                a.clone(),
                            _ => return Err(anyhow::anyhow!(
                                "expected an Ed448 key in \
                                 composite construction")),
                        };

                        let mldsa = mldsa.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "ML-DSA key has wrong size"))?;

                        Key6::new(creation_time,
                                  PublicKeyAlgorithm::MLDSA87_Ed448,
                                  mpi::PublicKey::MLDSA87_Ed448 {
                                      eddsa,
                                      mldsa,
                                  })?.into()
                    },

                    Some(p) => return Err(anyhow::anyhow!(
                        "ML-DSA parameters {:?} not supported by OpenPGP", p)),

                    None => return Err(anyhow::anyhow!(
                        "ML-DSA parameters not returned by token")),
                }
            },

            (Some(KeyType::SLH_DSA), None) => {
                use cryptoki::object::SlhDsaParameterSetType;
                let slh_dsa = key_info.key_value
                    .ok_or(anyhow::anyhow!("key value not returned by token"))?;
                let slh_dsa_parameters: Option<SlhDsaParameterSetType>
                    = key_info.opaque_parameters.map(|p| p.into());

                match slh_dsa_parameters {
                    Some(SlhDsaParameterSetType::SHAKE_128S) => {
                        let slh_dsa = slh_dsa.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "SLH-DSA key has wrong size"))?;

                        Key6::new(creation_time,
                                  PublicKeyAlgorithm::SLHDSA128s,
                                  mpi::PublicKey::SLHDSA128s {
                                      public: slh_dsa,
                                  })?.into()
                    },

                    Some(SlhDsaParameterSetType::SHAKE_128F) => {
                        let slh_dsa = slh_dsa.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "SLH-DSA key has wrong size"))?;

                        Key6::new(creation_time,
                                  PublicKeyAlgorithm::SLHDSA128f,
                                  mpi::PublicKey::SLHDSA128f {
                                      public: slh_dsa,
                                  })?.into()
                    },

                    Some(SlhDsaParameterSetType::SHAKE_256S) => {
                        let slh_dsa = slh_dsa.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "SLH-DSA key has wrong size"))?;

                        Key6::new(creation_time,
                                  PublicKeyAlgorithm::SLHDSA256s,
                                  mpi::PublicKey::SLHDSA256s {
                                      public: slh_dsa,
                                  })?.into()
                    },

                    Some(p) => return Err(anyhow::anyhow!(
                        "SLH-DSA parameters {} not supported by OpenPGP", p)),

                    None => return Err(anyhow::anyhow!(
                        "SLH-DSA parameters not returned by token")),
                }
            },

            (Some(KeyType::ML_KEM), Some(Some(KeyType::EC_MONTGOMERY)))
                | (Some(KeyType::EC_MONTGOMERY), Some(Some(KeyType::ML_KEM))) =>
            {
                let (mlkem_info, ecdh_info)
                    = if key_info.key_type == Some(KeyType::ML_KEM)
                {
                    (key_info, composite_info.unwrap())
                } else {
                    (composite_info.unwrap(), key_info)
                };

                let ecdh = ec_montgomery_to_key(&ecdh_info)?;

                use cryptoki::object::MlKemParameterSetType;
                let mlkem = mlkem_info.key_value
                    .ok_or(anyhow::anyhow!("key value not returned by token"))?;
                let mlkem_parameters: Option<MlKemParameterSetType>
                    = mlkem_info.opaque_parameters.map(|p| p.into());

                match mlkem_parameters {
                    Some(MlKemParameterSetType::ML_KEM_768) => {
                        let ecdh = match ecdh.mpis() {
                            mpi::PublicKey::X25519 { u } =>
                                Box::new(u.clone()),
                            _ => return Err(anyhow::anyhow!(
                                "expected an X25519 key in \
                                 composite construction, got {:?}", ecdh)),
                        };

                        let mlkem = mlkem.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "ML-KEM key has wrong size"))?;

                        match profile {
                            Profile::RFC9580 => {
                                Key6::new(creation_time,
                                          PublicKeyAlgorithm::MLKEM768_X25519,
                                          mpi::PublicKey::MLKEM768_X25519 {
                                              ecdh,
                                              mlkem,
                                          })?.into()
                            }
                            Profile::RFC4880 => {
                                Key4::new(creation_time,
                                          PublicKeyAlgorithm::MLKEM768_X25519,
                                          mpi::PublicKey::MLKEM768_X25519 {
                                              ecdh,
                                              mlkem,
                                          })?.into()
                            }
                            _ => return Err(anyhow::anyhow!(
                                "unsupported profile {:?}", profile)),
                        }
                    },

                    Some(MlKemParameterSetType::ML_KEM_1024) => {
                        let ecdh = match ecdh.mpis() {
                            mpi::PublicKey::X448 { u } =>
                                u.clone(),
                            _ => return Err(anyhow::anyhow!(
                                "expected an X448 key in \
                                 composite construction")),
                        };

                        let mlkem = mlkem.try_into()
                            .map_err(|_| anyhow::anyhow!(
                                "ML-KEM key has wrong size"))?;

                        match profile {
                            Profile::RFC9580 => {
                                Key6::new(creation_time,
                                          PublicKeyAlgorithm::MLKEM1024_X448,
                                          mpi::PublicKey::MLKEM1024_X448 {
                                              ecdh,
                                              mlkem,
                                          })?.into()
                            }
                            Profile::RFC4880 => {
                                Key4::new(creation_time,
                                          PublicKeyAlgorithm::MLKEM1024_X448,
                                          mpi::PublicKey::MLKEM1024_X448 {
                                              ecdh,
                                              mlkem,
                                          })?.into()
                            }
                            _ => return Err(anyhow::anyhow!(
                                "unsupported profile {:?}", profile)),
                        }
                    },

                    Some(p) => return Err(anyhow::anyhow!(
                        "ML-KEM parameters {:?} not supported by OpenPGP", p)),

                    None => return Err(anyhow::anyhow!(
                        "ML-KEM parameters not returned by token")),
                }
            },

            (Some(kt), Some(Some(other))) => {
                return Err(anyhow::anyhow!(
                    "Can't export key: unsupported composite key: {}+{}",
                    kt, other));
            }

            (Some(kt), None) => {
                return Err(anyhow::anyhow!(
                    "Can't export key: unsupported key type: {}", kt));
            }
        };

        *openpgp_key = Some(key.clone());

        Ok(key)
    }

    /// Return armor headers.
    pub fn armor_headers(&self) -> Vec<(&'static str, String)> {
        let mut headers = Vec::new();

        // Unique ID.
        if let Some(unique_id) = self.key_unique_id() {
            if unique_id.iter().all(u8::is_ascii_alphanumeric) {
                // If the unique ID is just ASCII, use as is.
                headers.push((
                    "UniqueID",
                    String::from_utf8_lossy(unique_id).to_string()));
            } else {
                // Otherwise, use the hex encoded version.

                // If Key::key_unique_id returns something, so will
                // Key::key_unique_id_hex.
                headers.push((
                    "Cryptoki-UniqueID",
                    self.key_unique_id_hex().unwrap()));
            }
        }

        // ID.
        if let Some(id) = self.key_id() {
            headers.push((
                "Cryptoki-ID",
                String::from_utf8_lossy(id).to_string()));
        }

        // Label.
        if let Some(label) = self.key_label() {
            headers.push((
                "Cryptoki-Label",
                String::from_utf8_lossy(label).to_string()));
        }

        if let Some(composite_key) = self.composite_key() {
            // Constituent Key ID.
            if let Some(id) = composite_key.key_id() {
                headers.push((
                    "Cryptoki-Constituent-ID",
                    String::from_utf8_lossy(id).to_string()));
            }
        }


        headers
    }
}
