//! OTP (One-Time Password) Token Management for Enrollment
//!
//! Generates time-limited cryptographically secure tokens for node enrollment.
//! Tokens are stored in RAM and automatically expire.

use chrono::{DateTime, Duration, Utc};
use rand::Rng;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use thiserror::Error;

#[allow(dead_code)]
#[derive(Error, Debug)]
pub enum TokenError {
    #[error("token expired")]
    Expired,
    #[error("token not found")]
    NotFound,
    #[error("invalid token format")]
    InvalidFormat,
}

/// OTP Token with expiration
#[derive(Debug, Clone)]
struct Token {
    value: String,
    created_at: DateTime<Utc>,
    expires_at: DateTime<Utc>,
    used: bool,
}

/// Thread-safe OTP token manager
pub struct TokenManager {
    tokens: Arc<Mutex<HashMap<String, Token>>>,
}

impl TokenManager {
    pub fn new() -> Self {
        Self {
            tokens: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Generate a new OTP token with specified expiry duration
    pub fn generate(&self, expiry_minutes: i64) -> String {
        let token_value = generate_secure_token(10);
        let now = Utc::now();
        let expires_at = now + Duration::minutes(expiry_minutes);

        let token = Token {
            value: token_value.clone(),
            created_at: now,
            expires_at,
            used: false,
        };

        let mut tokens = self.tokens.lock().unwrap();
        tokens.insert(token_value.clone(), token);

        tracing::info!(
            "Generated OTP token (expires in {} minutes): {}",
            expiry_minutes,
            mask_token(&token_value)
        );

        token_value
    }

    /// Validate and consume a token
    pub fn validate(&self, token_value: &str) -> Result<(), TokenError> {
        let mut tokens = self.tokens.lock().unwrap();

        let token = tokens
            .get_mut(token_value)
            .ok_or(TokenError::NotFound)?;

        // Check expiration
        if Utc::now() > token.expires_at {
            tokens.remove(token_value);
            tracing::warn!("Attempted to use expired token: {}", mask_token(token_value));
            return Err(TokenError::Expired);
        }

        // Check if already used
        if token.used {
            tracing::warn!("Attempted to reuse token: {}", mask_token(token_value));
            return Err(TokenError::NotFound);
        }

        // Mark as used and remove immediately
        token.used = true;
        tokens.remove(token_value);

        tracing::info!("OTP token validated and consumed: {}", mask_token(token_value));
        Ok(())
    }

    /// Cleanup expired tokens (should be called periodically)
    pub fn cleanup_expired(&self) {
        let mut tokens = self.tokens.lock().unwrap();
        let now = Utc::now();

        let expired: Vec<String> = tokens
            .iter()
            .filter(|(_, token)| now > token.expires_at)
            .map(|(key, _)| key.clone())
            .collect();

        for key in &expired {
            tokens.remove(key);
        }

        if !expired.is_empty() {
            tracing::info!("Cleaned up {} expired tokens", expired.len());
        }
    }

    /// Get count of active tokens
    pub fn active_count(&self) -> usize {
        let tokens = self.tokens.lock().unwrap();
        tokens.len()
    }
}

impl Default for TokenManager {
    fn default() -> Self {
        Self::new()
    }
}

/// Generate cryptographically secure random token
fn generate_secure_token(length: usize) -> String {
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    let mut rng = rand::thread_rng();

    (0..length)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect()
}

/// Mask token for logging (show first 2 and last 2 chars)
fn mask_token(token: &str) -> String {
    if token.len() <= 4 {
        return "****".to_string();
    }
    format!("{}****{}", &token[..2], &token[token.len() - 2..])
}

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

    #[test]
    fn test_token_generation() {
        let manager = TokenManager::new();
        let token = manager.generate(3);
        assert_eq!(token.len(), 10);
        assert!(token.chars().all(|c| c.is_ascii_alphanumeric()));
    }

    #[test]
    fn test_token_validation() {
        let manager = TokenManager::new();
        let token = manager.generate(3);

        // First use should succeed
        assert!(manager.validate(&token).is_ok());

        // Second use should fail (token consumed)
        assert!(matches!(manager.validate(&token), Err(TokenError::NotFound)));
    }

    #[test]
    fn test_token_expiration() {
        let manager = TokenManager::new();
        let token = manager.generate(0); // Expires immediately

        std::thread::sleep(std::time::Duration::from_millis(100));
        assert!(matches!(manager.validate(&token), Err(TokenError::Expired)));
    }

    #[test]
    fn test_cleanup() {
        let manager = TokenManager::new();
        manager.generate(0);
        manager.generate(0);

        std::thread::sleep(std::time::Duration::from_millis(100));
        manager.cleanup_expired();

        assert_eq!(manager.active_count(), 0);
    }
}
