//! Retry logic tests
//!
//! Tests for exponential backoff and retry strategies.

use crate::resilience::retry::{retry_with_backoff, RetryConfig, RetryError};
use crate::CommonError;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};

#[tokio::test]
async fn test_retry_success_on_first_attempt() {
    let result = retry_with_backoff(
        || async { Ok::<_, CommonError>(42) },
        RetryConfig::default(),
    )
    .await;

    assert_eq!(result.unwrap(), 42);
}

#[tokio::test]
async fn test_retry_success_after_failures() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let result = retry_with_backoff(
        move || {
            let attempt = attempts_clone.fetch_add(1, Ordering::SeqCst);
            async move {
                if attempt < 2 {
                    Err(CommonError::network("temporary failure"))
                } else {
                    Ok(100)
                }
            }
        },
        RetryConfig::default(),
    )
    .await;

    assert_eq!(result.unwrap(), 100);
    assert_eq!(attempts.load(Ordering::SeqCst), 3);
}

#[tokio::test]
async fn test_retry_max_attempts_exceeded() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let config = RetryConfig {
        max_retries: 3,
        base_delay: Duration::from_millis(10),
        max_delay: Duration::from_millis(100),
        ..Default::default()
    };

    let result = retry_with_backoff(
        move || {
            attempts_clone.fetch_add(1, Ordering::SeqCst);
            async { Err::<i32, _>(CommonError::network("persistent failure")) }
        },
        config,
    )
    .await;

    assert!(result.is_err());
    // Initial attempt + 3 retries = 4 total attempts
    assert_eq!(attempts.load(Ordering::SeqCst), 4);

    match result.unwrap_err() {
        RetryError::MaxRetriesExceeded { attempts, .. } => {
            assert_eq!(attempts, 4);
        }
        _ => panic!("Expected MaxRetriesExceeded error"),
    }
}

#[tokio::test]
async fn test_retry_non_retryable_error() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let result = retry_with_backoff(
        move || {
            attempts_clone.fetch_add(1, Ordering::SeqCst);
            async { Err::<i32, _>(CommonError::validation("invalid input")) }
        },
        RetryConfig::default(),
    )
    .await;

    assert!(result.is_err());
    // Should fail immediately without retries
    assert_eq!(attempts.load(Ordering::SeqCst), 1);

    match result.unwrap_err() {
        RetryError::NonRetryableError(_) => {}
        _ => panic!("Expected NonRetryableError"),
    }
}

#[tokio::test]
async fn test_retry_exponential_backoff_timing() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let config = RetryConfig {
        max_retries: 3,
        base_delay: Duration::from_millis(100),
        max_delay: Duration::from_millis(1000),
        ..Default::default()
    };

    let start = Instant::now();

    let _ = retry_with_backoff(
        move || {
            attempts_clone.fetch_add(1, Ordering::SeqCst);
            async { Err::<i32, _>(CommonError::network("failure")) }
        },
        config,
    )
    .await;

    let elapsed = start.elapsed();

    // Expected delays: 100ms, 200ms, 400ms = 700ms total
    // Allow some variance for scheduling
    assert!(
        elapsed >= Duration::from_millis(650),
        "Elapsed: {:?}",
        elapsed
    );
    assert!(
        elapsed < Duration::from_millis(900),
        "Elapsed: {:?}",
        elapsed
    );
}

#[tokio::test]
async fn test_retry_max_delay_cap() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let config = RetryConfig {
        max_retries: 5,
        base_delay: Duration::from_millis(100),
        max_delay: Duration::from_millis(300),
        ..Default::default()
    };

    let start = Instant::now();

    let _ = retry_with_backoff(
        move || {
            attempts_clone.fetch_add(1, Ordering::SeqCst);
            async { Err::<i32, _>(CommonError::network("failure")) }
        },
        config,
    )
    .await;

    let elapsed = start.elapsed();

    // Expected delays: 100ms, 200ms, 300ms (capped), 300ms (capped), 300ms (capped)
    // Total: 1200ms, but allow variance
    assert!(
        elapsed >= Duration::from_millis(1100),
        "Elapsed: {:?}",
        elapsed
    );
    assert!(
        elapsed < Duration::from_millis(1500),
        "Elapsed: {:?}",
        elapsed
    );
}

#[tokio::test]
async fn test_retry_database_error() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let result = retry_with_backoff(
        move || {
            let attempt = attempts_clone.fetch_add(1, Ordering::SeqCst);
            async move {
                if attempt < 2 {
                    // Use network error instead to avoid needing sqlx::Error
                    Err(CommonError::network("database connection lost"))
                } else {
                    Ok(())
                }
            }
        },
        RetryConfig {
            max_retries: 3,
            base_delay: Duration::from_millis(10),
            max_delay: Duration::from_millis(100),
            ..Default::default()
        },
    )
    .await;

    assert!(result.is_ok());
    assert_eq!(attempts.load(Ordering::SeqCst), 3);
}

#[tokio::test]
async fn test_retry_timeout_error() {
    let attempts = Arc::new(AtomicU32::new(0));
    let attempts_clone = Arc::clone(&attempts);

    let result = retry_with_backoff(
        move || {
            let attempt = attempts_clone.fetch_add(1, Ordering::SeqCst);
            async move {
                if attempt < 1 {
                    Err(CommonError::timeout(150, 100))
                } else {
                    Ok("success")
                }
            }
        },
        RetryConfig {
            max_retries: 2,
            base_delay: Duration::from_millis(10),
            max_delay: Duration::from_millis(100),
            ..Default::default()
        },
    )
    .await;

    assert!(result.is_ok());
    assert_eq!(attempts.load(Ordering::SeqCst), 2);
}

#[tokio::test]
async fn test_retry_error_contains_last_error() {
    let result = retry_with_backoff(
        || async { Err::<i32, _>(CommonError::network("test error")) },
        RetryConfig {
            max_retries: 2,
            base_delay: Duration::from_millis(10),
            max_delay: Duration::from_millis(50),
            ..Default::default()
        },
    )
    .await;

    match result.unwrap_err() {
        RetryError::MaxRetriesExceeded { last_error, .. } => {
            assert!(last_error.to_string().contains("test error"));
        }
        _ => panic!("Expected MaxRetriesExceeded"),
    }
}

#[tokio::test]
async fn test_retry_config_default() {
    let config = RetryConfig::default();
    assert_eq!(config.max_retries, 3);
    assert_eq!(config.base_delay, Duration::from_millis(100));
    assert_eq!(config.max_delay, Duration::from_secs(10));
}
