//! Ephemeral-static ECMQV key exchange mode.
//!
//! This mode uses both ephemeral and static keys. It provides mutual
//! authentication (via static public keys from certificates) and forward
//! secrecy (via ephemeral keys). This is the full ECMQV protocol as specified
//! in NIST SP 800-56A and draft-sullivan-tls-ecmqv-01.

use rand_core::{CryptoRng, RngCore};

use crate::error::AgreementError;
use crate::key::{EphemeralKey, PublicKey, StaticKey};
use crate::key_exchange::{KeyExchange, SharedSecret, perform_agreement};
use crate::kdf::{KdfParams, SharedInfo};
use crate::params::CurveParameters;

/// Ephemeral-static ECMQV key exchange.
///
/// Both parties contribute ephemeral keys (for forward secrecy) and
/// static keys (for authentication). The static keys must be certified
/// and validated by the application.
///
/// This is the full ECMQV protocol, suitable for authenticated key exchange
/// in TLS-like environments.
///
/// # Example
/// ```
/// # use ecmqv_rs::{EphemeralStatic, KeyExchange, CurveParameters, KdfParams, SharedInfo, HashFunction, StaticKey};
/// # use rand::rngs::OsRng;
/// let mut rng = OsRng;
/// let params = CurveParameters::Curve25519;
///
/// // Load or generate static keys (in practice, these come from certificates)
/// let alice_static = StaticKey::from_seed(&[42u8; 32], &mut rng, params).unwrap();
/// let bob_static = StaticKey::from_seed(&[43u8; 32], &mut rng, params).unwrap();
/// let alice_static_pub = alice_static.public();
/// let bob_static_pub = bob_static.public();
///
/// // Generate ephemeral keys
/// let (alice_eph, alice_eph_pub) = EphemeralStatic::generate_ephemeral(&mut rng, &params).unwrap();
/// let (bob_eph, bob_eph_pub) = EphemeralStatic::generate_ephemeral(&mut rng, &params).unwrap();
///
/// // Create exchangers
/// let alice = EphemeralStatic::new(alice_eph, alice_static, params).unwrap();
/// let bob = EphemeralStatic::new(bob_eph, bob_static, params).unwrap();
///
/// let info = SharedInfo {
///     id_a: b"alice",
///     id_b: b"bob",
///     other_data: b"session123",
/// };
/// let kdf = KdfParams {
///     hash_fn: HashFunction::Sha256,
///     output_len: 32,
///     label: None,
///     salt: None,
/// };
///
/// let alice_secret = alice.agree(&bob_eph_pub, Some(&bob_static_pub), &info, kdf.clone()).unwrap();
/// let bob_secret = bob.agree(&alice_eph_pub, Some(&alice_static_pub), &info, kdf).unwrap();
/// assert_eq!(alice_secret.as_slice(), bob_secret.as_slice());
/// ```
pub struct EphemeralStatic {
    own_ephemeral: EphemeralKey,
    own_static: StaticKey,
    params: CurveParameters,
}

impl EphemeralStatic {
    /// Creates a new `EphemeralStatic` instance from existing ephemeral and static keys.
    ///
    /// It is the caller's responsibility to ensure the keys belong to the same curve.
    ///
    /// # Errors
    /// Returns `ParameterMismatch` if the ephemeral or static key does not match the given curve.
    pub fn new(
        own_ephemeral: EphemeralKey,
        own_static: StaticKey,
        params: CurveParameters,
    ) -> Result<Self, AgreementError> {
        // Verify curve consistency
        if own_ephemeral.curve_id() != params.curve_id() {
            return Err(AgreementError::ParameterMismatch);
        }
        if own_static.curve_id() != params.curve_id() {
            return Err(AgreementError::ParameterMismatch);
        }
        Ok(Self {
            own_ephemeral,
            own_static,
            params,
        })
    }

    /// Creates a new `EphemeralStatic` instance, generating a fresh ephemeral key
    /// while using an existing static key.
    ///
    /// This is a convenience constructor when the static key is already loaded.
    ///
    /// # Errors
    /// Returns `UnsupportedCurve` if the curve is not enabled.
    pub fn with_existing_static<R: RngCore + CryptoRng>(
        rng: &mut R,
        own_static: StaticKey,
        params: CurveParameters,
    ) -> Result<Self, AgreementError> {
        let own_ephemeral = EphemeralKey::generate(rng, params)?;
        Self::new(own_ephemeral, own_static, params)
    }

