//! Slack request signature verification.

use std::fmt::Write as FmtWrite;

use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
use subtle::ConstantTimeEq;

type HmacSha256 = Hmac<Sha256>;

/// Verify the Slack request signature using HMAC-SHA256.
///
/// Slack sends `X-Slack-Signature` and `X-Slack-Request-Timestamp` headers.
/// We compute `HMAC-SHA256(signing_secret, "v0:{timestamp}:{body}")` and
/// compare it to the provided signature.
///
/// This uses a constant-time comparison to prevent timing attacks.
pub(super) fn verify_slack_signature(
    signing_secret: &str,
    timestamp: &str,
    body: &[u8],
    signature: &str,
) -> bool {
    let mut mac = HmacSha256::new_from_slice(signing_secret.as_bytes())
        .expect("HMAC can take key of any size");
    mac.update(b"v0:");
    mac.update(timestamp.as_bytes());
    mac.update(b":");
    mac.update(body);
    let result = mac.finalize().into_bytes();

    // Format as "v0={hex}"
    let mut expected = String::from("v0=");
    for byte in &result {
        write!(expected, "{byte:02x}").unwrap();
    }

    // Constant-time comparison to prevent timing attacks.
    expected.as_bytes().ct_eq(signature.as_bytes()).into()
}

// ── Tests ───────────────────────────────────────────────────────────────

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

    // ── Signature verification ──────────────────────────────────────────

    #[test]
    fn verify_signature_with_known_values() {
        // Test with a known good signature.
        let secret = "8f742231b10e8888abcd99yez67291";
        let timestamp = "1531420618";
        let body = b"token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c659f";

        let sig = compute_test_signature(secret, timestamp, body);

        assert!(verify_slack_signature(secret, timestamp, body, &sig));
    }

    #[test]
    fn verify_signature_rejects_bad_signature() {
        let secret = "test-secret";
        let timestamp = "1234567890";
        let body = b"test body";

        assert!(!verify_slack_signature(
            secret,
            timestamp,
            body,
            "v0=bad_signature_here"
        ));
    }

    #[test]
    fn verify_signature_rejects_wrong_secret() {
        let timestamp = "1234567890";
        let body = b"test body";

        let sig = compute_test_signature("correct-secret", timestamp, body);
        assert!(!verify_slack_signature(
            "wrong-secret",
            timestamp,
            body,
            &sig
        ));
    }

    /// Helper to compute a test signature using `Hmac<Sha256>`.
    fn compute_test_signature(secret: &str, timestamp: &str, body: &[u8]) -> String {
        use std::fmt::Write;

        let mut mac =
            HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size");
        mac.update(b"v0:");
        mac.update(timestamp.as_bytes());
        mac.update(b":");
        mac.update(body);
        let result = mac.finalize().into_bytes();

        let mut sig = String::from("v0=");
        for byte in &result {
            write!(sig, "{byte:02x}").unwrap();
        }
        sig
    }
}
