/*
 * Copyright 2025 Yubico AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::convert::TryFrom;
use std::fmt;
use std::fmt::Display;
use std::string::ToString;
use strum_macros::EnumIter;
use pem::Pem;
use openssl::pkey;
use openssl::bn::{BigNum, BigNumContext};
use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;

use yubihsmrs::object::{ObjectAlgorithm, ObjectCapability, ObjectDescriptor, ObjectDomain, ObjectType};
use yubihsmrs::Session;
use crate::traits::operation_traits::{YubihsmOperations};
use crate::traits::command_traits::Command;
use crate::common::error::MgmError;
use crate::common::algorithms::MgmAlgorithm;
use crate::common::util::{get_op_keys, get_object_descriptors};
use crate::common::types::{EXIT_LABEL, NewObjectSpec};

#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
pub enum AsymCommand {
    List,
    GetKeyProperties,
    Generate,
    Import,
    Delete,
    GetPublicKey,
    GetX509Certificate,
    Sign,
    Decrypt,
    DeriveEcdh,
    SignAttestationCert,
    Exit,
}

impl Command for AsymCommand {
    fn label(&self) -> &'static str {
        match self {
            Self::List => "List",
            Self::GetKeyProperties => "Get Object Properties",
            Self::Generate => "Generate",
            Self::Import => "Import",
            Self::Delete => "Delete",
            Self::GetPublicKey => "Get Public Key",
            Self::GetX509Certificate => "Get X509 Certificate",
            Self::Sign => "Sign",
            Self::Decrypt => "Decrypt",
            Self::DeriveEcdh => "Derive ECDH",
            Self::SignAttestationCert => "Sign Attestation Certificate",
            Self::Exit => EXIT_LABEL,
        }
    }

    fn description(&self) -> &'static str {
        match self {
            Self::List => "List all asymmetric keys and X509 certificates stored on the YubiHSM",
            Self::GetKeyProperties => "Get properties of an asymmetric key or X509 certificate stored on the YubiHSM",
            Self::Generate => "Generate a new asymmetric key inside the YubiHSM",
            Self::Import => "Import an asymmetric key or X509 certificate into the YubiHSM",
            Self::Delete => "Delete asymmetric keys or X509 certificates from the YubiHSM",
            Self::GetPublicKey => "Retrieve the public key portion of an asymmetric key stored on the YubiHSM",
            Self::GetX509Certificate => "Retrieve an X509 certificate stored on the YubiHSM",
            Self::Sign => "Sign data using an asymmetric private key stored on the YubiHSM",
            Self::Decrypt => "Decrypt data using an asymmetric private key stored on the YubiHSM",
            Self::DeriveEcdh => "Derive an ECDH shared secret using an EC private key stored on the YubiHSM",
            Self::SignAttestationCert => "Generate and sign an attestation certificate for a key generated on the YubiHSM",
            Self::Exit => "",
        }
    }

    fn required_capabilities(&self) -> &'static [ObjectCapability] {
        match self {
            Self::List | Self::GetKeyProperties | Self::Exit => &[],
            Self::Generate => &[ObjectCapability::GenerateAsymmetricKey],
            Self::Import => &[ObjectCapability::PutAsymmetricKey, ObjectCapability::PutOpaque],
            Self::Delete => &[ObjectCapability::DeleteAsymmetricKey, ObjectCapability::DeleteOpaque],
            Self::GetPublicKey => &[],
            Self::GetX509Certificate => &[],
            Self::Sign => &[ObjectCapability::SignPkcs,
                ObjectCapability::SignPss,
                ObjectCapability::SignEcdsa,
                ObjectCapability::SignEddsa],
            Self::Decrypt => &[ObjectCapability::DecryptPkcs,
                ObjectCapability::DecryptOaep],
            Self::DeriveEcdh => &[ObjectCapability::DeriveEcdh],
            Self::SignAttestationCert => &[ObjectCapability::SignAttestationCertificate],
        }
    }
}


#[derive(Debug, Clone, Copy, PartialEq,  Eq, Default)]
pub enum AttestationType {
    #[default]
    DeviceSigned,
    SelfSigned,
    AsymSigned,
}

impl Display for AttestationType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AttestationType::DeviceSigned => write!(f, "Signed by device attestation key"),
            AttestationType::SelfSigned => write!(f, "Self signed"),
            AttestationType::AsymSigned => write!(f, "Signed by another asymmetric key"),
        }
    }
}

pub struct AsymmetricOperations;

impl YubihsmOperations for AsymmetricOperations {

    fn context(&self) -> &'static str {
        AsymmetricOperations::ASYM_CONTEXT
    }

    fn get_all_objects(&self, session: &Session) -> Result<Vec<ObjectDescriptor>, MgmError> {
        Self::get_asymmetric_objects(session, &[ObjectType::AsymmetricKey, ObjectType::Opaque])
    }

    fn get_generation_algorithms(&self) -> Vec<MgmAlgorithm> {
        let mut algos = MgmAlgorithm::RSA_KEY_ALGORITHMS.to_vec();
        algos.extend(MgmAlgorithm::EC_KEY_ALGORITHMS.to_vec());
        algos.extend(MgmAlgorithm::ED_KEY_ALGORITHMS.to_vec());
        algos
    }

    fn get_object_capabilities(&self, _: Option<ObjectType>, object_algorithm: Option<ObjectAlgorithm>) -> Result<Vec<ObjectCapability>, MgmError> {
        let algo = if let Some(a) = object_algorithm {
            a
        } else {
            return Err(MgmError::InvalidInput("Missing asymmetric object algorithm".to_string()));
        };
        let caps = if Self::is_rsa_key_algorithm(&algo) {
            Self::RSA_KEY_CAPABILITIES.to_vec()
        } else if Self::is_ec_key_algorithm(&algo) {
            Self::EC_KEY_CAPABILITIES.to_vec()
        } else if algo == ObjectAlgorithm::Ed25519 {
            Self::ED_KEY_CAPABILITIES.to_vec()
        } else if algo == ObjectAlgorithm::OpaqueX509Certificate {
            Self::OPAQUE_CAPABILITIES.to_vec()
        } else {
            return Err(MgmError::InvalidInput(
                format!("Unsupported asymmetric object algorithm {:?}", object_algorithm)));
        };
        Ok(caps)
    }

    fn generate(&self, session: &Session, spec: &NewObjectSpec) -> Result<u16, MgmError> {
        let key = session
            .generate_asymmetric_key_with_keyid(
                spec.id, &spec.label, &spec.capabilities, &spec.domains, spec.algorithm)?;
        Ok(key.get_key_id())
    }

    fn import(&self, session: &Session, spec: &NewObjectSpec) -> Result<u16, MgmError> {
        let key_data = &spec.data[0];
        let id =
            if Self::is_rsa_key_algorithm(&spec.algorithm) {
                session.import_rsa_key(
                    spec.id,
                    &spec.label,
                    &spec.domains,
                    &spec.capabilities,
                    spec.algorithm,
                    &key_data[0..key_data.len() / 2],
                    &key_data[key_data.len() / 2..])?
            } else if Self::is_ec_key_algorithm(&spec.algorithm) {
                session.import_ec_key(
                    spec.id,
                    &spec.label,
                    &spec.domains,
                    &spec.capabilities,
                    spec.algorithm,
                    key_data)?
            } else if spec.algorithm == ObjectAlgorithm::Ed25519 {
                session.import_ed_key(
                    spec.id,
                    &spec.label,
                    &spec.domains,
                    &spec.capabilities,
                    key_data)?
            } else if spec.algorithm == ObjectAlgorithm::OpaqueX509Certificate {
                session.import_cert(
                    spec.id,
                    &spec.label,
                    &spec.domains,
                    &spec.capabilities,
                    key_data)?
            } else {
                return Err(MgmError::InvalidInput(
                    format!("Unsupported asymmetric key algorithm {:?}", spec.algorithm)));
            };
        Ok(id)
    }
}

impl AsymmetricOperations {

    pub const ASYM_CONTEXT: &'static str = "asym";

    const RSA_KEY_CAPABILITIES: [ObjectCapability; 6] = [
        ObjectCapability::SignPkcs,
        ObjectCapability::SignPss,
        ObjectCapability::DecryptPkcs,
        ObjectCapability::DecryptOaep,
        ObjectCapability::ExportableUnderWrap,
        ObjectCapability::SignAttestationCertificate];

    const EC_KEY_CAPABILITIES: [ObjectCapability; 4] = [
        ObjectCapability::SignEcdsa,
        ObjectCapability::DeriveEcdh,
        ObjectCapability::ExportableUnderWrap,
        ObjectCapability::SignAttestationCertificate];

    const ED_KEY_CAPABILITIES: [ObjectCapability; 2] = [
        ObjectCapability::SignEddsa,
        ObjectCapability::ExportableUnderWrap];

    const OPAQUE_CAPABILITIES: [ObjectCapability; 1] = [
        ObjectCapability::ExportableUnderWrap];


//----------------------------------------------------------
//       Main symmetric functions requiring a session
// ----------------------------------------------------------
    pub fn get_asymmetric_objects(session: &Session, types: &[ObjectType]) -> Result<Vec<ObjectDescriptor>, MgmError> {
        let mut objects = Vec::new();
        if types.contains(&ObjectType::Opaque) {
            objects.extend(session.list_objects_with_filter(0, ObjectType::Opaque, "", ObjectAlgorithm::OpaqueX509Certificate, &Vec::new())?);
        }
        if types.contains(&ObjectType::AsymmetricKey) {
            objects.extend(session.list_objects_with_filter(0, ObjectType::AsymmetricKey, "", ObjectAlgorithm::ANY, &Vec::new())?);
        }
        get_object_descriptors(session, &objects)
    }

    pub fn get_signing_keys(session: &Session, authkey: &ObjectDescriptor) -> Result<Vec<ObjectDescriptor>, MgmError> {
        let caps = [
            ObjectCapability::SignPkcs,
            ObjectCapability::SignPss,
            ObjectCapability::SignEcdsa,
            ObjectCapability::SignEddsa
        ];
        let mut keys =
            get_op_keys(session, authkey, &caps, ObjectType::AsymmetricKey, None)?;

        keys.retain(|k| {
            (Self::is_rsa_key_algorithm(k.algorithm()) && (k.capabilities().contains(&ObjectCapability::SignPkcs) || k.capabilities().contains(&ObjectCapability::SignPss))) ||
            (Self::is_ec_key_algorithm(k.algorithm()) && k.capabilities().contains(&ObjectCapability::SignEcdsa)) ||
            (k.algorithm() == &ObjectAlgorithm::Ed25519 && k.capabilities().contains(&ObjectCapability::SignEddsa))
        });
        Ok(keys)
    }

    pub fn get_decryption_keys(session: &Session, authkey: &ObjectDescriptor)  -> Result<Vec<ObjectDescriptor>, MgmError> {
        let caps = [
            ObjectCapability::DecryptPkcs,
            ObjectCapability::DecryptOaep,
        ];
        get_op_keys(session, authkey, &caps, ObjectType::AsymmetricKey, Some(&MgmAlgorithm::extract_algorithms(&MgmAlgorithm::RSA_KEY_ALGORITHMS)))
    }

    pub fn get_derivation_keys(session: &Session, authkey: &ObjectDescriptor)  -> Result<Vec<ObjectDescriptor>, MgmError> {
        get_op_keys(session, authkey, &[ObjectCapability::DeriveEcdh], ObjectType::AsymmetricKey, Some(&MgmAlgorithm::extract_algorithms(&MgmAlgorithm::EC_KEY_ALGORITHMS)))
    }

    pub fn get_signing_algorithms(authkey: &ObjectDescriptor, signkey: &ObjectDescriptor) -> Vec<MgmAlgorithm> {
        let mut algos = Vec::new();
        if Self::is_rsa_key_algorithm(signkey.algorithm()) {
            if signkey.capabilities().contains(&ObjectCapability::SignPkcs) &&
                authkey.capabilities().contains(&ObjectCapability::SignPkcs) {
                algos.extend_from_slice(&MgmAlgorithm::RSA_PKCS_ALGORITHMS);
            }
            if signkey.capabilities().contains(&ObjectCapability::SignPss) &&
                authkey.capabilities().contains(&ObjectCapability::SignPss) {
                algos.extend_from_slice(&MgmAlgorithm::RSA_PSS_ALGORITHMS);
            }
        } else if Self::is_ec_key_algorithm(signkey.algorithm()) &&
            signkey.capabilities().contains(&ObjectCapability::SignEcdsa) &&
                authkey.capabilities().contains(&ObjectCapability::SignEcdsa) {
            algos.extend_from_slice(&MgmAlgorithm::ECDSA_ALGORITHMS);
        } else if signkey.algorithm() == &ObjectAlgorithm::Ed25519 &&
            signkey.capabilities().contains(&ObjectCapability::SignEddsa) &&
                authkey.capabilities().contains(&ObjectCapability::SignEddsa) {
                algos.extend_from_slice(&MgmAlgorithm::EDDSA_ALGORITHMS);
        }
        algos
    }

    pub fn get_decryption_algorithms(authkey: &ObjectDescriptor, deckey: &ObjectDescriptor) -> Vec<MgmAlgorithm> {
        let mut algos = Vec::new();
        if Self::is_rsa_key_algorithm(deckey.algorithm()) {
            if deckey.capabilities().contains(&ObjectCapability::DecryptPkcs) && authkey.capabilities().contains(&ObjectCapability::DecryptPkcs) {
                algos.push(ObjectAlgorithm::RsaPkcs1Decrypt.into());
            }
            if deckey.capabilities().contains(&ObjectCapability::DecryptOaep) && authkey.capabilities().contains(&ObjectCapability::DecryptOaep) {
                algos.extend_from_slice(&MgmAlgorithm::RSA_OAEP_ALGORITHMS);
            }
        }
        algos
    }

    pub fn get_pubkey(session: &Session, object_id: u16, object_typ: &ObjectType) -> Result<Pem, MgmError> {
        let (pubkey, algo) = session.get_pubkey(object_id, *object_typ)?;
        Self::get_pubkey_pem(algo, pubkey.as_slice())
    }

    pub fn get_certificate(session: &Session, key_id: u16) -> Result<Pem, MgmError> {
        let data = session.get_opaque(key_id)?;
        let cert: Vec<u8> = match openssl::x509::X509::from_der(data.as_slice()) {
            Ok(cert) => cert.to_pem()?,
            Err(_) => return Err(MgmError::InvalidInput(format!("Opaque object 0x{:04x} is not an X509Certificate", key_id)))
        };
        Ok(Pem::try_from(cert.as_slice())?)
    }

    pub fn sign(session: &Session, sign_key_id: u16, sign_algorithm: &ObjectAlgorithm, data: &[u8]) -> Result<Vec<u8>, MgmError> {
        let sig = match sign_algorithm {
            ObjectAlgorithm::RsaPkcs1Sha1 | ObjectAlgorithm::RsaPkcs1Sha256 | ObjectAlgorithm::RsaPkcs1Sha384 | ObjectAlgorithm::RsaPkcs1Sha512 => {
                let hashed_bytes = Self::get_hashed_bytes(sign_algorithm, data)?;
                session.sign_pkcs1v1_5(sign_key_id, true, hashed_bytes.as_slice())?
            },
            ObjectAlgorithm::RsaPssSha1 | ObjectAlgorithm::RsaPssSha256 | ObjectAlgorithm::RsaPssSha384 | ObjectAlgorithm::RsaPssSha512 => {
                let hashed_bytes = Self::get_hashed_bytes(sign_algorithm, data)?;
                let mgf1_algo = Self::get_mgf1_algorithm(sign_algorithm)?;
                session.sign_pss(sign_key_id, hashed_bytes.len(), mgf1_algo, hashed_bytes.as_slice())?
            },
            ObjectAlgorithm::EcdsaSha1 | ObjectAlgorithm::EcdsaSha256 | ObjectAlgorithm::EcdsaSha384 | ObjectAlgorithm::EcdsaSha512 => {
                let hashed_bytes = Self::get_hashed_bytes(sign_algorithm, data)?;
                session.sign_ecdsa(sign_key_id, hashed_bytes.as_slice())?
            },
            ObjectAlgorithm::Ed25519 => {
                session.sign_eddsa(sign_key_id, data)?
            },
            _ => {
                return Err(MgmError::Error("Unsupported signing algorithm".to_string()))
            }
        };
        Ok(sig)
    }

    pub fn decrypt(session: &Session, decryption_key_id: u16, decryption_algorithm: &ObjectAlgorithm, ciphertext: &[u8]) -> Result<Vec<u8>, MgmError> {
        let data = if decryption_algorithm == &ObjectAlgorithm::RsaPkcs1Decrypt {
            session.decrypt_pkcs1v1_5(decryption_key_id, ciphertext)?
        } else if Self::is_oaep_algorithm(decryption_algorithm) {
            let oaep_label: &[u8] = &[];
            let oaep_label = Self::get_hashed_bytes(decryption_algorithm, oaep_label)?;
            let mgf1_algo = Self::get_mgf1_algorithm(decryption_algorithm)?;
            session.decrypt_oaep(decryption_key_id, ciphertext, &oaep_label, mgf1_algo)?
        } else {
            return Err(MgmError::InvalidInput("Selected decryption algorithm is not supported".to_string()))
        };
        Ok(data)
    }

    pub fn derive_ecdh(session: &Session, hsm_key: &ObjectDescriptor, peer_pubkey: Pem) -> Result<Vec<u8>, MgmError> {
        let (peer_type, peer_algo, peer_key) = Self::parse_asym_pem(peer_pubkey)?;

        if peer_type != ObjectType::PublicKey || !Self::is_ec_key_algorithm(&peer_algo) {
            return Err(MgmError::InvalidInput("Peer public key is not an EC public key".to_string()))
        }

        if peer_algo != *hsm_key.algorithm() {
            return Err(MgmError::InvalidInput("Peer public key algorithm does not match HSM key algorithm".to_string()))
        }

        Ok(session.derive_ecdh(hsm_key.object_id(), peer_key.as_slice())?)
    }

    pub fn get_attestation_cert(
        session: &Session,
        attested_key: u16,
        attesting_key: u16,
        template_cert: Option<Pem>) -> Result<Pem, MgmError> {
        let cert_bytes = if attesting_key == 0 {
            session.sign_attestation_certificate(attested_key, 0)?
        } else {
            let imported_template = Self::import_attestation_template(session, attesting_key, attested_key, template_cert)?;
            let cert = session.sign_attestation_certificate(attested_key, attesting_key)?;
            if imported_template {
                session.delete_object(attesting_key, ObjectType::Opaque)?;
            }
            cert
        };


        let cert: Vec<u8> = openssl::x509::X509::from_der(cert_bytes.as_slice())?.to_pem()?;
        Ok(Pem::try_from(cert.as_slice())?)
    }


//----------------------------------------------------------
//       Public helper functions: Everything asymmetric
//----------------------------------------------------------


    pub fn is_rsa_key_algorithm(algorithm: &ObjectAlgorithm) -> bool {
        MgmAlgorithm::RSA_KEY_ALGORITHMS.iter().any(|a| a.algorithm() == *algorithm)
    }

    pub fn is_ec_key_algorithm(algorithm: &ObjectAlgorithm) -> bool {
        MgmAlgorithm::EC_KEY_ALGORITHMS.iter().any(|a| a.algorithm() == *algorithm)
    }

    pub fn is_oaep_algorithm(algorithm: &ObjectAlgorithm) -> bool {
        MgmAlgorithm::RSA_OAEP_ALGORITHMS.iter().any(|a| a.algorithm() == *algorithm)
    }

    pub fn get_hashed_bytes(algo: &ObjectAlgorithm, input: &[u8]) -> Result<Vec<u8>, MgmError> {
        match algo {
            ObjectAlgorithm::RsaPkcs1Sha1 | ObjectAlgorithm::RsaPssSha1 | ObjectAlgorithm::EcdsaSha1 | ObjectAlgorithm::RsaOaepSha1 => Ok(openssl::hash::hash(MessageDigest::sha1(), input)?.to_vec()),
            ObjectAlgorithm::RsaPkcs1Sha256 | ObjectAlgorithm::RsaPssSha256 | ObjectAlgorithm::EcdsaSha256 | ObjectAlgorithm::RsaOaepSha256 => Ok(openssl::hash::hash(MessageDigest::sha256(), input)?.to_vec()),
            ObjectAlgorithm::RsaPkcs1Sha384 | ObjectAlgorithm::RsaPssSha384 | ObjectAlgorithm::EcdsaSha384 | ObjectAlgorithm::RsaOaepSha384 => Ok(openssl::hash::hash(MessageDigest::sha384(), input)?.to_vec()),
            ObjectAlgorithm::RsaPkcs1Sha512 | ObjectAlgorithm::RsaPssSha512 | ObjectAlgorithm::EcdsaSha512 | ObjectAlgorithm::RsaOaepSha512 => Ok(openssl::hash::hash(MessageDigest::sha512(), input)?.to_vec()),
            _ => Err(MgmError::InvalidInput("Algorithm does not contain hash component".to_string()))
        }
    }

    pub fn get_mgf1_algorithm(algo: &ObjectAlgorithm) -> Result<ObjectAlgorithm, MgmError> {
        match algo {
            ObjectAlgorithm::RsaOaepSha1 | ObjectAlgorithm::RsaPssSha1 => Ok(ObjectAlgorithm::Mgf1Sha1),
            ObjectAlgorithm::RsaOaepSha256 | ObjectAlgorithm::RsaPssSha256 => Ok(ObjectAlgorithm::Mgf1Sha256),
            ObjectAlgorithm::RsaOaepSha384 | ObjectAlgorithm::RsaPssSha384 => Ok(ObjectAlgorithm::Mgf1Sha384),
            ObjectAlgorithm::RsaOaepSha512 | ObjectAlgorithm::RsaPssSha512 => Ok(ObjectAlgorithm::Mgf1Sha512),
            _ => Err(MgmError::InvalidInput("Algorithm is not an RSA PSS or OAEP decryption algorithm".to_string())),
        }
    }

    pub fn parse_asym_pem(pem: Pem) -> Result<(ObjectType, ObjectAlgorithm, Vec<u8>), MgmError> {
        let bytes = pem.contents();
        if bytes.is_empty() {
            return Err(MgmError::InvalidInput("DER data is empty".to_string()));
        }
        if let Ok(privkey) = openssl::pkey::PKey::private_key_from_der(bytes) {
            return match privkey.id() {
                pkey::Id::RSA => {
                    let private_rsa = privkey.rsa()?;
                    let key_algorithm = Self::get_rsa_key_algo(private_rsa.size())?;

                    let Some(p) = private_rsa.p() else {
                        return Err(MgmError::InvalidInput("Failed to read p value".to_string()));
                    };
                    let Some(q) = private_rsa.q() else {
                        return Err(MgmError::InvalidInput("Failed to read q value".to_string()));
                    };

                    let mut key_bytes = p.to_vec();
                    key_bytes.extend_from_slice(&q.to_vec());
                    Ok((ObjectType::AsymmetricKey, key_algorithm, key_bytes))
                }
                pkey::Id::EC => {
                    let private_ec = privkey.ec_key()?;
                    let s = private_ec.private_key();
                    let group = private_ec.group();
                    let Some(nid) = group.curve_name() else {
                        return Err(MgmError::InvalidInput("Failed to read EC curve name".to_string()))
                    };

                    let key_algorithm = Self::get_algo_from_nid(nid)?;
                    Ok((ObjectType::AsymmetricKey, key_algorithm, s.to_vec()))
                }
                pkey::Id::ED25519 => {
                    let private_ed = PKey::private_key_from_raw_bytes(bytes, pkey::Id::ED25519)?;
                    let k = private_ed.raw_private_key()?;
                    Ok((ObjectType::AsymmetricKey, ObjectAlgorithm::Ed25519, k.to_vec()))
                }
                _ => Err(MgmError::InvalidInput("Unknown or unsupported key type".to_string())),
            }
        }

        if let Ok(pubkey) = openssl::pkey::PKey::public_key_from_der(bytes) {
            return match pubkey.id() {
                pkey::Id::RSA => {
                    let public_rsa = pubkey.rsa()?;
                    let n = public_rsa.n();
                    // let e = public_rsa.e();

                    let key_bytes = n.to_vec();
                    // key_bytes.extend_from_slice(&e.to_vec());
                    let key_algorithm = Self::get_rsa_key_algo(public_rsa.size())?;
                    Ok((ObjectType::PublicKey, key_algorithm, key_bytes))
                }
                pkey::Id::EC => {
                    let public_ec = pubkey.ec_key()?;
                    let group = public_ec.group();
                    let point = public_ec.public_key();
                    let mut ctx = BigNumContext::new()?;
                    let pubkey_bytes = point.to_bytes(group, PointConversionForm::UNCOMPRESSED, &mut ctx)?;

                    let Some(nid) = group.curve_name() else {
                        return Err(MgmError::InvalidInput("Failed to read EC curve name".to_string()))
                    };

                    let key_algorithm = Self::get_algo_from_nid(nid)?;
                    Ok((ObjectType::PublicKey, key_algorithm, pubkey_bytes.to_vec()))
                }
                pkey::Id::ED25519 => {
                    let public_ed = PKey::public_key_from_raw_bytes(bytes, pkey::Id::ED25519)?;
                    let k = public_ed.raw_public_key()?;
                    Ok((ObjectType::PublicKey, ObjectAlgorithm::Ed25519, k.to_vec()))
                }
                _ => Err(MgmError::InvalidInput("Unknown or unsupported key type".to_string())),
            }
        }

        if openssl::x509::X509::from_der(bytes).is_ok() {
            return Ok((ObjectType::Opaque, ObjectAlgorithm::OpaqueX509Certificate, bytes.to_vec()))
        }

        Err(MgmError::InvalidInput("Failed to parse PEM data as either private key, public key or X509Certificate".to_string()))
    }

    pub fn get_pubkey_pem(algorithm: ObjectAlgorithm, pubkey_bytes: &[u8]) -> Result<Pem, MgmError> {
        let pem_bytes: Vec<u8> = if Self::is_rsa_key_algorithm(&algorithm) {
            let e = BigNum::from_slice(&[0x01, 0x00, 0x01])?;
            let n = BigNum::from_slice(pubkey_bytes)?;
            let rsa_pubkey = openssl::rsa::Rsa::from_public_components(n, e)?;
            rsa_pubkey.public_key_to_pem()?
        } else if Self::is_ec_key_algorithm(&algorithm) {
            let nid = match algorithm {
                ObjectAlgorithm::EcP256 => Nid::X9_62_PRIME256V1,
                ObjectAlgorithm::EcK256 => Nid::SECP256K1,
                ObjectAlgorithm::EcP384 => Nid::SECP384R1,
                ObjectAlgorithm::EcP521 => Nid::SECP521R1,
                ObjectAlgorithm::EcP224 => Nid::SECP224R1,
                ObjectAlgorithm::EcBp256 => Nid::BRAINPOOL_P256R1,
                ObjectAlgorithm::EcBp384 => Nid::BRAINPOOL_P384R1,
                ObjectAlgorithm::EcBp512 => Nid::BRAINPOOL_P512R1,
                _ => unreachable!()
            };
            let ec_group = EcGroup::from_curve_name(nid)?;
            let mut ctx = BigNumContext::new()?;

            let mut ec_pubkey_bytes: Vec<u8> = Vec::new();
            if pubkey_bytes[0] != 0x04 {
                ec_pubkey_bytes.push(0x04);
            }
            ec_pubkey_bytes.extend(pubkey_bytes);
            let ec_point = EcPoint::from_bytes(&ec_group, ec_pubkey_bytes.as_slice(), &mut ctx)?;
            let ec_pubkey = EcKey::from_public_key(&ec_group, &ec_point)?;
            ec_pubkey.public_key_to_pem()?
        } else if algorithm == ObjectAlgorithm::Ed25519 {
            let ed_pubkey = PKey::public_key_from_raw_bytes(pubkey_bytes, pkey::Id::ED25519)?;
            ed_pubkey.public_key_to_pem()?
        } else {
            return Err(MgmError::InvalidInput(
                format!("Unknown or unsupported asymmetric key algorithm {:?}", algorithm)));
        };
        let pem = Pem::try_from(pem_bytes.as_slice())?;
        Ok(pem)
    }
    //
    // pub fn parse_subject_dn_string(dn_string: &str) -> Result<X509Name, MgmError> {
    //     // Ex: "CN=Test,O=Example,C=US"
    //
    //     let mut builder = X509NameBuilder::new()?;
    //
    //     // Split by comma, but be careful with escaped commas
    //     for component in dn_string.split(',') {
    //         let component = component.trim();
    //
    //         // Split by '=' to get key and value
    //         if let Some((key, value)) = component.split_once('=') {
    //             let key = key.trim();
    //             let value = value.trim();
    //             builder.append_entry_by_text(key, value)?;
    //         } else {
    //             return Err(MgmError::InvalidInput(format!("Invalid DN component: {}", component)));
    //         }
    //     }
    //
    //     Ok(builder.build())
    // }


//----------------------------------------------------------
//       Internal helper functions
//----------------------------------------------------------


    fn get_rsa_key_algo(size_in_bytes: u32) -> Result<ObjectAlgorithm, MgmError> {
        match size_in_bytes {
            256 => Ok(ObjectAlgorithm::Rsa2048),
            384 => Ok(ObjectAlgorithm::Rsa3072),
            512 => Ok(ObjectAlgorithm::Rsa4096),
            _ => {
                Err(MgmError::Error(format!("Unsupported RSA key size {}", (size_in_bytes * 8))))
            }
        }
    }

    fn get_algo_from_nid(nid: Nid) -> Result<ObjectAlgorithm, MgmError> {
        match nid {
            Nid::X9_62_PRIME256V1 => Ok(ObjectAlgorithm::EcP256),
            Nid::SECP256K1 => Ok(ObjectAlgorithm::EcK256),
            Nid::SECP384R1 => Ok(ObjectAlgorithm::EcP384),
            Nid::SECP521R1 => Ok(ObjectAlgorithm::EcP521),
            Nid::SECP224R1 => Ok(ObjectAlgorithm::EcP224),
            Nid::BRAINPOOL_P256R1 => Ok(ObjectAlgorithm::EcBp256),
            Nid::BRAINPOOL_P384R1 => Ok(ObjectAlgorithm::EcBp384),
            Nid::BRAINPOOL_P512R1 => Ok(ObjectAlgorithm::EcBp512),
            _ => {
                Err(MgmError::InvalidInput(format!("Unsupported EC curve {:?}", nid)))
            }
        }
    }

    fn import_attestation_template(session: &Session, attesting_key: u16, attested_key: u16, template_cert: Option<Pem>) -> Result<bool, MgmError> {
        match session.get_object_info(attesting_key, ObjectType::Opaque) {
            Ok(opaque) => {
                if template_cert.is_some() || opaque.algorithm() != &ObjectAlgorithm::OpaqueX509Certificate {
                    return Err(MgmError::Error("An opaque object with the same ID as the attesting key already exists on the device".to_string()))
                }
                Ok(false)
            },
            Err(_) => {
                if template_cert.is_some() {
                    let (_, algo, der_bytes) = Self::parse_asym_pem(template_cert.unwrap())?;
                    if algo != ObjectAlgorithm::OpaqueX509Certificate {
                        return Err(MgmError::InvalidInput("Attestation template is not an X509Certificate".to_string()));
                    }
                    session.import_cert(attesting_key, "template_cert", &ObjectDomain::from_primitive(0xFFFF), &Vec::new(), &der_bytes)?;
                } else {
                    let template_cert = session.sign_attestation_certificate(attested_key, 0)?;
                    session.import_cert(attesting_key, "template_cert", &ObjectDomain::from_primitive(0xFFFF), &Vec::new(), template_cert.as_slice())?;
                }
                Ok(true)
            }
        }
    }
}


//----------------------------------------------------------
//       JAVA Special case
//----------------------------------------------------------

#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
pub enum JavaCommand {
    List,
    GetKeyProperties,
    Generate,
    Import,
    Delete,
    Exit,
}

impl Command for JavaCommand {
    fn label(&self) -> &'static str {
        match self {
            Self::List => "List",
            Self::GetKeyProperties => "Get Object Properties",
            Self::Generate => "Generate",
            Self::Import => "Import",
            Self::Delete => "Delete",
            Self::Exit => EXIT_LABEL,
        }
    }

    fn description(&self) -> &'static str {
        match self {
            Self::List => "List all SunPKCS11 compatible keys where an asymmetric key and an X509Certificate objects have the same Object ID on the YubiHSM",
            Self::GetKeyProperties => "Get properties of a SunPKCS11 compatible asymmetric key stored on the YubiHSM",
            Self::Generate => "Generate a new SunPKCS11 compatible asymmetric key inside the YubiHSM by signing an attestation certificate for the generated key and store it using the same Object ID as the private key",
            Self::Import => "Import an asymmetric key and an X509 certificate into the YubiHSM using the same Object ID",
            Self::Delete => "Delete an asymmetric key an the X509 certificate with the same Object ID from the YubiHSM",
            Self::Exit => "",
        }
    }

    fn required_capabilities(&self) -> &'static [ObjectCapability] {
        match self {
            Self::List | Self::GetKeyProperties | Self::Exit => &[],
            Self::Generate => &[ObjectCapability::GenerateAsymmetricKey, ObjectCapability::PutOpaque, ObjectCapability::SignAttestationCertificate],
            Self::Import => &[ObjectCapability::PutAsymmetricKey, ObjectCapability::PutOpaque],
            Self::Delete => &[ObjectCapability::DeleteAsymmetricKey, ObjectCapability::DeleteOpaque],
        }
    }

    fn require_all_capabilities(&self) -> bool {
        true
    }
}

pub struct JavaOps;

impl YubihsmOperations for JavaOps {

    fn context(&self) -> &'static str {
        JavaOps::SUNPKCS11_CONTEXT
    }

    fn get_all_objects(&self, session: &Session) -> Result<Vec<ObjectDescriptor>, MgmError> {
        let certs = session.list_objects_with_filter(
            0,
            ObjectType::Opaque,
            "",
            ObjectAlgorithm::OpaqueX509Certificate,
            &Vec::new())?;

        let mut keys = Vec::new();
        for c in &certs {
            if let Ok(desc) = session.get_object_info(c.object_id, ObjectType::AsymmetricKey) {
                keys.push(desc);
            }
        }
        Ok(keys)
    }

    fn get_generation_algorithms(&self) -> Vec<MgmAlgorithm> {
        AsymmetricOperations.get_generation_algorithms()
    }

    fn get_object_capabilities(&self, _object_type: Option<ObjectType>, object_algorithm: Option<ObjectAlgorithm>) -> Result<Vec<ObjectCapability>, MgmError> {
        AsymmetricOperations.get_object_capabilities(_object_type, object_algorithm)
    }

    fn generate(&self, session: &Session, spec: &NewObjectSpec) -> Result<u16, MgmError> {
        Self::check_free_id(session, spec.id)?;

        let key_id = if spec.id != 0 {
            AsymmetricOperations.generate(session, spec)?
        } else {
            let mut id;
            loop {
                id = AsymmetricOperations.generate(session, spec)?;
                if session.get_object_info(id, ObjectType::Opaque).is_err() {
                    break;
                }
                session.delete_object(id, ObjectType::AsymmetricKey)?;
            }
            id
        };

        let cert = session.sign_attestation_certificate(key_id, 0);
        let cert = match cert {
            Ok(c) => c,
            Err(e) => {
                session.delete_object(key_id, ObjectType::AsymmetricKey)?;
                return Err(MgmError::from(e))
            }
        };

        let cert_capabilities =
            if spec.capabilities.contains(&ObjectCapability::ExportableUnderWrap) {
                vec![ObjectCapability::ExportableUnderWrap]
            } else {
                vec![]
            };

        let res = session.import_cert(
            key_id,
            spec.label.as_str(),
            spec.domains.as_slice(),
            cert_capabilities.as_slice(),
            cert.as_slice());

        match res {
            Ok(_) => Ok(key_id),
            Err(e) => {
                session.delete_object(key_id, ObjectType::AsymmetricKey)?;
                Err(MgmError::from(e))
            }
        }
    }

    fn import(&self, session: &Session, spec: &NewObjectSpec) -> Result<u16, MgmError> {
        Self::check_free_id(session, spec.id)?;

        let key_id = if spec.id != 0 {
            AsymmetricOperations.import(session, spec)?
        } else {
            let mut id;
            loop {
                id = AsymmetricOperations.import(session, spec)?;
                if session.get_object_info(id, ObjectType::Opaque).is_err() {
                    break;
                }
                session.delete_object(id, ObjectType::AsymmetricKey)?;
            }
            id
        };

        let cert_capabilities =
            if spec.capabilities.contains(&ObjectCapability::ExportableUnderWrap) {
                vec![ObjectCapability::ExportableUnderWrap]
            } else {
                vec![]
            };

        let res = session.import_cert(
            key_id,
            spec.label.as_str(),
            spec.domains.as_slice(),
            cert_capabilities.as_slice(),
            spec.data[1].as_slice());

        match res {
            Ok(_) => Ok(key_id),
            Err(e) => {
                session.delete_object(key_id, ObjectType::AsymmetricKey)?;
                Err(MgmError::from(e))
            }
        }    }

    fn delete(&self, session: &Session, object_id: u16, _object_type: &ObjectType) -> Result<(), MgmError> {
        session.delete_object(object_id, ObjectType::AsymmetricKey)?;
        session.delete_object(object_id, ObjectType::Opaque)?;
        Ok(())
    }
}

impl JavaOps {

    pub const SUNPKCS11_CONTEXT: &'static str = "sunpkcs11";

    fn check_free_id(session: &Session, id: u16) -> Result<(), MgmError> {
        if id != 0 && (session.get_object_info(id, ObjectType::AsymmetricKey).is_ok() || session.get_object_info(id, ObjectType::Opaque).is_ok()) {
            return Err(MgmError::Error("Object ID already in use".to_string()));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::traits::operation_traits::YubihsmOperations;

    // ── Helper: build ObjectDescriptor with specific caps + algo ──

    fn make_desc(algo: ObjectAlgorithm, caps: Vec<ObjectCapability>) -> ObjectDescriptor {
        let mut desc = ObjectDescriptor::default();
        desc.set_algorithm(algo);
        desc.set_capabilities(caps);
        desc
    }
    
    // ══════════════════════════════════════════════
    //  is_rsa_key_algorithm
    // ══════════════════════════════════════════════

    #[test]
    fn test_is_rsa() {
        assert!(AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::Rsa2048));
        assert!(AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::Rsa3072));
        assert!(AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::Rsa4096));
    }

    #[test]
    fn test_is_rsa_rejects() {
        assert!(!AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::EcP256));
        assert!(!AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::Ed25519));
        assert!(!AsymmetricOperations::is_rsa_key_algorithm(&ObjectAlgorithm::Aes128));
    }

    // ══════════════════════════════════════════════
    //  is_ec_key_algorithm
    // ══════════════════════════════════════════════

    #[test]
    fn test_is_ec_p256() {
        for algo in MgmAlgorithm::EC_KEY_ALGORITHMS {
            assert!(AsymmetricOperations::is_ec_key_algorithm(&algo.algorithm()));
        }
    }

    #[test]
    fn test_is_ec_rejects() {
        assert!(!AsymmetricOperations::is_ec_key_algorithm(&ObjectAlgorithm::Rsa2048));
        assert!(!AsymmetricOperations::is_ec_key_algorithm(&ObjectAlgorithm::Ed25519));
        assert!(!AsymmetricOperations::is_ec_key_algorithm(&ObjectAlgorithm::Aes128));
    }

    // ══════════════════════════════════════════════
    //  is_oaep_algorithm
    // ══════════════════════════════════════════════

    #[test]
    fn test_is_oaep_sha256() {
        for algo in MgmAlgorithm::RSA_OAEP_ALGORITHMS {
            assert!(AsymmetricOperations::is_oaep_algorithm(&algo.algorithm()));
        }
    }

    // ══════════════════════════════════════════════
    //  get_hashed_bytes
    // ═════════════════════��════════════════════════

    #[test]
    fn test_hash_sha1_length() {
        let input = b"test";
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha1, input).unwrap();
        assert_eq!(hash.len(), 20); // SHA-1 = 20 bytes
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPssSha1, input).unwrap();
        assert_eq!(hash.len(), 20); // SHA-1 = 20 bytes
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::EcdsaSha1, input).unwrap();
        assert_eq!(hash.len(), 20); // SHA-1 = 20 bytes
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaOaepSha1, input).unwrap();
        assert_eq!(hash.len(), 20); // SHA-1 = 20 bytes
    }

    #[test]
    fn test_hash_sha256_length() {
        let input = b"test";
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha256, input).unwrap();
        assert_eq!(hash.len(), 32);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPssSha256, input).unwrap();
        assert_eq!(hash.len(), 32);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::EcdsaSha256, input).unwrap();
        assert_eq!(hash.len(), 32);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaOaepSha256, input).unwrap();
        assert_eq!(hash.len(), 32);
    }

    #[test]
    fn test_hash_sha384_length() {
        let input = b"test";
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha384, input).unwrap();
        assert_eq!(hash.len(), 48);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPssSha384, input).unwrap();
        assert_eq!(hash.len(), 48);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::EcdsaSha384, input).unwrap();
        assert_eq!(hash.len(), 48);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaOaepSha384, input).unwrap();
        assert_eq!(hash.len(), 48);
    }

    #[test]
    fn test_hash_sha512_length() {
        let input = b"test";
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha512, input).unwrap();
        assert_eq!(hash.len(), 64);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPssSha512, input).unwrap();
        assert_eq!(hash.len(), 64);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::EcdsaSha512, input).unwrap();
        assert_eq!(hash.len(), 64);
        let hash = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaOaepSha512, input).unwrap();
        assert_eq!(hash.len(), 64);
    }

    #[test]
    fn test_hash_deterministic() {
        let h1 = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha256, b"hello").unwrap();
        let h2 = AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::RsaPkcs1Sha256, b"hello").unwrap();
        assert_eq!(h1, h2);
    }

    #[test]
    fn test_hash_unsupported_algo() {
        assert!(AsymmetricOperations::get_hashed_bytes(&ObjectAlgorithm::Aes128, b"test").is_err());
    }

    // ══════════════════════════════════════════════
    //  get_mgf1_algorithm
    // ══════════════════════════════════════════════

    #[test]
    fn test_mgf1_oaep_sha1() {
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaOaepSha1).unwrap(),
            ObjectAlgorithm::Mgf1Sha1
        );
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaPssSha1).unwrap(),
            ObjectAlgorithm::Mgf1Sha1
        );
    }

    #[test]
    fn test_mgf1_oaep_sha256() {
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaOaepSha256).unwrap(),
            ObjectAlgorithm::Mgf1Sha256
        );
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaPssSha256).unwrap(),
            ObjectAlgorithm::Mgf1Sha256
        );

    }

    #[test]
    fn test_mgf1_pss_sha384() {
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaOaepSha384).unwrap(),
            ObjectAlgorithm::Mgf1Sha384
        );
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaPssSha384).unwrap(),
            ObjectAlgorithm::Mgf1Sha384
        );

    }

    #[test]
    fn test_mgf1_pss_sha512() {
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaOaepSha512).unwrap(),
            ObjectAlgorithm::Mgf1Sha512
        );
        assert_eq!(
            AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::RsaPssSha512).unwrap(),
            ObjectAlgorithm::Mgf1Sha512
        );
    }

    #[test]
    fn test_mgf1_unsupported() {
        assert!(AsymmetricOperations::get_mgf1_algorithm(&ObjectAlgorithm::Aes128).is_err());
    }

    // ══════════════════════════════════════════════
    //  get_object_capabilities
    // ══════════════════════════════════════════════

    #[test]
    fn test_object_caps_rsa() {
        let caps = AsymmetricOperations.get_object_capabilities(None, Some(ObjectAlgorithm::Rsa2048)).unwrap();
        assert_eq!(caps.len(), AsymmetricOperations::RSA_KEY_CAPABILITIES.len());
        assert!(caps.iter().all(|item| AsymmetricOperations::RSA_KEY_CAPABILITIES.contains(item)));
    }

    #[test]
    fn test_object_caps_ec() {
        let caps = AsymmetricOperations.get_object_capabilities(None, Some(ObjectAlgorithm::EcP256)).unwrap();
        assert_eq!(caps.len(), AsymmetricOperations::EC_KEY_CAPABILITIES.len());
        assert!(caps.iter().all(|item| AsymmetricOperations::EC_KEY_CAPABILITIES.contains(item)));
    }

    #[test]
    fn test_object_caps_ed() {
        let caps = AsymmetricOperations.get_object_capabilities(None, Some(ObjectAlgorithm::Ed25519)).unwrap();
        assert_eq!(caps.len(), AsymmetricOperations::ED_KEY_CAPABILITIES.len());
        assert!(caps.iter().all(|item| AsymmetricOperations::ED_KEY_CAPABILITIES.contains(item)));
    }

    #[test]
    fn test_object_caps_opaque() {
        let caps = AsymmetricOperations.get_object_capabilities(None, Some(ObjectAlgorithm::OpaqueX509Certificate)).unwrap();
        assert_eq!(caps.len(), AsymmetricOperations::OPAQUE_CAPABILITIES.len());
        assert!(caps.iter().all(|item| AsymmetricOperations::OPAQUE_CAPABILITIES.contains(item)));
    }

    #[test]
    fn test_object_caps_none_algo_err() {
        assert!(AsymmetricOperations.get_object_capabilities(None, None).is_err());
    }

    #[test]
    fn test_object_caps_unsupported_algo_err() {
        assert!(AsymmetricOperations.get_object_capabilities(None, Some(ObjectAlgorithm::Aes128)).is_err());
    }

    #[test]
    fn test_object_caps_type_irrelevant() {
        let caps = AsymmetricOperations.get_object_capabilities(Some(ObjectType::SymmetricKey), Some(ObjectAlgorithm::Rsa2048)).unwrap();
        assert_eq!(caps.len(), AsymmetricOperations::RSA_KEY_CAPABILITIES.len());
        assert!(caps.iter().all(|item| AsymmetricOperations::RSA_KEY_CAPABILITIES.contains(item)));
    }

    // ══════════════════════════════════════════════
    //  get_signing_algorithms
    // ══════════════════════════════════════════════

    #[test]
    fn test_signing_algos_rsa_pkcs_only() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignPkcs]);
        let signkey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::SignPkcs]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert_eq!(algos.len(), MgmAlgorithm::RSA_PKCS_ALGORITHMS.len());
        assert!(algos.iter().all(|item| MgmAlgorithm::RSA_PKCS_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_signing_algos_rsa_pss_only() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignPkcs, ObjectCapability::SignPss]);
        let signkey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::SignPss]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert_eq!(algos.len(), MgmAlgorithm::RSA_PSS_ALGORITHMS.len()); // RSA_PSS_ALGORITHMS = 4
        assert!(algos.iter().all(|item| MgmAlgorithm::RSA_PSS_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_signing_algos_rsa_both() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignPkcs, ObjectCapability::SignPss]);
        let signkey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::SignPkcs, ObjectCapability::SignPss]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert_eq!(algos.len(), MgmAlgorithm::RSA_PKCS_ALGORITHMS.len() + MgmAlgorithm::RSA_PSS_ALGORITHMS.len()); // 4 PKCS + 4 PSS
        assert!(algos.iter().all(|item| MgmAlgorithm::RSA_PKCS_ALGORITHMS.contains(item) || MgmAlgorithm::RSA_PSS_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_signing_algos_ec() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignEcdsa]);
        let signkey = make_desc(ObjectAlgorithm::EcP256, vec![ObjectCapability::SignEcdsa]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert_eq!(algos.len(), MgmAlgorithm::ECDSA_ALGORITHMS.len()); // ECDSA_ALGORITHMS = 4
        assert!(algos.iter().all(|item| MgmAlgorithm::ECDSA_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_signing_algos_ed25519() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignEddsa]);
        let signkey = make_desc(ObjectAlgorithm::Ed25519, vec![ObjectCapability::SignEddsa]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert_eq!(algos.len(), MgmAlgorithm::EDDSA_ALGORITHMS.len()); // EDDSA_ALGORITHMS = 1
        assert!(algos.iter().all(|item| MgmAlgorithm::EDDSA_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_signing_algos_empty_when_authkey_lacks_cap() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![]); // no signing cap
        let signkey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::SignPkcs]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        assert!(algos.is_empty());
    }

    #[test]
    fn test_signing_algos_match_key_algorithm() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::SignPkcs, ObjectCapability::SignEcdsa]); // no signing cap
        let signkey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::SignPkcs, ObjectCapability::SignEcdsa]);
        let algos = AsymmetricOperations::get_signing_algorithms(&authkey, &signkey);
        // Even though the signing key was given the capability to sign ECDSA, it is an RSA key, so it will only be
        // able to sign with RSA algorithms. The presence of the SignEcdsa cap on the key should not cause any ECDSA
        // algos to be included in the result.
        assert_eq!(algos.len(), MgmAlgorithm::RSA_PKCS_ALGORITHMS.len());
        assert!(algos.iter().all(|item| MgmAlgorithm::RSA_PKCS_ALGORITHMS.contains(item)));
    }

    // ══════════════════════════════════════════════
    //  get_decryption_algorithms
    // ══════════════════════════════════════════════

    #[test]
    fn test_decryption_algos_pkcs() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::DecryptPkcs]);
        let deckey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::DecryptPkcs]);
        let algos = AsymmetricOperations::get_decryption_algorithms(&authkey, &deckey);
        assert_eq!(algos.len(), 1); // just RsaPkcs1Decrypt
        assert_eq!(algos[0], ObjectAlgorithm::RsaPkcs1Decrypt.into());
    }

    #[test]
    fn test_decryption_algos_oaep() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::DecryptOaep]);
        let deckey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::DecryptOaep]);
        let algos = AsymmetricOperations::get_decryption_algorithms(&authkey, &deckey);
        assert_eq!(algos.len(), MgmAlgorithm::RSA_OAEP_ALGORITHMS.len()); // RSA_OAEP_ALGORITHMS = 4
        assert!(algos.iter().all(|item| MgmAlgorithm::RSA_OAEP_ALGORITHMS.contains(item)));
    }

    #[test]
    fn test_decryption_algos_empty_for_ec() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::DecryptPkcs]);
        let deckey = make_desc(ObjectAlgorithm::EcP256, vec![ObjectCapability::DecryptPkcs]);
        let algos = AsymmetricOperations::get_decryption_algorithms(&authkey, &deckey);
        assert!(algos.is_empty()); // EC keys can't decrypt
    }

    #[test]
    fn test_decryption_algos_limited_by_authkey() {
        let authkey = make_desc(ObjectAlgorithm::ANY, vec![ObjectCapability::DecryptOaep]);
        let deckey = make_desc(ObjectAlgorithm::Rsa2048, vec![ObjectCapability::DecryptPkcs , ObjectCapability::DecryptOaep]);
        let algos = AsymmetricOperations::get_decryption_algorithms(&authkey, &deckey);
        assert_eq!(algos.len(), MgmAlgorithm::RSA_OAEP_ALGORITHMS.len());
        // PKCS decryption should not be included because the authkey doesn't have the DecryptPkcs capability
        assert!(!algos.contains(&ObjectAlgorithm::RsaPkcs1Decrypt.into()));
    }

    // ══════════════════════════════════════════════
    //  parse_asym_pem  (requires test fixture PEM files)
    // ══════════════════════════════════════════════

    fn fixture_path(name: &str) -> String {
        let path = format!("resources/test_data/{}", name);
        assert!(
            std::path::Path::new(&path).exists(),
            "Test fixture missing: {}.",
            path
        );
        path
    }

    fn load_pem(name: &str) -> Pem {
        let content = std::fs::read_to_string(fixture_path(name)).unwrap();
        pem::parse_many(content).unwrap().remove(0)
    }

    #[test]
    fn test_parse_rsa_private_key() {
        let pem = load_pem("rsa2048_private.pem");
        let (obj_type, algo, bytes) = AsymmetricOperations::parse_asym_pem(pem).unwrap();
        assert_eq!(obj_type, ObjectType::AsymmetricKey);
        assert_eq!(algo, ObjectAlgorithm::Rsa2048);
        assert_eq!(bytes.len(), 256); // RSA 2048 private key should be 256 bytes
    }

    #[test]
    fn test_parse_ecp256_private_key() {
        let pem = load_pem("ecp256_private.pem");
        let (obj_type, algo, bytes) = AsymmetricOperations::parse_asym_pem(pem).unwrap();
        assert_eq!(obj_type, ObjectType::AsymmetricKey);
        assert_eq!(algo, ObjectAlgorithm::EcP256);
        assert_eq!(bytes.len(), 32); // EC P256 private key should be 32 bytes
    }

    #[test]
    fn test_parse_ecp256_public_key() {
        let pem = load_pem("ecp256_public.pem");
        let (obj_type, algo, bytes) = AsymmetricOperations::parse_asym_pem(pem).unwrap();
        assert_eq!(obj_type, ObjectType::PublicKey);
        assert_eq!(algo, ObjectAlgorithm::EcP256);
        assert_eq!(bytes.len(), 65); // EC P256 public key should be 65 bytes (0x04 + 32 byte X + 32 byte Y)
    }

    #[test]
    fn test_parse_x509_cert() {
        let pem = load_pem("x509_cert.pem");
        let (obj_type, algo, bytes) = AsymmetricOperations::parse_asym_pem(pem).unwrap();
        assert_eq!(obj_type, ObjectType::Opaque);
        assert_eq!(algo, ObjectAlgorithm::OpaqueX509Certificate);
        assert!(!bytes.is_empty());
    }

    #[test]
    fn test_parse_empty_pem() {
        let pem = Pem::new("EMPTY", vec![]);
        assert!(AsymmetricOperations::parse_asym_pem(pem).is_err());
    }
}