//! Backpressure Controller for managing write load
//!
//! Prevents system overload by monitoring queue levels and
//! applying appropriate throttling or rejection policies.

use std::sync::Arc;
use std::time::Duration;
use tokio::time::sleep;

use super::metrics::WriteMetrics;

/// Configuration for backpressure control
#[derive(Debug, Clone)]
pub struct BackpressureConfig {
    /// Queue fill ratio at which slowdown begins (0.0 - 1.0)
    pub queue_low_watermark: f64,
    
    /// Queue fill ratio at which severe slowdown applies (0.0 - 1.0)
    pub queue_high_watermark: f64,
    
    /// Queue fill ratio at which requests are rejected (0.0 - 1.0)
    pub reject_threshold: f64,
    
    /// Base slowdown duration (ms) at low watermark
    pub base_slowdown_ms: u64,
    
    /// Maximum slowdown duration (ms) at high watermark
    pub max_slowdown_ms: u64,
    
    /// Whether to use exponential backoff
    pub exponential_backoff: bool,
}

impl Default for BackpressureConfig {
    fn default() -> Self {
        Self {
            queue_low_watermark: 0.5,
            queue_high_watermark: 0.8,
            reject_threshold: 0.95,
            base_slowdown_ms: 1,
            max_slowdown_ms: 100,
            exponential_backoff: true,
        }
    }
}

impl BackpressureConfig {
    /// Create config that allows higher load before throttling
    pub fn permissive() -> Self {
        Self {
            queue_low_watermark: 0.7,
            queue_high_watermark: 0.9,
            reject_threshold: 0.98,
            base_slowdown_ms: 1,
            max_slowdown_ms: 50,
            exponential_backoff: false,
        }
    }
    
    /// Create config that throttles more aggressively
    pub fn strict() -> Self {
        Self {
            queue_low_watermark: 0.3,
            queue_high_watermark: 0.6,
            reject_threshold: 0.85,
            base_slowdown_ms: 5,
            max_slowdown_ms: 200,
            exponential_backoff: true,
        }
    }
}

/// Action to take based on backpressure evaluation
#[derive(Debug, Clone, PartialEq)]
pub enum BackpressureAction {
    /// Accept the request immediately
    Accept,
    
    /// Accept after a delay
    Slowdown(Duration),
    
    /// Reject the request
    Reject,
}

/// Backpressure Controller
///
/// Monitors queue fill levels and determines appropriate actions
/// to prevent system overload.
pub struct BackpressureController {
    /// Configuration
    config: BackpressureConfig,
    
    /// Metrics reference for monitoring
    metrics: Arc<WriteMetrics>,
}

impl BackpressureController {
    /// Create a new backpressure controller
    pub fn new(config: BackpressureConfig, metrics: Arc<WriteMetrics>) -> Self {
        Self { config, metrics }
    }
    
    /// Evaluate what action to take based on current queue level
    pub fn evaluate(&self, queue_fill_ratio: f64) -> BackpressureAction {
        if queue_fill_ratio >= self.config.reject_threshold {
            // Queue is critically full - reject
            BackpressureAction::Reject
        } else if queue_fill_ratio >= self.config.queue_high_watermark {
            // Queue is heavily loaded - severe slowdown
            let slowdown = self.calculate_slowdown(queue_fill_ratio, true);
            BackpressureAction::Slowdown(slowdown)
        } else if queue_fill_ratio >= self.config.queue_low_watermark {
            // Queue is moderately loaded - mild slowdown
            let slowdown = self.calculate_slowdown(queue_fill_ratio, false);
            BackpressureAction::Slowdown(slowdown)
        } else {
            // Queue has room - accept immediately
            BackpressureAction::Accept
        }
    }
    
    /// Calculate slowdown duration based on queue fill ratio
    fn calculate_slowdown(&self, fill_ratio: f64, severe: bool) -> Duration {
        let base = self.config.base_slowdown_ms as f64;
        let max = self.config.max_slowdown_ms as f64;
        
        let slowdown_ms = if self.config.exponential_backoff {
            // Exponential increase as we approach high watermark
            let ratio = if severe {
                // Between high watermark and reject threshold
                (fill_ratio - self.config.queue_high_watermark) / 
                    (self.config.reject_threshold - self.config.queue_high_watermark)
            } else {
                // Between low and high watermark
                (fill_ratio - self.config.queue_low_watermark) / 
                    (self.config.queue_high_watermark - self.config.queue_low_watermark)
            };
            
            let exponent = ratio * 4.0; // Max 4x exponential factor
            let delay = base * (2.0_f64).powf(exponent);
            delay.min(max)
        } else {
            // Linear increase
            let range = max - base;
            let progress = if severe {
                (fill_ratio - self.config.queue_high_watermark) / 
                    (self.config.reject_threshold - self.config.queue_high_watermark)
            } else {
                (fill_ratio - self.config.queue_low_watermark) / 
                    (self.config.queue_high_watermark - self.config.queue_low_watermark)
            };
            base + (range * progress)
        };
        
        Duration::from_millis(slowdown_ms as u64)
    }
    
