//! Retry logic with exponential backoff
//!
//! Provides automatic retry mechanisms with configurable backoff strategies
//! for handling transient failures in distributed systems.
//!
//! # Example
//!
//! ```rust
//! use common::resilience::retry::{retry_with_backoff, RetryConfig};
//! use common::CommonError;
//! use std::time::Duration;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let config = RetryConfig {
//!     max_retries: 3,
//!     base_delay: Duration::from_millis(100),
//!     max_delay: Duration::from_secs(10),
//! };
//!
//! let result = retry_with_backoff(
//!     || async {
//!         // Your operation that might fail
//!         make_api_call().await
//!     },
//!     config,
//! ).await?;
//! # Ok(())
//! # }
//! # async fn make_api_call() -> Result<(), CommonError> { Ok(()) }
//! ```

use crate::CommonError;
use std::future::Future;
use std::time::Duration;
use thiserror::Error;
use tokio::time::sleep;

/// Retry configuration with circuit breaker and HFT optimization support
#[derive(Debug, Clone)]
pub struct RetryConfig {
    /// Maximum number of retry attempts (not including initial attempt)
    pub max_retries: u32,
    /// Base delay for exponential backoff
    pub base_delay: Duration,
    /// Maximum delay cap for exponential backoff
    pub max_delay: Duration,
    /// Backoff multiplier for exponential delay calculation (default 2.0)
    pub backoff_multiplier: f64,
    /// Circuit breaker failure threshold
    pub circuit_breaker_threshold: u32,
    /// Circuit breaker timeout before reset attempt
    pub circuit_breaker_timeout: Duration,
    /// Enable jitter for exponential backoff to prevent thundering herd
    pub enable_jitter: bool,
    /// HFT-specific nanosecond precision delays
    pub hft_precision_mode: bool,
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self {
            max_retries: 3,
            base_delay: Duration::from_millis(100),
            max_delay: Duration::from_secs(10),
            backoff_multiplier: 2.0,
            circuit_breaker_threshold: 5,
            circuit_breaker_timeout: Duration::from_secs(60),
            enable_jitter: true,
            hft_precision_mode: false,
        }
    }
}

impl RetryConfig {
    /// Create HFT-optimized configuration for low-latency operations
    pub fn hft_optimized() -> Self {
        Self {
            max_retries: 3,
            base_delay: Duration::from_micros(50), // 50µs base delay
            max_delay: Duration::from_millis(5),   // 5ms max delay
            backoff_multiplier: 2.0,
            circuit_breaker_threshold: 10,
            circuit_breaker_timeout: Duration::from_secs(30),
            enable_jitter: false, // No jitter for HFT - predictable timing
            hft_precision_mode: true,
        }
    }

    /// Create configuration for network operations
    pub fn network_optimized() -> Self {
        Self {
            max_retries: 5,
            base_delay: Duration::from_millis(500),
            max_delay: Duration::from_secs(10),
            backoff_multiplier: 2.0,
            circuit_breaker_threshold: 3,
            circuit_breaker_timeout: Duration::from_secs(30),
            enable_jitter: true,
            hft_precision_mode: false,
        }
    }

    /// Create configuration for database operations
    pub fn database_optimized() -> Self {
        Self {
            max_retries: 7,
            base_delay: Duration::from_millis(250),
            max_delay: Duration::from_secs(5),
            backoff_multiplier: 2.0,
            circuit_breaker_threshold: 5,
            circuit_breaker_timeout: Duration::from_secs(45),
            enable_jitter: true,
            hft_precision_mode: false,
        }
    }
}

/// Retry-specific errors
#[derive(Debug, Error)]
pub enum RetryError {
    /// Maximum retry attempts exceeded
    #[error("Max retries exceeded after {attempts} attempts. Last error: {last_error}")]
    MaxRetriesExceeded {
        /// Number of attempts made
        attempts: u32,
        /// The last error encountered
        last_error: CommonError,
    },

    /// Error is not retryable
    #[error("Non-retryable error: {0}")]
    NonRetryableError(CommonError),
}

/// Calculate exponential backoff delay
///
/// Uses exponential backoff with a maximum delay cap to prevent excessive waiting.
///
/// # Arguments
///
/// * `attempt` - Current retry attempt number (0-based)
/// * `base_delay` - Base delay for exponential calculation
/// * `max_delay` - Maximum delay cap
///
/// # Returns
///
/// Calculated delay duration
fn calculate_backoff(attempt: u32, base_delay: Duration, max_delay: Duration) -> Duration {
    let base_ms = base_delay.as_millis() as u64;

    // Exponential backoff: base_delay * 2^attempt
    let delay_ms = base_ms.saturating_mul(2_u64.saturating_pow(attempt.min(10)));

    // Cap at max_delay
    let capped_ms = delay_ms.min(max_delay.as_millis() as u64);

    Duration::from_millis(capped_ms)
}

