//! Server-side CAPTCHA challenge generation, storage, and verification.
//!
//! Keys
//! ────
//! `captcha:{captcha_id}` → expected answer string, TTL 5 min, single-use (GETDEL on verify)

use rand::Rng;
use redis::AsyncCommands;
use crate::RedisPool;

const CAPTCHA_TTL_SECS: u64 = 300; // 5 minutes

/// A freshly generated challenge, ready to be persisted and returned to the client.
pub struct Challenge {
    pub captcha_id: String,
    pub challenge: String,
    /// Expected answer — never sent to the client, only stored server-side.
    pub answer: String,
}

/// Generate a small arithmetic challenge (e.g. "7 + 12 = ?") with a random id.
pub fn generate() -> Challenge {
    let mut rng = rand::thread_rng();
    let a: i32 = rng.gen_range(1..=20);
    let b: i32 = rng.gen_range(1..=20);
    // Randomly pick + or - (subtraction always ordered so the answer is non-negative).
    let (challenge, answer) = if rng.gen_bool(0.5) {
        (format!("{a} + {b} = ?"), a + b)
    } else {
        let (hi, lo) = if a >= b { (a, b) } else { (b, a) };
        (format!("{hi} - {lo} = ?"), hi - lo)
    };

    Challenge {
        captcha_id: uuid::Uuid::new_v4().to_string(),
        challenge,
        answer: answer.to_string(),
    }
}

/// Store the expected answer for a captcha_id. TTL 5 min.
pub async fn set(redis: &mut RedisPool, captcha_id: &str, answer: &str) -> Result<(), redis::RedisError> {
    let key = format!("captcha:{captcha_id}");
    redis.set_ex::<_, _, ()>(key, answer, CAPTCHA_TTL_SECS).await
}

/// Atomically fetch and delete the expected answer for a captcha_id (single-use).
/// Returns `None` if the id doesn't exist or has expired.
pub async fn consume(redis: &mut RedisPool, captcha_id: &str) -> Result<Option<String>, redis::RedisError> {
    let key = format!("captcha:{captcha_id}");
    redis.get_del(key).await
}

/// Verify (and consume, single-use, regardless of outcome) a client-submitted answer.
/// Returns `true` only if the captcha_id existed, hadn't expired, and the answer matched.
pub async fn verify(redis: &mut RedisPool, captcha_id: &str, submitted_answer: &str) -> bool {
    match consume(redis, captcha_id).await {
        Ok(Some(expected)) => expected.trim().eq_ignore_ascii_case(submitted_answer.trim()),
        _ => false,
    }
}
