//! Post-quantum hybrid crypto provider for OpenMLS (M7 PoC).
//!
//! Uses X25519 + ML-KEM-768 hybrid KEM for HPKE operations where openmls
//! would use DHKEM(X25519), and delegates all other operations (AEAD, hash,
//! signatures, KDF, randomness) to `openmls_rust_crypto::RustCrypto`.
//!
//! # Key format
//!
//! When the provider sees a **hybrid public key** (length `HYBRID_PUBLIC_KEY_LEN` =
//! 32 + 1184 bytes) or **hybrid private key** (length `HYBRID_PRIVATE_KEY_LEN` =
//! 32 + 2400 bytes), it uses `hybrid_kem` for HPKE. Otherwise it delegates to
//! RustCrypto (classical X25519 HPKE).
//!
//! # MLS compatibility
//!
//! The current MLS ciphersuite (MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)
//! uses 32-byte X25519 init keys in the wire format. This provider can produce
//! and consume **hybrid** init keys (1216-byte public, 2432-byte private), but
//! that is a non-standard extension: other MLS implementations will not
//! accept KeyPackages with hybrid init keys unless they implement the same
//! extension. This PoC validates that the OpenMLS trait surface is satisfiable
//! with a custom HPKE backend; full interoperability would require a new
//! ciphersuite or protocol extension.

use openmls_rust_crypto::RustCrypto;
use openmls_traits::{
    crypto::OpenMlsCrypto,
    types::{
        CryptoError, ExporterSecret, HpkeCiphertext, HpkeConfig, HpkeKeyPair, HpkeKemType,
    },
    OpenMlsCryptoProvider,
};
use tls_codec::SecretVLBytes;

use crate::hybrid_kem::{
    hybrid_decapsulate_only, hybrid_decrypt, hybrid_encapsulate_only, hybrid_encrypt,
    hybrid_export, HybridKeypair, HybridPublicKey,
    HYBRID_KEM_OUTPUT_LEN, HYBRID_PRIVATE_KEY_LEN, HYBRID_PUBLIC_KEY_LEN,
};
use crate::keystore::DiskKeyStore;

// Re-export types used by OpenMlsCrypto (full path for clarity).
use openmls_traits::types::{
    AeadType, Ciphersuite, HashType, SignatureScheme,
};

/// Crypto backend that uses hybrid KEM for HPKE when keys are in hybrid format,
/// and delegates everything else to RustCrypto.
///
/// When `hybrid_enabled` is `true`, `derive_hpke_keypair` produces hybrid keys
/// (1216-byte public, 2432-byte private). When `false`, it delegates to
/// RustCrypto and produces classical 32-byte X25519 keys.
///
/// The `hpke_seal` / `hpke_open` methods always detect the key format by length,
/// so they work correctly regardless of the flag — a hybrid-length key will use
/// hybrid KEM, a classical-length key will use RustCrypto.
#[derive(Debug)]
pub struct HybridCrypto {
    rust_crypto: RustCrypto,
    /// When true, `derive_hpke_keypair` produces hybrid (X25519 + ML-KEM-768)
    /// keys. When false, it produces classical X25519 keys via RustCrypto.
    hybrid_enabled: bool,
}

impl HybridCrypto {
    /// Create a hybrid-enabled crypto backend (derive_hpke_keypair produces hybrid keys).
    pub fn new() -> Self {
        Self {
            rust_crypto: RustCrypto::default(),
            hybrid_enabled: true,
        }
    }

    /// Alias for `new()` — hybrid mode enabled.
    pub fn new_hybrid() -> Self {
        Self::new()
    }

    /// Create a classical crypto backend (derive_hpke_keypair produces standard
    /// X25519 keys, but seal/open still accept hybrid keys by length detection).
    pub fn new_classical() -> Self {
        Self {
            rust_crypto: RustCrypto::default(),
            hybrid_enabled: false,
        }
    }

