#![forbid(unsafe_code)]
#![allow(dead_code)]

//! Rate limiting for the lifecycle API server.
//!
//! This module provides:
//! - Token bucket algorithm for rate limiting
//! - Per-IP address rate limiting
//! - Configurable request rates and burst sizes
//! - HTTP 429 (Too Many Requests) responses
//! - Standard rate limit headers (X-RateLimit-*)
//!
//! Rate limiting is applied globally or per-IP based on configuration.

use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use thiserror::Error;

/// Rate limiting errors
#[allow(dead_code)]
#[derive(Error, Debug, Clone)]
pub enum RateLimitError {
    /// Request rate limit has been exceeded for this IP
    #[error("Rate limit exceeded")]
    LimitExceeded,
}

/// Per-IP rate limit state
#[derive(Debug, Clone)]
struct IpRateLimitState {
    /// Remaining tokens in this second
    tokens: f64,

    /// Last time tokens were updated
    last_update: Instant,
}

impl IpRateLimitState {
    /// Create a new rate limit state with full burst capacity
    fn new(burst_size: f64) -> Self {
        Self {
            tokens: burst_size,
            last_update: Instant::now(),
        }
    }

    /// Update tokens based on elapsed time
    /// Returns the current number of available tokens
    fn refill(&mut self, rate: f64) -> f64 {
        let now = Instant::now();
        let elapsed = now.duration_since(self.last_update).as_secs_f64();
        self.last_update = now;

        // Add tokens based on rate (tokens per second)
        self.tokens += elapsed * rate;

        // Cap at burst size
        self.tokens
    }
}

/// Rate limiter for the lifecycle API
///
/// Uses a token bucket algorithm to track per-IP rate limits.
/// Each IP address has its own token bucket.
#[derive(Clone, Debug)]
pub struct RateLimiter {
    /// Requests per second allowed
    requests_per_second: f64,

    /// Maximum burst size (tokens in bucket)
    burst_size: f64,

    /// Per-IP rate limit states
    ip_states: Arc<Mutex<HashMap<IpAddr, IpRateLimitState>>>,

    /// Cleanup interval (remove old states)
    #[allow(dead_code)]
    cleanup_interval: Duration,

    /// Last cleanup time
    #[allow(dead_code)]
    last_cleanup: Arc<Mutex<Instant>>,
}

impl RateLimiter {
    /// Create a new rate limiter
    ///
    /// # Arguments
    ///
    /// * `requests_per_second` - Number of requests allowed per second (can be fractional)
    /// * `burst_size` - Maximum burst capacity (typically 1-10x the request rate)
    ///
    /// # Example
    ///
    /// ```ignore
    /// // Allow 100 requests per second with burst of 200
    /// let limiter = RateLimiter::new(100.0, 200.0);
    /// ```
    pub fn new(requests_per_second: f64, burst_size: f64) -> Self {
        let effective_burst = if burst_size > 0.0 {
            burst_size
        } else {
            requests_per_second.max(1.0)
        };

        Self {
            requests_per_second,
            burst_size: effective_burst,
            ip_states: Arc::new(Mutex::new(HashMap::new())),
            cleanup_interval: Duration::from_secs(300), // Cleanup every 5 minutes
            last_cleanup: Arc::new(Mutex::new(Instant::now())),
        }
    }

    /// Check if a request from an IP is allowed
    ///
    /// Returns:
    /// - `Ok((remaining_tokens, reset_at))` if request is allowed
    /// - `Err(RateLimitError::LimitExceeded)` if rate limit is exceeded
    pub fn check_limit(&self, ip: IpAddr) -> Result<(f64, Instant), RateLimitError> {
        let mut states = self.ip_states.lock().unwrap();

        // Get or create state for this IP
        let state = states
            .entry(ip)
            .or_insert_with(|| IpRateLimitState::new(self.burst_size));

        // Refill tokens
        let available = state.refill(self.requests_per_second);

        // Check if we have tokens available
        if available >= 1.0 {
            // Consume one token
            state.tokens -= 1.0;
            let remaining = state.tokens.floor() as u32 as f64;
            let reset = state.last_update + Duration::from_secs(1);
            Ok((remaining, reset))
        } else {
            Err(RateLimitError::LimitExceeded)
        }
    }

    /// Get statistics for an IP address
    #[allow(dead_code)]
    pub fn get_stats(&self, ip: IpAddr) -> Option<(f64, Instant)> {
        let mut states = self.ip_states.lock().unwrap();

        states.get_mut(&ip).map(|state| {
            let available = state.refill(self.requests_per_second);
            let reset = state.last_update + Duration::from_secs(1);
            (available, reset)
        })
    }

    /// Clean up old entries to prevent memory leaks
    /// Removes entries that haven't been accessed in over 1 hour
    #[allow(dead_code)]
    pub fn cleanup_old_entries(&self) {
        let mut last_cleanup = self.last_cleanup.lock().unwrap();

        if last_cleanup.elapsed() < self.cleanup_interval {
            return; // Not time to cleanup yet
        }

        *last_cleanup = Instant::now();

        let mut states = self.ip_states.lock().unwrap();
        let now = Instant::now();
        let max_age = Duration::from_secs(3600); // 1 hour

        states.retain(|_, state| {
            // Keep entries that have been updated recently
            now.duration_since(state.last_update) < max_age
        });
    }

