//! Retry Logic, Backoff Strategies, and Circuit Breaker Pattern
//!
//! Implements resilient connection handling with:
//! - Multiple backoff strategies (Exponential, Linear, Fibonacci, Fixed)
//! - Timeout policies (connect, auth, command, total)
//! - Circuit breaker pattern for cascading failure prevention
//! - Per-host circuit breaker state management

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};

use serde::{Deserialize, Serialize};
use tracing::{debug, warn};

// ============================================================================
// Backoff Strategies
// ============================================================================

/// Backoff strategy for retries
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BackoffStrategy {
    /// Exponential backoff: base^attempt (capped at max)
    Exponential {
        #[serde(default = "default_base")]
        base: u32,
        #[serde(default = "default_max_seconds")]
        max_seconds: u64,
    },
    /// Linear backoff: base * attempt
    Linear {
        #[serde(default = "default_base_seconds")]
        base_seconds: u64,
        #[serde(default = "default_max_seconds")]
        max_seconds: u64,
    },
    /// Fibonacci backoff
    Fibonacci {
        #[serde(default = "default_max_seconds")]
        max_seconds: u64,
    },
    /// Fixed backoff
    Fixed {
        #[serde(default = "default_fixed")]
        seconds: u64,
    },
}

impl Default for BackoffStrategy {
    fn default() -> Self {
        Self::Exponential {
            base: 2,
            max_seconds: 60,
        }
    }
}

fn default_base() -> u32 {
    2
}

fn default_base_seconds() -> u64 {
    1
}

fn default_fixed() -> u64 {
    1
}

fn default_max_seconds() -> u64 {
    60
}

impl BackoffStrategy {
    /// Calculate backoff duration for attempt number (0-indexed)
    pub fn calculate(&self, attempt: u32) -> Duration {
        let seconds = match self {
            BackoffStrategy::Exponential { base, max_seconds } => {
                (*base as u64).pow(attempt).min(*max_seconds)
            }
            BackoffStrategy::Linear {
                base_seconds,
                max_seconds,
            } => (base_seconds * (attempt as u64 + 1)).min(*max_seconds),
            BackoffStrategy::Fibonacci { max_seconds } => fibonacci(attempt).min(*max_seconds),
            BackoffStrategy::Fixed { seconds } => *seconds,
        };
        Duration::from_secs(seconds)
    }
}

/// Calculate Fibonacci number
fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 1,
        1 => 1,
        _ => {
            let mut a = 1u64;
            let mut b = 1u64;
            for _ in 2..=n {
                let temp = a + b;
                a = b;
                b = temp;
            }
            b
        }
    }
}

// ============================================================================
// Retry Configuration
// ============================================================================

/// Retry policy configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryPolicy {
    /// Maximum number of retries
    #[serde(default = "default_max_retries")]
    pub max_retries: u32,
    /// Backoff strategy to use
    #[serde(default)]
    pub backoff: BackoffStrategy,
    /// Error types that are retryable
    #[serde(default)]
    pub retryable_errors: Vec<String>,
}

fn default_max_retries() -> u32 {
    3
}

impl Default for RetryPolicy {
    fn default() -> Self {
        Self {
            max_retries: 3,
            backoff: BackoffStrategy::Exponential {
                base: 2,
                max_seconds: 60,
            },
            retryable_errors: vec![
                "ConnectionFailed".to_string(),
                "TimeoutExceeded".to_string(),
                "PoolExhausted".to_string(),
            ],
        }
    }
}

// ============================================================================
// Circuit Breaker
// ============================================================================

/// Circuit breaker state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitBreakerState {
    /// Circuit is closed, requests flow normally
    Closed,
    /// Circuit is open, requests fail immediately
    Open,
    /// Testing if service recovered
    HalfOpen,
}

