use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};

/// Strategy for determining retry timing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RetryStrategy {
    /// No retry strategy
    None,

    /// Fixed delay between retries
    Fixed(Duration),

    /// Linear increase in delay
    Linear(Duration),

    /// Exponential increase in delay (doubles each time)
    Exponential(Duration),
}

/// Configuration for job retry behavior
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Retry {
    /// Strategy for calculating retry time
    strategy: RetryStrategy,
}

impl Default for Retry {
    fn default() -> Self {
        Self::none()
    }
}

impl Retry {
    /// Create a new retry configuration with a strategy
    pub fn new(strategy: RetryStrategy) -> Self {
        Self { strategy }
    }

    /// Create a retry configuration with no retries
    pub fn none() -> Self {
        Self::new(RetryStrategy::None)
    }

    /// Create a fixed delay retry
    pub fn fixed(delay: Duration) -> Self {
        Self::new(RetryStrategy::Fixed(delay))
    }

    /// Create a linear retry
    pub fn linear(increment: Duration) -> Self {
        Self::new(RetryStrategy::Linear(increment))
    }

    /// Create an exponential retry
    pub fn exponential(initial_delay: Duration) -> Self {
        Self::new(RetryStrategy::Exponential(initial_delay))
    }

    /// Check if retry is enabled
    pub fn is_enabled(&self) -> bool {
        !matches!(self.strategy, RetryStrategy::None)
    }

    /// Calculate the next retry time based on current attempt number
    pub fn next_retry_time(&self, current_attempt: i32) -> DateTime<Utc> {
        let now = Utc::now();

        match &self.strategy {
            RetryStrategy::None => now,
            RetryStrategy::Fixed(delay) => now + *delay,
            RetryStrategy::Linear(increment) => now + (*increment * current_attempt),
            RetryStrategy::Exponential(initial_delay) => {
                now + *initial_delay * 2_i32.pow((current_attempt - 1).max(0) as u32)
            }
        }
    }

    /// Get the retry strategy
    pub fn strategy(&self) -> &RetryStrategy {
        &self.strategy
    }
}

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

    #[test]
    fn test_retry_none_is_not_enabled() {
        // Arrange
        let retry = Retry::none();

        // Act
        let enabled = retry.is_enabled();

        // Assert
        assert!(!enabled);
    }

    #[test]
    fn test_retry_fixed_is_enabled() {
        // Arrange
        let retry = Retry::fixed(Duration::seconds(10));

        // Act
        let enabled = retry.is_enabled();

        // Assert
        assert!(enabled);
    }

    #[test]
    fn test_retry_linear_is_enabled() {
        // Arrange
        let retry = Retry::linear(Duration::seconds(5));

        // Act
        let enabled = retry.is_enabled();

        // Assert
        assert!(enabled);
    }

    #[test]
    fn test_retry_exponential_is_enabled() {
        // Arrange
        let retry = Retry::exponential(Duration::seconds(2));

        // Act
        let enabled = retry.is_enabled();

        // Assert
        assert!(enabled);
    }

    #[test]
    fn test_fixed_retry_time_is_constant() {
        // Arrange
        let delay = Duration::seconds(30);
        let retry = Retry::fixed(delay);
        let now = Utc::now();

        // Act
        let next1 = retry.next_retry_time(1);
        let next2 = retry.next_retry_time(2);
        let next3 = retry.next_retry_time(3);

        // Assert
        assert!((next1 - now - delay).num_seconds().abs() < 1);
        assert!((next2 - now - delay).num_seconds().abs() < 1);
        assert!((next3 - now - delay).num_seconds().abs() < 1);
    }

    #[test]
    fn test_linear_retry_time_increases_linearly() {
        // Arrange
        let increment = Duration::seconds(10);
        let retry = Retry::linear(increment);
        let now = Utc::now();

        // Act
        let next1 = retry.next_retry_time(1);
        let next2 = retry.next_retry_time(2);
        let next3 = retry.next_retry_time(3);

        // Assert
        assert!((next1 - now - increment).num_seconds().abs() < 1);
        assert!((next2 - now - increment * 2).num_seconds().abs() < 1);
        assert!((next3 - now - increment * 3).num_seconds().abs() < 1);
    }

    #[test]
    fn test_exponential_retry_time_doubles() {
        // Arrange
        let initial = Duration::seconds(5);
        let retry = Retry::exponential(initial);
        let now = Utc::now();

        // Act
        let next1 = retry.next_retry_time(1);
        let next2 = retry.next_retry_time(2);
        let next3 = retry.next_retry_time(3);

        // Assert
        assert!((next1 - now - initial).num_seconds().abs() < 1);
        assert!((next2 - now - initial * 2).num_seconds().abs() < 1);
        assert!((next3 - now - initial * 4).num_seconds().abs() < 1);
    }

    #[test]
    fn test_default_is_none() {
        // Arrange & Act
        let retry = Retry::default();

        // Assert
        assert!(!retry.is_enabled());
        assert!(matches!(retry.strategy(), RetryStrategy::None));
    }
}