    /// Whether this backend produces hybrid keys from `derive_hpke_keypair`.
    pub fn is_hybrid_enabled(&self) -> bool {
        self.hybrid_enabled
    }

    /// Expose the underlying RustCrypto for rand() and delegation.
    pub fn rust_crypto(&self) -> &RustCrypto {
        &self.rust_crypto
    }

    fn is_hybrid_public_key(pk_r: &[u8]) -> bool {
        pk_r.len() == HYBRID_PUBLIC_KEY_LEN
    }

    fn is_hybrid_private_key(sk_r: &[u8]) -> bool {
        sk_r.len() == HYBRID_PRIVATE_KEY_LEN
    }
}

impl Default for HybridCrypto {
    fn default() -> Self {
        Self::new()
    }
}

impl OpenMlsCrypto for HybridCrypto {
    fn supports(&self, ciphersuite: Ciphersuite) -> Result<(), CryptoError> {
        self.rust_crypto.supports(ciphersuite)
    }

    fn supported_ciphersuites(&self) -> Vec<Ciphersuite> {
        self.rust_crypto.supported_ciphersuites()
    }

    fn hkdf_extract(
        &self,
        hash_type: HashType,
        salt: &[u8],
        ikm: &[u8],
    ) -> Result<SecretVLBytes, CryptoError> {
        self.rust_crypto.hkdf_extract(hash_type, salt, ikm)
    }

    fn hkdf_expand(
        &self,
        hash_type: HashType,
        prk: &[u8],
        info: &[u8],
        okm_len: usize,
    ) -> Result<SecretVLBytes, CryptoError> {
        self.rust_crypto.hkdf_expand(hash_type, prk, info, okm_len)
    }

    fn hash(&self, hash_type: HashType, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
        self.rust_crypto.hash(hash_type, data)
    }

    fn aead_encrypt(
        &self,
        alg: AeadType,
        key: &[u8],
        data: &[u8],
        nonce: &[u8],
        aad: &[u8],
    ) -> Result<Vec<u8>, CryptoError> {
        self.rust_crypto.aead_encrypt(alg, key, data, nonce, aad)
    }

    fn aead_decrypt(
        &self,
        alg: AeadType,
        key: &[u8],
        ct_tag: &[u8],
        nonce: &[u8],
        aad: &[u8],
    ) -> Result<Vec<u8>, CryptoError> {
        self.rust_crypto.aead_decrypt(alg, key, ct_tag, nonce, aad)
    }

