//! Client-side cryptographic handshake.
//!
//! Performs the caller role of the WarzonePhone key exchange:
//! send `CallOffer` → recv `CallAnswer` → derive shared `CryptoSession`.

use wzp_crypto::{CryptoSession, KeyExchange, WarzoneKeyExchange};
use wzp_proto::{
    HangupReason, MediaTransport, QualityProfile, SignalMessage, default_signal_version,
};

/// Errors that can occur during the client-side cryptographic handshake.
#[derive(Debug)]
pub enum HandshakeError {
    ConnectionClosed,
    ProtocolVersionMismatch { server_supported: Vec<u8> },
    UnexpectedSignal(&'static str),
    SignatureVerificationFailed,
    KeyDerivation(String),
    Transport(wzp_proto::TransportError),
}

impl std::fmt::Display for HandshakeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ConnectionClosed => write!(f, "connection closed before receiving CallAnswer"),
            Self::ProtocolVersionMismatch { server_supported } => {
                write!(
                    f,
                    "protocol version mismatch: server supports {server_supported:?}"
                )
            }
            Self::UnexpectedSignal(expected) => write!(f, "expected CallAnswer, got {expected}"),
            Self::SignatureVerificationFailed => write!(f, "callee signature verification failed"),
            Self::KeyDerivation(msg) => write!(f, "key derivation failed: {msg}"),
            Self::Transport(e) => write!(f, "transport error: {e}"),
        }
    }
}

impl std::error::Error for HandshakeError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Transport(e) => Some(e),
            _ => None,
        }
    }
}

impl From<wzp_proto::TransportError> for HandshakeError {
    fn from(e: wzp_proto::TransportError) -> Self {
        Self::Transport(e)
    }
}

/// Perform the client (caller) side of the cryptographic handshake.
///
/// 1. Derive identity from `seed`
/// 2. Generate ephemeral X25519 keypair
/// 3. Sign `(ephemeral_pub || "call-offer")` with identity key
/// 4. Send `CallOffer` with identity_pub, ephemeral_pub, signature
/// 5. Receive `CallAnswer`, verify callee signature
/// 6. Derive shared ChaCha20-Poly1305 session
pub async fn perform_handshake(
    transport: &dyn MediaTransport,
    seed: &[u8; 32],
    alias: Option<&str>,
) -> Result<Box<dyn CryptoSession>, HandshakeError> {
    // 1. Create key exchange from identity seed
    let mut kx = WarzoneKeyExchange::from_identity_seed(seed);
    let identity_pub = kx.identity_public_key();

    // 2. Generate ephemeral key
    let ephemeral_pub = kx.generate_ephemeral();

    // 3. Sign (ephemeral_pub || "call-offer")
    let mut sign_data = Vec::with_capacity(32 + 10);
    sign_data.extend_from_slice(&ephemeral_pub);
    sign_data.extend_from_slice(b"call-offer");
    let signature = kx.sign(&sign_data);

    // 4. Send CallOffer
    let offer = SignalMessage::CallOffer {
        version: default_signal_version(),
        identity_pub,
        ephemeral_pub,
        signature,
        supported_profiles: vec![
            QualityProfile::STUDIO_64K,
            QualityProfile::STUDIO_48K,
            QualityProfile::STUDIO_32K,
            QualityProfile::GOOD,
            QualityProfile::DEGRADED,
            QualityProfile::CATASTROPHIC,
        ],
        alias: alias.map(|s| s.to_string()),
        protocol_version: 2,
        supported_versions: vec![2],
    };
    transport
        .send_signal(&offer)
        .await
        .map_err(HandshakeError::Transport)?;

    // 5. Wait for CallAnswer
    let answer = transport
        .recv_signal()
        .await
        .map_err(HandshakeError::Transport)?
        .ok_or(HandshakeError::ConnectionClosed)?;

    let (callee_identity_pub, callee_ephemeral_pub, callee_signature, _chosen_profile) =
        match answer {
            SignalMessage::CallAnswer {
                identity_pub,
                ephemeral_pub,
                signature,
                chosen_profile,
                ..
            } => (identity_pub, ephemeral_pub, signature, chosen_profile),
            SignalMessage::Hangup {
                reason: HangupReason::ProtocolVersionMismatch { server_supported },
                ..
            } => {
                return Err(HandshakeError::ProtocolVersionMismatch { server_supported });
            }
            _ => {
                return Err(HandshakeError::UnexpectedSignal("CallAnswer"));
            }
        };

    // 6. Verify callee's signature over (ephemeral_pub || "call-answer")
    let mut verify_data = Vec::with_capacity(32 + 11);
    verify_data.extend_from_slice(&callee_ephemeral_pub);
    verify_data.extend_from_slice(b"call-answer");
    if !WarzoneKeyExchange::verify(&callee_identity_pub, &verify_data, &callee_signature) {
        return Err(HandshakeError::SignatureVerificationFailed);
    }

    // 7. Derive session
    let session = kx
        .derive_session(&callee_ephemeral_pub)
        .map_err(|e| HandshakeError::KeyDerivation(e.to_string()))?;

    Ok(session)
}

#[cfg(test)]
mod tests {
    use super::*;

    // Integration test lives in tests/ — unit-level coverage relies on wzp-crypto tests.
    #[test]
    fn sign_data_format() {
        let kx = WarzoneKeyExchange::from_identity_seed(&[0xAA; 32]);
        let eph = [0x11u8; 32];
        let mut data = Vec::new();
        data.extend_from_slice(&eph);
        data.extend_from_slice(b"call-offer");
        let sig = kx.sign(&data);
        assert!(WarzoneKeyExchange::verify(
            &kx.identity_public_key(),
            &data,
            &sig,
        ));
    }
}