/// Circuit breaker configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitBreakerConfig {
    /// Failures before opening circuit
    #[serde(default = "default_failure_threshold")]
    pub failure_threshold: u32,
    /// Successes before closing circuit
    #[serde(default = "default_success_threshold")]
    pub success_threshold: u32,
    /// Time in open state before half-open
    #[serde(default = "default_timeout_seconds")]
    pub timeout_seconds: u64,
    /// Whether to track per-host
    #[serde(default = "default_per_host")]
    pub per_host: bool,
}

fn default_failure_threshold() -> u32 {
    5
}

fn default_success_threshold() -> u32 {
    2
}

fn default_timeout_seconds() -> u64 {
    60
}

fn default_per_host() -> bool {
    true
}

impl Default for CircuitBreakerConfig {
    fn default() -> Self {
        Self {
            failure_threshold: 5,
            success_threshold: 2,
            timeout_seconds: 60,
            per_host: true,
        }
    }
}

/// Circuit breaker for per-host tracking
pub struct CircuitBreaker {
    state: std::sync::RwLock<CircuitBreakerState>,
    failure_count: Arc<AtomicU32>,
    success_count: Arc<AtomicU32>,
    last_failure_time: std::sync::RwLock<Option<Instant>>,
    config: CircuitBreakerConfig,
}

impl CircuitBreaker {
    /// Create new circuit breaker
    pub fn new(config: CircuitBreakerConfig) -> Self {
        Self {
            state: std::sync::RwLock::new(CircuitBreakerState::Closed),
            failure_count: Arc::new(AtomicU32::new(0)),
            success_count: Arc::new(AtomicU32::new(0)),
            last_failure_time: std::sync::RwLock::new(None),
            config,
        }
    }

    /// Get current state
    pub fn state(&self) -> CircuitBreakerState {
        let current_state = *self.state.read().unwrap();

        // Check if we should transition from Open to HalfOpen
        if current_state == CircuitBreakerState::Open {
            if let Some(last_failure) = *self.last_failure_time.read().unwrap() {
                if last_failure.elapsed() >= Duration::from_secs(self.config.timeout_seconds) {
                    debug!("Circuit breaker transitioning from Open to HalfOpen");
                    let mut state = self.state.write().unwrap();
                    *state = CircuitBreakerState::HalfOpen;
                    self.success_count.store(0, Ordering::SeqCst);
                    return CircuitBreakerState::HalfOpen;
                }
            }
        }

        current_state
    }

    /// Record successful request
    pub fn record_success(&self) {
        let state = self.state();
        self.failure_count.store(0, Ordering::SeqCst);

        if state == CircuitBreakerState::HalfOpen {
            let success = self.success_count.fetch_add(1, Ordering::SeqCst) + 1;
            if success >= self.config.success_threshold {
                debug!("Circuit breaker closed after {} successes", success);
                let mut circuit_state = self.state.write().unwrap();
                *circuit_state = CircuitBreakerState::Closed;
            }
        }
    }

    /// Record failed request
    pub fn record_failure(&self) {
        let failure = self.failure_count.fetch_add(1, Ordering::SeqCst) + 1;
        self.success_count.store(0, Ordering::SeqCst);
        *self.last_failure_time.write().unwrap() = Some(Instant::now());

        let state = self.state();
        if failure >= self.config.failure_threshold && state == CircuitBreakerState::Closed {
            warn!("Circuit breaker opened after {} failures", failure);
            let mut circuit_state = self.state.write().unwrap();
            *circuit_state = CircuitBreakerState::Open;
        }
    }

    /// Check if request is allowed
    pub fn is_allowed(&self) -> bool {
        self.state() != CircuitBreakerState::Open
    }

    /// Reset circuit breaker
    pub fn reset(&self) {
        self.failure_count.store(0, Ordering::SeqCst);
        self.success_count.store(0, Ordering::SeqCst);
        *self.last_failure_time.write().unwrap() = None;
        let mut state = self.state.write().unwrap();
        *state = CircuitBreakerState::Closed;
    }
}

