//! Ed25519 identity keypair for MLS credentials and AS registration.
//!
//! The [`IdentityKeypair`] is the long-term identity key embedded in MLS
//! `BasicCredential`s. It is used for signing MLS messages and as the
//! indexing key for the Authentication Service.
//!
//! # Zeroize
//!
//! The 32-byte private seed is stored as `Zeroizing<[u8; 32]>`, which zeroes
//! the bytes on drop. `[u8; 32]` is `Copy + Default` and satisfies zeroize's
//! `DefaultIsZeroes` constraint, avoiding a conflict with ed25519-dalek's
//! `SigningKey` zeroize impl.
//!
//! # Fingerprint
//!
//! A 32-byte SHA-256 digest of the raw public key bytes is used as a compact,
//! collision-resistant identifier for logging.

use ed25519_dalek::{Signer as DalekSigner, SigningKey, VerifyingKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use zeroize::Zeroizing;

/// An Ed25519 identity keypair.
///
/// Created with [`IdentityKeypair::generate`]. The private signing key seed
/// is zeroed when this struct is dropped.
pub struct IdentityKeypair {
    /// Raw 32-byte private seed — zeroized on drop.
    ///
    /// Stored as bytes rather than `SigningKey` to satisfy zeroize's
    /// `DefaultIsZeroes` bound on `Zeroizing<T>`.
    seed: Zeroizing<[u8; 32]>,
    /// Corresponding 32-byte public verifying key.
    verifying: VerifyingKey,
}

impl IdentityKeypair {
    /// Recreate an identity keypair from a 32-byte seed.
    pub fn from_seed(seed: [u8; 32]) -> Self {
        let signing = SigningKey::from_bytes(&seed);
        let verifying = signing.verifying_key();
        Self {
            seed: Zeroizing::new(seed),
            verifying,
        }
    }

    /// Return the raw 32-byte private seed (for persistence).
    ///
    /// The returned value is wrapped in [`Zeroizing`] so it is securely
    /// erased when dropped, preventing the seed from lingering in memory.
    pub fn seed_bytes(&self) -> Zeroizing<[u8; 32]> {
        Zeroizing::new(*self.seed)
    }
}

impl IdentityKeypair {
    /// Generate a fresh random Ed25519 identity keypair.
    pub fn generate() -> Self {
        use rand::rngs::OsRng;
        let signing = SigningKey::generate(&mut OsRng);
        let verifying = signing.verifying_key();
        let seed = Zeroizing::new(signing.to_bytes());
        Self { seed, verifying }
    }

    /// Return the raw 32-byte Ed25519 public key.
    ///
    /// This is the byte array used as `identityKey` in `auth.capnp` calls.
    pub fn public_key_bytes(&self) -> [u8; 32] {
        self.verifying.to_bytes()
    }

    /// Return the SHA-256 fingerprint of the public key (32 bytes).
    pub fn fingerprint(&self) -> [u8; 32] {
        let mut hasher = Sha256::new();
        hasher.update(self.verifying.to_bytes());
        hasher.finalize().into()
    }

    /// Reconstruct the `SigningKey` from the stored seed bytes.
    fn signing_key(&self) -> SigningKey {
        SigningKey::from_bytes(&self.seed)
    }
}

/// Implement the openmls `Signer` trait so `IdentityKeypair` can be passed
/// directly to `KeyPackage::builder().build(...)` without needing the external
/// `openmls_basic_credential` crate.
#[cfg(feature = "native")]
impl openmls_traits::signatures::Signer for IdentityKeypair {
    fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, openmls_traits::types::Error> {
        let sk = self.signing_key();
        let sig: ed25519_dalek::Signature = sk.sign(payload);
        Ok(sig.to_bytes().to_vec())
    }

    fn signature_scheme(&self) -> openmls_traits::types::SignatureScheme {
        openmls_traits::types::SignatureScheme::ED25519
    }
}

impl IdentityKeypair {
    /// Sign arbitrary bytes with the Ed25519 key and return the 64-byte signature.
    ///
    /// Used by sealed sender to sign the inner payload for recipient verification.
    pub fn sign_raw(&self, payload: &[u8]) -> [u8; 64] {
        let sk = self.signing_key();
        let sig: ed25519_dalek::Signature = sk.sign(payload);
        sig.to_bytes()
    }

    /// Verify an Ed25519 signature over `payload` using the given public key.
    pub fn verify_raw(
        public_key: &[u8; 32],
        payload: &[u8],
        signature: &[u8; 64],
    ) -> Result<(), crate::error::CoreError> {
        use ed25519_dalek::Verifier;

        let vk = VerifyingKey::from_bytes(public_key)
            .map_err(|e| crate::error::CoreError::Mls(format!("invalid public key: {e}")))?;
        let sig = ed25519_dalek::Signature::from_bytes(signature);
        vk.verify(payload, &sig)
            .map_err(|e| crate::error::CoreError::Mls(format!("signature verification failed: {e}")))
    }
}

/// Verify a 96-byte delivery proof produced by the server's `build_delivery_proof`.
///
/// # Layout
/// ```text
/// bytes  0..32 — SHA-256(seq_le || recipient_key || timestamp_ms_le)
/// bytes 32..96 — Ed25519 signature over those 32 bytes
/// ```
///
/// Returns `Ok(true)` when the proof is structurally valid and the signature verifies,
/// `Ok(false)` when the proof length is wrong (graceful degradation for old servers),
/// or `Err` when the signature is structurally invalid / verification fails.
pub fn verify_delivery_proof(
    server_pubkey: &[u8; 32],
    proof: &[u8],
) -> Result<bool, crate::error::CoreError> {
    if proof.len() != 96 {
        return Ok(false);
    }
    let hash: [u8; 32] = proof[..32].try_into().expect("slice is 32 bytes");
    let sig: [u8; 64] = proof[32..96].try_into().expect("slice is 64 bytes");
    IdentityKeypair::verify_raw(server_pubkey, &hash, &sig)?;
    Ok(true)
}

impl Serialize for IdentityKeypair {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_bytes(&self.seed[..])
    }
}