    /// Apply backpressure action (async)
    /// 
    /// Returns Ok(()) if request should proceed, Err if rejected.
    pub async fn apply(&self, queue_fill_ratio: f64) -> Result<(), BackpressureError> {
        match self.evaluate(queue_fill_ratio) {
            BackpressureAction::Accept => Ok(()),
            BackpressureAction::Slowdown(duration) => {
                tracing::debug!("Backpressure slowdown: {:?}", duration);
                sleep(duration).await;
                Ok(())
            }
            BackpressureAction::Reject => {
                self.metrics.record_rejection();
                tracing::warn!("Backpressure rejected request: queue={:.2}%", queue_fill_ratio * 100.0);
                Err(BackpressureError::QueueFull(queue_fill_ratio))
            }
        }
    }
    
    /// Check if queue level is critical
    pub fn is_critical(&self, queue_fill_ratio: f64) -> bool {
        queue_fill_ratio >= self.config.reject_threshold
    }
    
    /// Check if queue level requires slowdown
    pub fn needs_slowdown(&self, queue_fill_ratio: f64) -> bool {
        queue_fill_ratio >= self.config.queue_low_watermark
    }
    
    /// Get current config
    pub fn config(&self) -> &BackpressureConfig {
        &self.config
    }
    
    /// Update config at runtime
    pub fn set_config(&mut self, config: BackpressureConfig) {
        self.config = config;
    }
}

/// Errors from backpressure control
#[derive(Debug, Clone, thiserror::Error)]
pub enum BackpressureError {
    #[error("Queue is full ({0:.2}%), request rejected")]
    QueueFull(f64),
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_backpressure_accept() {
        let config = BackpressureConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let controller = BackpressureController::new(config, metrics);
        
        // Low fill ratio - should accept
        let action = controller.evaluate(0.3);
        assert_eq!(action, BackpressureAction::Accept);
    }
    
    #[test]
    fn test_backpressure_slowdown() {
        let config = BackpressureConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let controller = BackpressureController::new(config, metrics);
        
        // Medium fill ratio - should slowdown
        let action = controller.evaluate(0.6);
        match action {
            BackpressureAction::Slowdown(d) => {
                assert!(d.as_millis() > 0);
            }
            _ => panic!("Expected slowdown"),
        }
    }
    
    #[test]
    fn test_backpressure_reject() {
        let config = BackpressureConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let controller = BackpressureController::new(config, metrics);
        
        // High fill ratio - should reject
        let action = controller.evaluate(0.96);
        assert_eq!(action, BackpressureAction::Reject);
    }
    
    #[test]
    fn test_backpressure_watermarks() {
        let config = BackpressureConfig {
            queue_low_watermark: 0.5,
            queue_high_watermark: 0.8,
            reject_threshold: 0.95,
            ..Default::default()
        };
        let metrics = Arc::new(WriteMetrics::new());
        let controller = BackpressureController::new(config, metrics);
        
        // Just below low watermark - accept
        assert_eq!(controller.evaluate(0.49), BackpressureAction::Accept);
        
        // At low watermark - slowdown
        assert!(matches!(controller.evaluate(0.50), BackpressureAction::Slowdown(_)));
        
        // Between low and high - slowdown
        assert!(matches!(controller.evaluate(0.65), BackpressureAction::Slowdown(_)));
        
        // At high watermark - slowdown (more severe)
        assert!(matches!(controller.evaluate(0.80), BackpressureAction::Slowdown(_)));
        
        // Just below reject - slowdown (severe)
        assert!(matches!(controller.evaluate(0.94), BackpressureAction::Slowdown(_)));
        
        // At reject threshold - reject
        assert_eq!(controller.evaluate(0.95), BackpressureAction::Reject);
    }
    
    #[test]
    fn test_slowdown_increases_with_load() {
        let config = BackpressureConfig::default();
        let metrics = Arc::new(WriteMetrics::new());
        let controller = BackpressureController::new(config, metrics);
        
        // Get slowdown at different levels
        let slowdown_55 = match controller.evaluate(0.55) {
            BackpressureAction::Slowdown(d) => d,
            _ => panic!("Expected slowdown"),
        };
        
        let slowdown_75 = match controller.evaluate(0.75) {
            BackpressureAction::Slowdown(d) => d,
            _ => panic!("Expected slowdown"),
        };
        
        // Higher load should mean longer slowdown
        assert!(slowdown_75 > slowdown_55);
    }
}
