//! OTP storage and rate-limiting.
//!
//! Keys
//! ────
//! `otp:code:{6-digit-code}` → user_id string, TTL 15 min
//! `otp:resend:{user_id}`    → resend attempt counter, TTL 1 hour (max 3)

use redis::AsyncCommands;
use crate::RedisPool;

const OTP_TTL_SECS: u64 = 900;           // 15 minutes
const RESEND_WINDOW_SECS: i64 = 3_600;   // 1 hour
const RESEND_MAX: i64 = 3;

// ── Store / verify ────────────────────────────────────────────────────────────

/// Store OTP code keyed by the code itself → user_id. TTL 15 min.
/// Also stores otp:plain:{user_id} → code for dev-test readability.
pub async fn set(redis: &mut RedisPool, code: &str, user_id: &str) -> Result<(), redis::RedisError> {
    let key = format!("otp:code:{code}");
    let plain_key = format!("otp:plain:{user_id}");
    // Store both: code→user_id (for verification) and plain→code (for dev debugging)
    redis.set_ex::<_, _, ()>(&plain_key, code, OTP_TTL_SECS).await?;
    redis.set_ex::<_, _, ()>(key, user_id, OTP_TTL_SECS).await
}

/// Atomically fetch the user_id for this OTP and delete it (single-use).
/// Returns `None` if the code doesn't exist or has expired.
pub async fn consume(redis: &mut RedisPool, code: &str) -> Result<Option<String>, redis::RedisError> {
    let key = format!("otp:code:{code}");
    // GETDEL: atomic get + delete (Redis ≥ 6.2)
    redis.get_del(key).await
}

// ── Resend rate limit ─────────────────────────────────────────────────────────

/// Returns `true` if the user is allowed to request another OTP (< 3 in last hour).
pub async fn resend_allowed(redis: &mut RedisPool, user_id: &str) -> Result<bool, redis::RedisError> {
    let key = format!("otp:resend:{user_id}");
    let count: i64 = redis.get(&key).await.unwrap_or(0);
    Ok(count < RESEND_MAX)
}

/// Increment the resend counter. Call after sending the OTP.
pub async fn record_resend(redis: &mut RedisPool, user_id: &str) -> Result<(), redis::RedisError> {
    let key = format!("otp:resend:{user_id}");
    let count: i64 = redis.incr(&key, 1i64).await?;
    // Only set expiry on first increment so window is fixed from first request
    if count == 1 {
        redis.expire::<_, ()>(&key, RESEND_WINDOW_SECS).await?;
    }
    Ok(())
}
