//! Stress Test Scenarios
//!
//! Predefined chaos engineering scenarios for resilience validation.

use anyhow::Result;
use std::sync::Arc;
use std::time::Duration;
use tracing::{info, warn};

use crate::fault_injector::{DatabaseFaultInjector, NetworkFaultInjector, RedisFaultInjector};
use crate::metrics::{RecoveryMetrics, RecoveryTimer, ResilienceMetrics};

/// Stress test scenario definition
#[derive(Debug, Clone)]
pub enum StressScenario {
    /// Database connection loss
    DatabaseConnectionLoss { duration: Duration },
    /// Database slow queries
    DatabaseSlowQueries { delay: Duration },
    /// Redis cache failure
    RedisCacheFailure,
    /// Redis connection timeout
    RedisConnectionTimeout { duration: Duration },
    /// Redis memory pressure
    RedisMemoryPressure { fill_percentage: u8 },
    /// Network partition
    NetworkPartition { duration: Duration },
    /// Network latency spike
    NetworkLatencySpike {
        latency: Duration,
        duration: Duration,
    },
    /// Combined failure (cascade)
    CascadeFailure,
}

impl StressScenario {
    /// Get human-readable name
    pub fn name(&self) -> &str {
        match self {
            Self::DatabaseConnectionLoss { .. } => "Database Connection Loss",
            Self::DatabaseSlowQueries { .. } => "Database Slow Queries",
            Self::RedisCacheFailure => "Redis Cache Failure",
            Self::RedisConnectionTimeout { .. } => "Redis Connection Timeout",
            Self::RedisMemoryPressure { .. } => "Redis Memory Pressure",
            Self::NetworkPartition { .. } => "Network Partition",
            Self::NetworkLatencySpike { .. } => "Network Latency Spike",
            Self::CascadeFailure => "Cascade Failure",
        }
    }
}

/// Scenario runner for executing stress tests
pub struct ScenarioRunner {
    db_injector: Option<Arc<DatabaseFaultInjector>>,
    redis_injector: Option<Arc<RedisFaultInjector>>,
    network_injector: Arc<NetworkFaultInjector>,
    metrics: Arc<ResilienceMetrics>,
}

impl ScenarioRunner {
    /// Create new scenario runner
    pub fn new(
        db_injector: Option<Arc<DatabaseFaultInjector>>,
        redis_injector: Option<Arc<RedisFaultInjector>>,
    ) -> Self {
        Self {
            db_injector,
            redis_injector,
            network_injector: Arc::new(NetworkFaultInjector::new()),
            metrics: Arc::new(ResilienceMetrics::new()),
        }
    }

    /// Run a specific scenario
    pub async fn run_scenario(&self, scenario: StressScenario) -> Result<RecoveryMetrics> {
        info!("Running stress scenario: {}", scenario.name());

        let mut timer = RecoveryTimer::start();

        match scenario {
            StressScenario::DatabaseConnectionLoss { duration } => {
                if let Some(injector) = &self.db_injector {
                    timer.mark_detection();
                    injector.inject_connection_loss(duration).await?;
                    timer.mark_recovery();
                } else {
                    warn!("Database injector not available for scenario");
                }
            },

            StressScenario::DatabaseSlowQueries { delay } => {
                if let Some(injector) = &self.db_injector {
                    timer.mark_detection();
                    injector.inject_slow_queries(delay).await?;
                    timer.mark_recovery();
                } else {
                    warn!("Database injector not available for scenario");
                }
            },

            StressScenario::RedisCacheFailure => {
                if let Some(injector) = &self.redis_injector {
                    timer.mark_detection();
                    injector.inject_cache_failure().await?;
                    timer.mark_recovery();
                } else {
                    warn!("Redis injector not available for scenario");
                }
            },

            StressScenario::RedisConnectionTimeout { duration } => {
                if let Some(injector) = &self.redis_injector {
                    timer.mark_detection();
                    injector.inject_connection_timeout(duration).await?;
                    timer.mark_recovery();
                } else {
                    warn!("Redis injector not available for scenario");
                }
            },

            StressScenario::RedisMemoryPressure { fill_percentage } => {
                if let Some(injector) = &self.redis_injector {
                    timer.mark_detection();
                    injector.inject_memory_pressure(fill_percentage).await?;
                    timer.mark_recovery();
                } else {
                    warn!("Redis injector not available for scenario");
                }
            },

            StressScenario::NetworkPartition { duration } => {
                timer.mark_detection();
                self.network_injector
                    .inject_network_partition(duration)
                    .await?;
                timer.mark_recovery();
            },

            StressScenario::NetworkLatencySpike { latency, duration } => {
                timer.mark_detection();
                self.network_injector
                    .inject_latency_spike(latency, duration)
                    .await?;
                timer.mark_recovery();
            },

            StressScenario::CascadeFailure => {
                timer.mark_detection();

                // Cascade: Redis -> Database -> Network
                if let Some(redis) = &self.redis_injector {
                    redis.inject_cache_failure().await?;
                }

                tokio::time::sleep(Duration::from_secs(1)).await;

                if let Some(db) = &self.db_injector {
                    db.inject_connection_loss(Duration::from_secs(2)).await?;
                }

                tokio::time::sleep(Duration::from_secs(1)).await;

                self.network_injector
                    .inject_network_partition(Duration::from_secs(2))
                    .await?;

                timer.mark_recovery();
            },
        }

        let metrics = timer.build_metrics();

        // Record metrics
        self.metrics.record_recovery(&metrics).await;

        info!(
            "Scenario '{}' completed - Detection: {:?}, Recovery: {:?}",
            scenario.name(),
            metrics.detection_time,
            metrics.recovery_time
        );

        Ok(metrics)
    }

    /// Run all predefined scenarios
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn run_all_scenarios(&self) -> Result<Vec<RecoveryMetrics>> {
        let scenarios = vec![
            StressScenario::DatabaseConnectionLoss {
                duration: Duration::from_secs(3),
            },
            StressScenario::DatabaseSlowQueries {
                delay: Duration::from_secs(2),
            },
            StressScenario::RedisCacheFailure,
            StressScenario::RedisConnectionTimeout {
                duration: Duration::from_secs(2),
            },
            StressScenario::NetworkPartition {
                duration: Duration::from_secs(3),
            },
            StressScenario::NetworkLatencySpike {
                latency: Duration::from_millis(500),
                duration: Duration::from_secs(2),
            },
            StressScenario::CascadeFailure,
        ];

        let mut results = Vec::new();
        for scenario in scenarios {
            match self.run_scenario(scenario).await {
                Ok(metrics) => results.push(metrics),
                Err(e) => {
                    warn!("Scenario failed with error: {}", e);
                },
            }
        }

        Ok(results)
    }

    /// Get collected metrics
    pub fn metrics(&self) -> Arc<ResilienceMetrics> {
        self.metrics.clone()
    }
}