    fn signature_key_gen(&self, alg: SignatureScheme) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
        self.rust_crypto.signature_key_gen(alg)
    }

    fn verify_signature(
        &self,
        alg: SignatureScheme,
        data: &[u8],
        pk: &[u8],
        signature: &[u8],
    ) -> Result<(), CryptoError> {
        self.rust_crypto.verify_signature(alg, data, pk, signature)
    }

    fn sign(&self, alg: SignatureScheme, data: &[u8], key: &[u8]) -> Result<Vec<u8>, CryptoError> {
        self.rust_crypto.sign(alg, data, key)
    }

    fn hpke_seal(
        &self,
        config: HpkeConfig,
        pk_r: &[u8],
        info: &[u8],
        aad: &[u8],
        ptxt: &[u8],
    ) -> HpkeCiphertext {
        if Self::is_hybrid_public_key(pk_r) {
            // The trait `OpenMlsCrypto::hpke_seal` returns `HpkeCiphertext` (not
            // `Result`), so we cannot propagate errors through the return type.
            // Returning an empty ciphertext would silently cause data loss.
            // Instead, panic on failure — a hybrid key that passes the length
            // check but fails deserialization or encryption indicates a critical
            // bug (corrupted key material), not a recoverable condition.
            let recipient_pk = HybridPublicKey::from_bytes(pk_r)
                .expect("hybrid public key deserialization failed — key material is corrupted");
            // Pass HPKE info and aad through for proper context binding (RFC 9180).
            let envelope = hybrid_encrypt(&recipient_pk, ptxt, info, aad)
                .expect("hybrid HPKE encryption failed — critical crypto error");
            let kem_output = envelope[..HYBRID_KEM_OUTPUT_LEN].to_vec();
            let ciphertext = envelope[HYBRID_KEM_OUTPUT_LEN..].to_vec();
            HpkeCiphertext {
                kem_output: kem_output.into(),
                ciphertext: ciphertext.into(),
            }
        } else {
            self.rust_crypto.hpke_seal(config, pk_r, info, aad, ptxt)
        }
    }

    fn hpke_open(
        &self,
        config: HpkeConfig,
        input: &HpkeCiphertext,
        sk_r: &[u8],
        info: &[u8],
        aad: &[u8],
    ) -> Result<Vec<u8>, CryptoError> {
        if Self::is_hybrid_private_key(sk_r) {
            let keypair = HybridKeypair::from_private_bytes(sk_r)
                .map_err(|_| CryptoError::HpkeDecryptionError)?;
            let envelope: Vec<u8> = input
                .kem_output.as_slice()
                .iter()
                .chain(input.ciphertext.as_slice())
                .copied()
                .collect();
            // Pass HPKE info and aad through for proper context binding (RFC 9180).
            hybrid_decrypt(&keypair, &envelope, info, aad)
                .map_err(|_| CryptoError::HpkeDecryptionError)
        } else {
            self.rust_crypto.hpke_open(config, input, sk_r, info, aad)
        }
    }

    fn hpke_setup_sender_and_export(
        &self,
        config: HpkeConfig,
        pk_r: &[u8],
        info: &[u8],
        exporter_context: &[u8],
        exporter_length: usize,
    ) -> Result<(Vec<u8>, ExporterSecret), CryptoError> {
        if Self::is_hybrid_public_key(pk_r) {
            // A key that passes the hybrid length check but fails deserialization
            // is corrupted — return an error instead of silently downgrading to
            // classical crypto (which would defeat PQ protection).
            let recipient_pk = HybridPublicKey::from_bytes(pk_r)
                .map_err(|_| CryptoError::SenderSetupError)?;
            let (kem_output, shared_secret) =
                hybrid_encapsulate_only(&recipient_pk).map_err(|_| CryptoError::SenderSetupError)?;
            let exported = hybrid_export(&shared_secret, exporter_context, exporter_length);
            Ok((kem_output, exported.into()))
        } else {
            self.rust_crypto.hpke_setup_sender_and_export(
                config, pk_r, info, exporter_context, exporter_length,
            )
        }
    }

    fn hpke_setup_receiver_and_export(
        &self,
        config: HpkeConfig,
        enc: &[u8],
        sk_r: &[u8],
        info: &[u8],
        exporter_context: &[u8],
        exporter_length: usize,
    ) -> Result<ExporterSecret, CryptoError> {
        if Self::is_hybrid_private_key(sk_r) {
            let keypair = HybridKeypair::from_private_bytes(sk_r)
                .map_err(|_| CryptoError::ReceiverSetupError)?;
            let shared_secret =
                hybrid_decapsulate_only(&keypair, enc).map_err(|_| CryptoError::ReceiverSetupError)?;
            let exported = hybrid_export(&shared_secret, exporter_context, exporter_length);
            Ok(exported.into())
        } else {
            self.rust_crypto.hpke_setup_receiver_and_export(
                config, enc, sk_r, info, exporter_context, exporter_length,
            )
        }
    }

    fn derive_hpke_keypair(&self, config: HpkeConfig, ikm: &[u8]) -> HpkeKeyPair {
        if self.hybrid_enabled && config.0 == HpkeKemType::DhKem25519 {
            let kp = HybridKeypair::derive_from_ikm(ikm);
            let private_bytes = kp.private_to_bytes();
            HpkeKeyPair {
                private: private_bytes.as_slice().into(),
                public: kp.public_key().to_bytes(),
            }
        } else {
            self.rust_crypto.derive_hpke_keypair(config, ikm)
        }
    }
}