    /// Reset rate limit for an IP (useful for testing)
    #[cfg(test)]
    pub fn reset(&self, ip: IpAddr) {
        let mut states = self.ip_states.lock().unwrap();
        states.remove(&ip);
    }

    /// Get current requests per second
    pub fn requests_per_second(&self) -> f64 {
        self.requests_per_second
    }

    /// Get current burst size
    pub fn burst_size(&self) -> f64 {
        self.burst_size
    }
}

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

    fn test_ip() -> IpAddr {
        IpAddr::from_str("192.168.1.1").unwrap()
    }

    fn test_ip2() -> IpAddr {
        IpAddr::from_str("192.168.1.2").unwrap()
    }

    #[test]
    fn test_rate_limiter_creation() {
        let limiter = RateLimiter::new(10.0, 20.0);
        assert_eq!(limiter.requests_per_second(), 10.0);
        assert_eq!(limiter.burst_size(), 20.0);
    }

    #[test]
    fn test_first_request_allowed() {
        let limiter = RateLimiter::new(10.0, 20.0);
        let ip = test_ip();

        let result = limiter.check_limit(ip);
        assert!(result.is_ok());
        let (remaining, _) = result.unwrap();
        assert!(remaining >= 19.0); // Burst was 20, consumed 1
    }

    #[test]
    fn test_burst_exhaustion() {
        let limiter = RateLimiter::new(10.0, 3.0); // Small burst for testing
        let ip = test_ip();

        // First 3 requests should succeed (burst size)
        assert!(limiter.check_limit(ip).is_ok());
        assert!(limiter.check_limit(ip).is_ok());
        assert!(limiter.check_limit(ip).is_ok());

        // Fourth request should fail
        assert!(limiter.check_limit(ip).is_err());
    }

    #[test]
    fn test_token_refill() {
        let limiter = RateLimiter::new(10.0, 15.0);
        let ip = test_ip();

        // Exhaust burst
        for _ in 0..15 {
            let _ = limiter.check_limit(ip);
        }

        // Verify we're limited
        assert!(limiter.check_limit(ip).is_err());

        // After 0.1 seconds (100ms), we should have 1 more token
        std::thread::sleep(Duration::from_millis(101));

        // Should allow next request (10 requests/sec = 1 request per 100ms)
        let result = limiter.check_limit(ip);
        assert!(result.is_ok());
    }

    #[test]
    fn test_per_ip_isolation() {
        let limiter = RateLimiter::new(10.0, 1.0); // Only 1 token per IP
        let ip1 = test_ip();
        let ip2 = test_ip2();

        // IP1 exhausts its limit
        assert!(limiter.check_limit(ip1).is_ok());
        assert!(limiter.check_limit(ip1).is_err());

        // IP2 should still have tokens
        assert!(limiter.check_limit(ip2).is_ok());
    }

    #[test]
    fn test_get_stats() {
        let limiter = RateLimiter::new(10.0, 5.0);
        let ip = test_ip();

        // Initially no stats
        assert!(limiter.get_stats(ip).is_none());

        // After first request
        let _ = limiter.check_limit(ip);
        let stats = limiter.get_stats(ip);
        assert!(stats.is_some());
        let (remaining, _) = stats.unwrap();
        assert!(remaining >= 3.0); // 5 burst - 1 consumed - refill = ~4
    }

    #[test]
    fn test_cleanup_doesnt_remove_recent() {
        let limiter = RateLimiter::new(10.0, 5.0);
        let ip = test_ip();

        // Use the limiter
        let _ = limiter.check_limit(ip);

        // Cleanup shouldn't remove recent entries
        limiter.cleanup_old_entries();

        // Should still have stats
        assert!(limiter.get_stats(ip).is_some());
    }

    #[test]
    fn test_minimum_burst_size() {
        // Burst size should be respected as provided
        let limiter = RateLimiter::new(10.0, 5.0);
        assert_eq!(limiter.burst_size(), 5.0); // Exact burst size provided
    }

    #[test]
    fn test_fractional_rate() {
        let limiter = RateLimiter::new(0.5, 1.0); // 1 request every 2 seconds
        let ip = test_ip();

        // First request should succeed
        assert!(limiter.check_limit(ip).is_ok());

        // Second request within 1 second should fail
        assert!(limiter.check_limit(ip).is_err());

        // After ~2+ seconds, should allow another
        std::thread::sleep(Duration::from_millis(2100));
        assert!(limiter.check_limit(ip).is_ok());
    }

    #[test]
    fn test_rate_limit_error_display() {
        let err = RateLimitError::LimitExceeded;
        assert_eq!(err.to_string(), "Rate limit exceeded");
    }

    #[test]
    fn test_reset_ip() {
        let limiter = RateLimiter::new(10.0, 5.0);
        let ip = test_ip();

        // Use tokens
        let _ = limiter.check_limit(ip);
        assert!(limiter.get_stats(ip).is_some());

        // Reset
        limiter.reset(ip);

        // Stats should be gone
        assert!(limiter.get_stats(ip).is_none());

        // Next request should succeed with fresh burst
        assert!(limiter.check_limit(ip).is_ok());
    }
}