impl<'de> Deserialize<'de> for IdentityKeypair {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let bytes: Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
        let seed: [u8; 32] = bytes
            .as_slice()
            .try_into()
            .map_err(|_| serde::de::Error::custom("identity seed must be 32 bytes"))?;
        Ok(IdentityKeypair::from_seed(seed))
    }
}

impl std::fmt::Debug for IdentityKeypair {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let fp = self.fingerprint();
        f.debug_struct("IdentityKeypair")
            .field(
                "fingerprint",
                &format!("{:02x}{:02x}{:02x}{:02x}…", fp[0], fp[1], fp[2], fp[3]),
            )
            .finish_non_exhaustive()
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod proof_tests {
    use super::*;
    use sha2::{Digest, Sha256};

    fn make_proof(kp: &IdentityKeypair, seq: u64, recipient_key: &[u8], timestamp_ms: u64) -> Vec<u8> {
        let mut hasher = Sha256::new();
        hasher.update(seq.to_le_bytes());
        hasher.update(recipient_key);
        hasher.update(timestamp_ms.to_le_bytes());
        let hash: [u8; 32] = hasher.finalize().into();
        let sig = kp.sign_raw(&hash);
        let mut proof = vec![0u8; 96];
        proof[..32].copy_from_slice(&hash);
        proof[32..].copy_from_slice(&sig);
        proof
    }

    #[test]
    fn verify_valid_proof() {
        let kp = IdentityKeypair::generate();
        let pk = kp.public_key_bytes();
        let rk = [0xabu8; 32];
        let proof = make_proof(&kp, 42, &rk, 1_700_000_000_000);
        assert!(verify_delivery_proof(&pk, &proof).unwrap());
    }

    #[test]
    fn reject_wrong_length() {
        let kp = IdentityKeypair::generate();
        let pk = kp.public_key_bytes();
        assert!(!verify_delivery_proof(&pk, &[0u8; 64]).unwrap());
        assert!(!verify_delivery_proof(&pk, &[]).unwrap());
        assert!(!verify_delivery_proof(&pk, &[0u8; 97]).unwrap());
    }

    #[test]
    fn reject_tampered_hash() {
        let kp = IdentityKeypair::generate();
        let pk = kp.public_key_bytes();
        let rk = [0x01u8; 32];
        let mut proof = make_proof(&kp, 1, &rk, 999);
        proof[0] ^= 0xff; // corrupt the hash bytes
        assert!(verify_delivery_proof(&pk, &proof).is_err());
    }

    #[test]
    fn reject_wrong_pubkey() {
        let kp = IdentityKeypair::generate();
        let other = IdentityKeypair::generate();
        let pk = other.public_key_bytes();
        let rk = [0x02u8; 32];
        let proof = make_proof(&kp, 5, &rk, 0);
        assert!(verify_delivery_proof(&pk, &proof).is_err());
    }
}