/// OpenMLS crypto provider that uses hybrid KEM for HPKE (when keys are in
/// hybrid format) and delegates the rest to RustCrypto.
#[derive(Debug)]
pub struct HybridCryptoProvider {
    crypto: HybridCrypto,
    key_store: DiskKeyStore,
}

impl HybridCryptoProvider {
    /// Create a hybrid-enabled provider (KeyPackages will contain hybrid init keys).
    pub fn new(key_store: DiskKeyStore) -> Self {
        Self {
            crypto: HybridCrypto::new_hybrid(),
            key_store,
        }
    }

    /// Alias for `new()` — hybrid mode enabled.
    pub fn new_hybrid(key_store: DiskKeyStore) -> Self {
        Self::new(key_store)
    }

    /// Create a classical-mode provider (KeyPackages use standard X25519 init keys,
    /// but seal/open still accept hybrid keys by length detection).
    pub fn new_classical(key_store: DiskKeyStore) -> Self {
        Self {
            crypto: HybridCrypto::new_classical(),
            key_store,
        }
    }

    /// Whether this provider produces hybrid keys from `derive_hpke_keypair`.
    pub fn is_hybrid_enabled(&self) -> bool {
        self.crypto.is_hybrid_enabled()
    }
}

impl Default for HybridCryptoProvider {
    fn default() -> Self {
        Self::new(DiskKeyStore::ephemeral())
    }
}

impl OpenMlsCryptoProvider for HybridCryptoProvider {
    type CryptoProvider = HybridCrypto;
    type RandProvider = RustCrypto;
    type KeyStoreProvider = DiskKeyStore;

    fn crypto(&self) -> &Self::CryptoProvider {
        &self.crypto
    }

    fn rand(&self) -> &Self::RandProvider {
        self.crypto.rust_crypto()
    }

    fn key_store(&self) -> &Self::KeyStoreProvider {
        &self.key_store
    }
}

// ── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use openmls_traits::types::HpkeKdfType;

    fn hpke_config_dhkem_x25519() -> HpkeConfig {
        HpkeConfig(
            HpkeKemType::DhKem25519,
            HpkeKdfType::HkdfSha256,
            openmls_traits::types::HpkeAeadType::AesGcm128,
        )
    }

    /// HPKE path with hybrid keys: derive_hpke_keypair (hybrid) -> hpke_seal -> hpke_open.
    #[test]
    fn hybrid_hpke_seal_open_round_trip() {
        let crypto = HybridCrypto::new();
        let ikm = b"test-ikm-for-hybrid-hpke-keypair";

        let keypair = crypto.derive_hpke_keypair(hpke_config_dhkem_x25519(), ikm);
        assert_eq!(keypair.public.len(), HYBRID_PUBLIC_KEY_LEN);
        assert_eq!(keypair.private.as_ref().len(), HYBRID_PRIVATE_KEY_LEN);

        let plaintext = b"hello post-quantum MLS";
        let info = b"mls 1.0 test";
        let aad = b"additional data";

        let ct = crypto.hpke_seal(
            hpke_config_dhkem_x25519(),
            &keypair.public,
            info,
            aad,
            plaintext,
        );
        assert!(!ct.kem_output.as_slice().is_empty());
        assert!(!ct.ciphertext.as_slice().is_empty());

        let decrypted = crypto
            .hpke_open(
                hpke_config_dhkem_x25519(),
                &ct,
                keypair.private.as_ref(),
                info,
                aad,
            )
            .expect("hpke_open with hybrid keys");
        assert_eq!(decrypted.as_slice(), plaintext);
    }

    /// HPKE exporter path: setup_sender_and_export then setup_receiver_and_export.
    #[test]
    fn hybrid_hpke_setup_sender_receiver_export() {
        let crypto = HybridCrypto::new();
        let ikm = b"exporter-ikm";

        let keypair = crypto.derive_hpke_keypair(hpke_config_dhkem_x25519(), ikm);
        let info = b"";
        let exporter_context = b"MLS 1.0 external init";
        let exporter_length = 32;

        let (kem_output, sender_exported) = crypto
            .hpke_setup_sender_and_export(
                hpke_config_dhkem_x25519(),
                &keypair.public,
                info,
                exporter_context,
                exporter_length,
            )
            .expect("sender and export");

        assert_eq!(kem_output.len(), HYBRID_KEM_OUTPUT_LEN);
        assert_eq!(sender_exported.as_ref().len(), exporter_length);

        let receiver_exported = crypto
            .hpke_setup_receiver_and_export(
                hpke_config_dhkem_x25519(),
                &kem_output,
                keypair.private.as_ref(),
                info,
                exporter_context,
                exporter_length,
            )
            .expect("receiver and export");

        assert_eq!(sender_exported.as_ref(), receiver_exported.as_ref());
    }

    /// Classical mode: derive_hpke_keypair produces standard 32-byte X25519 keys.
    #[test]
    fn classical_mode_produces_standard_keys() {
        let crypto = HybridCrypto::new_classical();
        let ikm = b"test-ikm-for-classical-hpke";

        let keypair = crypto.derive_hpke_keypair(hpke_config_dhkem_x25519(), ikm);
        // Classical X25519 keys are 32 bytes
        assert_eq!(keypair.public.len(), 32);
        assert_eq!(keypair.private.as_ref().len(), 32);
    }

    /// Classical mode round-trip: seal/open works with classical keys.
    #[test]
    fn classical_mode_seal_open_round_trip() {
        let crypto = HybridCrypto::new_classical();
        let ikm = b"test-ikm-for-classical-round-trip";

        let keypair = crypto.derive_hpke_keypair(hpke_config_dhkem_x25519(), ikm);
        assert_eq!(keypair.public.len(), 32); // classical key

        let plaintext = b"hello classical MLS";
        let info = b"mls 1.0 test";
        let aad = b"additional data";

        let ct = crypto.hpke_seal(
            hpke_config_dhkem_x25519(),
            &keypair.public,
            info,
            aad,
            plaintext,
        );
        assert!(!ct.kem_output.as_slice().is_empty());

        let decrypted = crypto
            .hpke_open(
                hpke_config_dhkem_x25519(),
                &ct,
                keypair.private.as_ref(),
                info,
                aad,
            )
            .expect("hpke_open with classical keys");
        assert_eq!(decrypted.as_slice(), plaintext);
    }

    /// KeyPackage generation with HybridCryptoProvider (validates full HPKE path in MLS).
    #[test]
    fn key_package_generation_with_hybrid_provider() {
        use openmls::prelude::{
            Credential, CredentialType, CredentialWithKey, CryptoConfig, KeyPackage,
        };
        use std::sync::Arc;
        use tls_codec::Serialize;

        use crate::identity::IdentityKeypair;

        const CIPHERSUITE: Ciphersuite =
            Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;

        let provider = HybridCryptoProvider::default();
        let identity = Arc::new(IdentityKeypair::generate());

        let credential = Credential::new(
            identity.public_key_bytes().to_vec(),
            CredentialType::Basic,
        )
        .unwrap();
        let credential_with_key = CredentialWithKey {
            credential,
            signature_key: identity.public_key_bytes().to_vec().into(),
        };

        let key_package = KeyPackage::builder()
            .build(
                CryptoConfig::with_default_version(CIPHERSUITE),
                &provider,
                identity.as_ref(),
                credential_with_key,
            )
            .expect("KeyPackage with hybrid HPKE");

        let bytes = key_package
            .tls_serialize_detached()
            .expect("serialize KeyPackage");
        assert!(!bytes.is_empty());
    }
}
