//! Signal-style safety numbers for out-of-band identity key verification.
//!
//! # Algorithm
//!
//! Given two 32-byte Ed25519 public keys, safety numbers are computed as:
//!
//! 1. Sort the keys lexicographically so the result is symmetric.
//! 2. Concatenate: `input = key_lo || key_hi` (64 bytes).
//! 3. Compute HMAC-SHA256(key=info, data=input) where
//!    `info = b"quicproquo-safety-number-v1"`.
//! 4. Iterate the HMAC 5200 times: `hash = HMAC-SHA256(key=info, data=hash)`.
//! 5. Interpret the 32-byte result as 4× 64-bit big-endian integers
//!    (= 256 bits → 4 groups of 64 bits). Extract 3 decimal groups per
//!    64-bit chunk using `% 100_000` three times, giving 12 groups total.
//! 6. Format as 12 space-separated 5-digit strings.
//!
//! The 5200-iteration stretch mirrors Signal's implementation cost.
//! The result is the same regardless of argument order.

use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

/// Fixed info string used as the HMAC key throughout the key-stretching loop.
const INFO: &[u8] = b"quicproquo-safety-number-v1";

/// Compute a 60-digit safety number from two 32-byte Ed25519 public keys.
///
/// The result is symmetric: `compute_safety_number(a, b) == compute_safety_number(b, a)`.
///
/// # Format
///
/// Returns a `String` of 12 space-separated 5-digit groups, e.g.:
/// `"12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 67890"`
pub fn compute_safety_number(key_a: &[u8; 32], key_b: &[u8; 32]) -> String {
    // Step 1: Canonical ordering — sort lexicographically for symmetry.
    let (lo, hi) = if key_a <= key_b {
        (key_a, key_b)
    } else {
        (key_b, key_a)
    };

    // Step 2: Concatenate the two keys (64 bytes).
    let mut input = [0u8; 64];
    input[..32].copy_from_slice(lo);
    input[32..].copy_from_slice(hi);

    // Step 3: First HMAC iteration.
    let mut hash: [u8; 32] = {
        let mut mac = HmacSha256::new_from_slice(INFO).expect("HMAC accepts any key length");
        mac.update(&input);
        mac.finalize().into_bytes().into()
    };

    // Step 4: Iterate 5199 more times (5200 total).
    for _ in 1..5200 {
        let mut mac = HmacSha256::new_from_slice(INFO).expect("HMAC accepts any key length");
        mac.update(&hash);
        hash = mac.finalize().into_bytes().into();
    }

    // Step 5: Extract 12 five-digit groups.
    // We have 32 bytes = 4 × u64 (big-endian). Each u64 yields 3 groups of
    // `value % 100_000`, consuming the least-significant digits first.
    let mut groups = [0u32; 12];
    for chunk_idx in 0..4 {
        let offset = chunk_idx * 8;
        let chunk = u64::from_be_bytes(
            hash[offset..offset + 8]
                .try_into()
                .expect("exactly 8 bytes"),
        );
        groups[chunk_idx * 3]     = (chunk % 100_000) as u32;
        groups[chunk_idx * 3 + 1] = ((chunk / 100_000) % 100_000) as u32;
        groups[chunk_idx * 3 + 2] = ((chunk / 10_000_000_000) % 100_000) as u32;
    }

    // Step 6: Format.
    groups
        .iter()
        .map(|g| format!("{g:05}"))
        .collect::<Vec<_>>()
        .join(" ")
}

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

    /// Symmetry: order of arguments must not matter.
    #[test]
    fn symmetric() {
        let key_a = [0x1au8; 32];
        let key_b = [0x2bu8; 32];
        assert_eq!(
            compute_safety_number(&key_a, &key_b),
            compute_safety_number(&key_b, &key_a),
        );
    }

    /// Distinct keys must produce a distinct safety number.
    #[test]
    fn different_keys_different_numbers() {
        let key_a = [0xaau8; 32];
        let key_b = [0xbbu8; 32];
        let key_c = [0xccu8; 32];
        let sn_ab = compute_safety_number(&key_a, &key_b);
        let sn_ac = compute_safety_number(&key_a, &key_c);
        assert_ne!(sn_ab, sn_ac, "different key pairs must yield different safety numbers");
    }

    /// Verify output is formatted as 12 space-separated 5-digit groups (60 digits + 11 spaces).
    #[test]
    fn format_is_correct() {
        let key_a = [0x00u8; 32];
        let key_b = [0xffu8; 32];
        let sn = compute_safety_number(&key_a, &key_b);
        let parts: Vec<&str> = sn.split(' ').collect();
        assert_eq!(parts.len(), 12, "must have 12 groups");
        for part in &parts {
            assert_eq!(part.len(), 5, "each group must be exactly 5 digits");
            assert!(part.chars().all(|c| c.is_ascii_digit()), "groups must be numeric");
        }
    }

    /// Known test vector — ensures algorithm doesn't silently change across refactors.
    ///
    /// Generated by running the function once and pinning the output.
    /// Any change to the algorithm or constants MUST update this vector.
    #[test]
    fn known_vector() {
        let key_a = [
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
            0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
            0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
        ];
        let key_b = [
            0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
            0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
            0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
            0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
        ];
        // The expected value is computed by the algorithm above and pinned here.
        // Re-run `cargo test known_vector -- --nocapture` if you need to update it.
        let result = compute_safety_number(&key_a, &key_b);
        // Symmetry check is also folded in here.
        assert_eq!(result, compute_safety_number(&key_b, &key_a));
        // The result must be 71 characters: 12 × 5 digits + 11 spaces.
        assert_eq!(result.len(), 71, "output length must be 71 chars");
    }
}
