//! MLS KeyPackage generation and TLS serialisation.
//!
//! # Ciphersuite
//!
//! `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` (ciphersuite ID `0x0001`).
//! This is the RECOMMENDED ciphersuite from RFC 9420 §17.1.
//!
//! # Single-use semantics
//!
//! Per RFC 9420 §10.1, each KeyPackage MUST be used at most once. The
//! Authentication Service enforces this by atomically removing a package on
//! fetch.
//!
//! # Wire format
//!
//! KeyPackages are TLS-encoded using `tls_codec` (same version as openmls).
//! The resulting bytes are opaque to the quicproquo transport layer.

use openmls::prelude::{
    Ciphersuite, Credential, CredentialType, CredentialWithKey, CryptoConfig, KeyPackage,
    KeyPackageIn, TlsDeserializeTrait, TlsSerializeTrait,
};
use openmls_rust_crypto::OpenMlsRustCrypto;
use sha2::{Digest, Sha256};

use crate::{error::CoreError, identity::IdentityKeypair};

/// The MLS ciphersuite used throughout quicproquo (RFC 9420 §17.1).
pub const ALLOWED_CIPHERSUITE: Ciphersuite =
    Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;

/// Wire value of the allowed ciphersuite (KeyPackage TLS encoding: version 2B, ciphersuite 2B).
const ALLOWED_CIPHERSUITE_WIRE: u16 = 0x0001;

const CIPHERSUITE: Ciphersuite = ALLOWED_CIPHERSUITE;

/// Validates that the KeyPackage bytes use an allowed ciphersuite (Phase 2: ciphersuite allowlist).
///
/// Parses the TLS-encoded KeyPackage and rejects if the ciphersuite is not
/// `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`. Does not verify signatures;
/// the server uses this only to enforce policy before storing.
pub fn validate_keypackage_ciphersuite(bytes: &[u8]) -> Result<(), CoreError> {
    if bytes.len() < 4 {
        return Err(CoreError::Mls("KeyPackage too short for version+ciphersuite".into()));
    }
    let cs_wire = u16::from_be_bytes([bytes[2], bytes[3]]);
    if cs_wire != ALLOWED_CIPHERSUITE_WIRE {
        return Err(CoreError::Mls(format!(
            "KeyPackage ciphersuite {:#06x} not in allowlist (only {:#06x} allowed)",
            cs_wire, ALLOWED_CIPHERSUITE_WIRE
        )));
    }
    // Optionally confirm full parse so we don't accept garbage that happens to have 0x0001 at offset 2.
    let mut cursor = bytes;
    let _kp = KeyPackageIn::tls_deserialize(&mut cursor)
        .map_err(|e| CoreError::Mls(format!("KeyPackage parse: {e:?}")))?;
    Ok(())
}

/// Generate a fresh MLS KeyPackage for `identity` and serialise it.
///
/// # Returns
///
/// `(tls_bytes, sha256_fingerprint)` where:
/// - `tls_bytes` is the TLS-encoded KeyPackage blob, suitable for uploading.
/// - `sha256_fingerprint` is the SHA-256 digest of `tls_bytes` for tamper detection.
///
/// # Errors
///
/// Returns [`CoreError::Mls`] if openmls fails to create the KeyPackage or if
/// TLS serialisation fails.
pub fn generate_key_package(identity: &IdentityKeypair) -> Result<(Vec<u8>, Vec<u8>), CoreError> {
    let backend = OpenMlsRustCrypto::default();

    // Build a BasicCredential using the raw Ed25519 public key bytes as the
    // MLS identity. Per RFC 9420, any byte string may serve as the identity.
    let credential = Credential::new(identity.public_key_bytes().to_vec(), CredentialType::Basic)
        .map_err(|e| CoreError::Mls(format!("{e:?}")))?;

    // The `signature_key` in CredentialWithKey is the Ed25519 public key that
    // will be used to verify the KeyPackage's leaf node signature.
    // `SignaturePublicKey` implements `From<Vec<u8>>`.
    let credential_with_key = CredentialWithKey {
        credential,
        signature_key: identity.public_key_bytes().to_vec().into(),
    };

    // `IdentityKeypair` implements `openmls_traits::signatures::Signer`
    // so it can be passed directly to the builder.
    let key_package = KeyPackage::builder()
        .build(
            CryptoConfig::with_default_version(CIPHERSUITE),
            &backend,
            identity,
            credential_with_key,
        )
        .map_err(|e| CoreError::Mls(format!("{e:?}")))?;

    // TLS-encode the KeyPackage using the trait from the openmls prelude.
    // This uses tls_codec 0.3 (the same version openmls uses internally),
    // avoiding a duplicate-trait conflict with tls_codec 0.4.
    let tls_bytes = key_package
        .tls_serialize_detached()
        .map_err(|e| CoreError::Mls(format!("{e:?}")))?;

    let fingerprint: Vec<u8> = Sha256::digest(&tls_bytes).to_vec();

    Ok((tls_bytes, fingerprint))
}
