//! Retry logic for HTTP requests
//!
//! **INTENTIONAL VIOLATION IN THIS FILE:**
//! - VIOLATION 7: No retry limit (max_retries = None)

use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Retry strategy configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
    /// Maximum number of retry attempts
    ///
    /// # VIOLATION 7: No Retry Limit
    /// @aphoria:claim[safety] Retry attempts MUST NOT exceed 3 -- Unlimited retries cause retry storms
    ///
    /// **Authority:** Requests library, Mozilla HTTP docs
    /// **Current value:** None (unbounded)
    /// **Should be:** Some(3)
    #[serde(default)]
    pub max_retries: Option<u32>,

    /// Backoff strategy
    #[serde(default = "default_backoff")]
    pub backoff: BackoffStrategy,

    /// Only retry idempotent methods (GET, PUT, DELETE)
    #[serde(default = "default_idempotent_only")]
    pub idempotent_only: bool,

    /// HTTP status codes to retry on
    #[serde(default = "default_retry_status_codes")]
    pub retry_status_codes: Vec<u16>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum BackoffStrategy {
    /// Fixed interval between retries
    Fixed(Duration),
    /// Exponential backoff (1s, 2s, 4s, ...)
    Exponential { base: Duration, multiplier: u32 },
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self {
            // VIOLATION 7: Unbounded retries
            max_retries: None,
            backoff: default_backoff(),
            idempotent_only: default_idempotent_only(),
            retry_status_codes: default_retry_status_codes(),
        }
    }
}

fn default_backoff() -> BackoffStrategy {
    // This is correct (exponential backoff per Requests library)
    BackoffStrategy::Exponential {
        base: Duration::from_secs(1),
        multiplier: 2,
    }
}

fn default_idempotent_only() -> bool {
    // This is correct (Requests library pattern)
    true
}

fn default_retry_status_codes() -> Vec<u16> {
    // Retry on server errors and rate limiting (Requests library pattern)
    vec![429, 500, 502, 503, 504]
}

impl RetryConfig {
    /// Create a production-safe retry config
    pub fn production() -> Self {
        Self {
            max_retries: Some(3), // Requests library default
            backoff: BackoffStrategy::Exponential {
                base: Duration::from_secs(1),
                multiplier: 2,
            },
            idempotent_only: true,
            retry_status_codes: vec![429, 500, 502, 503, 504],
        }
    }

    /// Validate retry configuration
    pub fn validate(&self) -> Result<(), String> {
        if self.max_retries.is_none() || self.max_retries.unwrap() > 3 {
            return Err("max_retries MUST be <= 3 (Requests library/Mozilla)".to_string());
        }
        if !self.idempotent_only {
            return Err("idempotent_only MUST be true to prevent duplicate POST operations".to_string());
        }
        Ok(())
    }
}

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

    #[test]
    fn default_retry_config_has_violation() {
        let config = RetryConfig::default();
        assert!(config.validate().is_err(), "Default retry config should have violation");
    }

    #[test]
    fn production_retry_config_is_valid() {
        let config = RetryConfig::production();
        assert!(config.validate().is_ok(), "Production retry config should be valid");
    }

    #[test]
    fn violation_7_no_retry_limit() {
        let config = RetryConfig::default();
        assert_eq!(config.max_retries, None, "VIOLATION 7: No retry limit");
    }

    #[test]
    fn backoff_is_exponential() {
        let config = RetryConfig::default();
        assert_eq!(
            config.backoff,
            BackoffStrategy::Exponential {
                base: Duration::from_secs(1),
                multiplier: 2
            },
            "Backoff should be exponential"
        );
    }

    #[test]
    fn idempotent_only_is_true() {
        let config = RetryConfig::default();
        assert!(config.idempotent_only, "idempotent_only should be true");
    }
}