// ============================================================================
// Timeout Configuration
// ============================================================================

/// Timeout policy for SSH operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutPolicy {
    /// Connection timeout (seconds)
    #[serde(default = "default_connect_timeout")]
    pub connect_timeout: u64,
    /// Authentication timeout (seconds)
    #[serde(default = "default_auth_timeout")]
    pub auth_timeout: u64,
    /// Single command execution timeout (seconds)
    #[serde(default = "default_command_timeout")]
    pub command_timeout: u64,
    /// Total operation timeout (seconds)
    #[serde(default = "default_total_timeout")]
    pub total_timeout: u64,
}

fn default_connect_timeout() -> u64 {
    30
}

fn default_auth_timeout() -> u64 {
    60
}

fn default_command_timeout() -> u64 {
    300
}

fn default_total_timeout() -> u64 {
    900
}

impl Default for TimeoutPolicy {
    fn default() -> Self {
        Self {
            connect_timeout: 30,
            auth_timeout: 60,
            command_timeout: 300,
            total_timeout: 900,
        }
    }
}

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

    #[test]
    fn test_exponential_backoff() {
        let strategy = BackoffStrategy::Exponential {
            base: 2,
            max_seconds: 60,
        };
        assert_eq!(strategy.calculate(0).as_secs(), 1);
        assert_eq!(strategy.calculate(1).as_secs(), 2);
        assert_eq!(strategy.calculate(2).as_secs(), 4);
        assert_eq!(strategy.calculate(3).as_secs(), 8);
        assert_eq!(strategy.calculate(10).as_secs(), 60); // capped at max
    }

    #[test]
    fn test_linear_backoff() {
        let strategy = BackoffStrategy::Linear {
            base_seconds: 5,
            max_seconds: 60,
        };
        assert_eq!(strategy.calculate(0).as_secs(), 5);
        assert_eq!(strategy.calculate(1).as_secs(), 10);
        assert_eq!(strategy.calculate(2).as_secs(), 15);
    }

    #[test]
    fn test_fibonacci_backoff() {
        let strategy = BackoffStrategy::Fibonacci { max_seconds: 100 };
        assert_eq!(strategy.calculate(0).as_secs(), 1);
        assert_eq!(strategy.calculate(1).as_secs(), 1);
        assert_eq!(strategy.calculate(2).as_secs(), 2);
        assert_eq!(strategy.calculate(3).as_secs(), 3);
        assert_eq!(strategy.calculate(4).as_secs(), 5);
    }

    #[test]
    fn test_circuit_breaker_state_transitions() {
        let config = CircuitBreakerConfig {
            failure_threshold: 3,
            success_threshold: 2,
            timeout_seconds: 1,
            per_host: true,
        };
        let cb = CircuitBreaker::new(config);

        // Initially closed
        assert_eq!(cb.state(), CircuitBreakerState::Closed);

        // Record failures
        cb.record_failure();
        cb.record_failure();
        cb.record_failure();

        // Should be open now
        assert_eq!(cb.state(), CircuitBreakerState::Open);
        assert!(!cb.is_allowed());

        // After timeout, should be half-open
        std::thread::sleep(Duration::from_secs(1));
        assert_eq!(cb.state(), CircuitBreakerState::HalfOpen);

        // Record successes
        cb.record_success();
        cb.record_success();

        // Should be closed again
        assert_eq!(cb.state(), CircuitBreakerState::Closed);
    }

    #[test]
    fn test_circuit_breaker_reset() {
        let config = CircuitBreakerConfig::default();
        let cb = CircuitBreaker::new(config);

        cb.record_failure();
        cb.record_failure();
        cb.record_failure();
        cb.record_failure();
        cb.record_failure();

        assert_eq!(cb.state(), CircuitBreakerState::Open);

        cb.reset();
        assert_eq!(cb.state(), CircuitBreakerState::Closed);
        assert!(cb.is_allowed());
    }
}