    /// Generates both ephemeral and static keys from seeds (testing only).
    ///
    /// In production, static keys should be loaded from secure storage.
    ///
    /// # Parameters
    /// - `rng`: cryptographically secure RNG (used if seed is invalid).
    /// - `params`: curve parameters.
    /// - `static_seed`: 32-byte seed for the static key.
    /// - `_ephemeral_seed`: currently ignored; ephemeral keys are generated randomly.
    ///
    /// # Errors
    /// Returns `UnsupportedCurve` if the curve is not enabled.
    #[cfg(test)]
    pub fn generate_from_seed<R: RngCore + CryptoRng>(
        rng: &mut R,
        params: CurveParameters,
        static_seed: &[u8; 32],
        _ephemeral_seed: Option<&[u8; 32]>, // currently ignored
    ) -> Result<Self, AgreementError> {
        let own_static = StaticKey::from_seed(static_seed, rng, params)?;
        let own_ephemeral = EphemeralKey::generate(rng, params)?;
        Self::new(own_ephemeral, own_static, params)
    }
}

impl KeyExchange for EphemeralStatic {
    type SharedSecret = SharedSecret;

    /// Generates a fresh ephemeral key pair for the specified curve.
    ///
    /// # Errors
    /// Returns `UnsupportedCurve` if the curve is not enabled.
    fn generate_ephemeral<R: RngCore + CryptoRng>(
        rng: &mut R,
        params: &CurveParameters,
    ) -> Result<(EphemeralKey, PublicKey), AgreementError> {
        let ephemeral = EphemeralKey::generate(rng, *params)?;
        let public = ephemeral.public();
        Ok((ephemeral, public))
    }

    /// Performs ephemeral-static key agreement with a peer.
    ///
    /// The peer's static public key is required for authentication.
    ///
    /// # Errors
    /// - `ParameterMismatch` if the peer's static key is missing or keys mismatch.
    /// - `InvalidPublicKey` if the peer's static key fails validation.
    /// - Other errors as documented in `KeyExchange::agree`.
    fn agree(
        &self,
        peer_ephemeral: &PublicKey,
        peer_static: Option<&PublicKey>,
        shared_info: &SharedInfo,
        kdf_params: KdfParams,
    ) -> Result<Self::SharedSecret, AgreementError> {
        // Require peer static public key for authentication
        let peer_static = peer_static.ok_or(AgreementError::ParameterMismatch)?;

        // Validate peer's static public key (ephemeral validation is done inside perform_agreement)
        peer_static.validate()?;

        // Use the central perform_agreement helper
        perform_agreement(
            &self.own_ephemeral,
            Some(&self.own_static),
            peer_ephemeral,
            Some(peer_static),
            &self.params,
            shared_info,
            kdf_params,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::rngs::OsRng;
    use crate::kdf::HashFunction;

    #[test]
    fn test_ephemeral_static_roundtrip() {
        let params = CurveParameters::Curve25519;
        let mut rng = OsRng;

        let alice_static_seed = [42u8; 32];
        let bob_static_seed = [43u8; 32];
        let alice_static = StaticKey::from_seed(&alice_static_seed, &mut rng, params).unwrap();
        let bob_static = StaticKey::from_seed(&bob_static_seed, &mut rng, params).unwrap();

        // Extract public keys before moving static keys into the struct
        let alice_static_pub = alice_static.public();
        let bob_static_pub = bob_static.public();

        let (alice_eph, alice_eph_pub) =
            EphemeralStatic::generate_ephemeral(&mut rng, &params).unwrap();
        let (bob_eph, bob_eph_pub) =
            EphemeralStatic::generate_ephemeral(&mut rng, &params).unwrap();

        let alice = EphemeralStatic::new(alice_eph, alice_static, params).unwrap();
        let bob = EphemeralStatic::new(bob_eph, bob_static, params).unwrap();

        let shared_info = SharedInfo {
            id_a: b"alice",
            id_b: b"bob",
            other_data: b"test",
        };
        let kdf_params = KdfParams {
            hash_fn: HashFunction::Sha256,
            output_len: 32,
            label: None,
            salt: None,
        };

        let alice_secret = alice
            .agree(&bob_eph_pub, Some(&bob_static_pub), &shared_info, kdf_params.clone())
            .unwrap();
        let bob_secret = bob
            .agree(&alice_eph_pub, Some(&alice_static_pub), &shared_info, kdf_params)
            .unwrap();

        assert_eq!(alice_secret.as_slice(), bob_secret.as_slice());
    }

    #[test]
    fn test_missing_peer_static_fails() {
        let params = CurveParameters::Curve25519;
        let mut rng = OsRng;
        let eph = EphemeralKey::generate(&mut rng, params).unwrap();
        let static_key = StaticKey::from_seed(&[0u8; 32], &mut rng, params).unwrap();
        let exchanger = EphemeralStatic::new(eph, static_key, params).unwrap();
        let (_, peer_pub) = EphemeralStatic::generate_ephemeral(&mut rng, &params).unwrap();
        let shared_info = SharedInfo {
            id_a: b"a",
            id_b: b"b",
            other_data: b"",
        };
        let kdf_params = KdfParams {
            hash_fn: HashFunction::Sha256,
            output_len: 32,
            label: None,
            salt: None,
        };
        let result = exchanger.agree(&peer_pub, None, &shared_info, kdf_params);
        assert!(matches!(result, Err(AgreementError::ParameterMismatch)));
    }
}