/// Retry an async operation with exponential backoff
///
/// Automatically retries retryable errors with exponential backoff.
/// Non-retryable errors fail immediately without retry.
///
/// # Arguments
///
/// * `operation` - Async operation to retry (must be a function that returns a Future)
/// * `config` - Retry configuration
///
/// # Returns
///
/// * `Ok(T)` - Operation succeeded
/// * `Err(RetryError::MaxRetriesExceeded)` - All retries exhausted
/// * `Err(RetryError::NonRetryableError)` - Error is not retryable
///
/// # Example
///
/// ```rust
/// use common::resilience::retry::{retry_with_backoff, RetryConfig};
/// use common::CommonError;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let result = retry_with_backoff(
///     || async {
///         // Your operation
///         Ok::<_, CommonError>(42)
///     },
///     RetryConfig::default(),
/// ).await?;
/// # Ok(())
/// # }
/// ```
pub async fn retry_with_backoff<F, Fut, T>(
    mut operation: F,
    config: RetryConfig,
) -> Result<T, RetryError>
where
    F: FnMut() -> Fut,
    Fut: Future<Output = Result<T, CommonError>>,
{
    let mut attempt = 0_u32;

    loop {
        match operation().await {
            Ok(result) => {
                if attempt > 0 {
                    tracing::info!(
                        attempt = attempt + 1,
                        "Operation succeeded after retries"
                    );
                }
                return Ok(result);
            }
            Err(error) => {
                // Check if error is retryable
                if !error.is_retryable() {
                    tracing::warn!(
                        error = %error,
                        "Non-retryable error encountered"
                    );
                    return Err(RetryError::NonRetryableError(error));
                }

                // Check if we've exhausted retries
                if attempt >= config.max_retries {
                    tracing::error!(
                        attempts = attempt + 1,
                        error = %error,
                        "Max retries exceeded"
                    );
                    return Err(RetryError::MaxRetriesExceeded {
                        attempts: attempt + 1,
                        last_error: error,
                    });
                }

                // Calculate backoff delay
                let delay = calculate_backoff(attempt, config.base_delay, config.max_delay);

                tracing::warn!(
                    attempt = attempt + 1,
                    delay_ms = delay.as_millis(),
                    error = %error,
                    "Retrying after error"
                );

                // Wait before retrying
                sleep(delay).await;

                attempt += 1;
            }
        }
    }
}

/// Retry context for tracking retry state
///
/// Can be attached to errors to provide retry metadata.
#[derive(Debug, Clone)]
pub struct RetryContext {
    /// Current attempt number (1-based)
    pub attempt_number: u32,
    /// Delay until next retry
    pub next_retry_delay: Option<Duration>,
    /// Total number of attempts allowed
    pub max_attempts: u32,
}

impl RetryContext {
    /// Create a new retry context
    pub fn new(attempt_number: u32, next_retry_delay: Option<Duration>, max_attempts: u32) -> Self {
        Self {
            attempt_number,
            next_retry_delay,
            max_attempts,
        }
    }

    /// Check if more retries are available
    pub fn has_retries_remaining(&self) -> bool {
        self.attempt_number < self.max_attempts
    }

    /// Get remaining retry attempts
    pub fn remaining_attempts(&self) -> u32 {
        self.max_attempts.saturating_sub(self.attempt_number)
    }
}

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

    #[test]
    fn test_calculate_backoff() {
        let base = Duration::from_millis(100);
        let max = Duration::from_secs(10);

        // Attempt 0: 100ms
        assert_eq!(calculate_backoff(0, base, max), Duration::from_millis(100));

        // Attempt 1: 200ms
        assert_eq!(calculate_backoff(1, base, max), Duration::from_millis(200));

        // Attempt 2: 400ms
        assert_eq!(calculate_backoff(2, base, max), Duration::from_millis(400));

        // Attempt 3: 800ms
        assert_eq!(calculate_backoff(3, base, max), Duration::from_millis(800));

        // Attempt 10: should be capped at 10s
        assert_eq!(calculate_backoff(10, base, max), Duration::from_secs(10));
    }

    #[test]
    fn test_retry_context() {
        let ctx = RetryContext::new(2, Some(Duration::from_millis(500)), 5);

        assert_eq!(ctx.attempt_number, 2);
        assert_eq!(ctx.max_attempts, 5);
        assert!(ctx.has_retries_remaining());
        assert_eq!(ctx.remaining_attempts(), 3);
    }

    #[test]
    fn test_retry_context_no_retries() {
        let ctx = RetryContext::new(5, None, 5);

        assert!(!ctx.has_retries_remaining());
        assert_eq!(ctx.remaining_attempts(), 0);
    }
}
