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

/// Strategy for introducing random delays in transaction propagation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DelayStrategy {
    /// Fixed delay
    Fixed(Duration),

    /// Random delay within a range
    Random { min: Duration, max: Duration },

    /// Exponential backoff with jitter
    Exponential {
        base: Duration,
        max: Duration,
        attempt: u32,
    },

    /// Delay based on observed Tor latency
    TorAdaptive {
        observed_latency: Duration,
        multiplier: f64,
    },
}

impl DelayStrategy {
    /// Wallet broadcast delay: 5-60 minutes
    pub fn wallet_broadcast() -> Self {
        Self::Random {
            min: Duration::from_secs(5 * 60),
            max: Duration::from_secs(60 * 60),
        }
    }

    /// Node rebroadcast delay: 10s - 2 minutes
    pub fn node_rebroadcast() -> Self {
        Self::Random {
            min: Duration::from_secs(10),
            max: Duration::from_secs(120),
        }
    }

    /// Mixing pool batch delay: 10-30 minutes
    pub fn mixing_pool() -> Self {
        Self::Random {
            min: Duration::from_secs(10 * 60),
            max: Duration::from_secs(30 * 60),
        }
    }

    /// Validation delay before processing: 5s - 60s
    pub fn validation() -> Self {
        Self::Random {
            min: Duration::from_secs(5),
            max: Duration::from_secs(60),
        }
    }

    /// Dandelion STEM hop delay: 30s - 5 minutes
    pub fn dandelion_stem() -> Self {
        Self::Random {
            min: Duration::from_secs(30),
            max: Duration::from_secs(5 * 60),
        }
    }

    /// Create adaptive delay based on observed Tor latency
    pub fn from_tor_latency(observed: Duration) -> Self {
        Self::TorAdaptive {
            observed_latency: observed,
            multiplier: 2.0, // Amplify variability
        }
    }

    /// Calculate the actual delay to use
    pub fn calculate(&self) -> Duration {
        let mut rng = rand::thread_rng();

        match self {
            Self::Fixed(d) => *d,

            Self::Random { min, max } => {
                let min_ms = min.as_millis() as u64;
                let max_ms = max.as_millis() as u64;
                let delay_ms = rng.gen_range(min_ms..=max_ms);
                Duration::from_millis(delay_ms)
            }

            Self::Exponential { base, max, attempt } => {
                let base_ms = base.as_millis() as u64;
                let max_ms = max.as_millis() as u64;

                // Exponential: base * 2^attempt with jitter
                let exp_delay = base_ms.saturating_mul(2u64.saturating_pow(*attempt));
                let capped = exp_delay.min(max_ms);

                // Add ±25% jitter
                let jitter_range = (capped as f64 * 0.25) as u64;
                let jitter = rng.gen_range(0..=jitter_range);
                let with_jitter = if rng.gen_bool(0.5) {
                    capped.saturating_add(jitter)
                } else {
                    capped.saturating_sub(jitter)
                };

                Duration::from_millis(with_jitter)
            }

            Self::TorAdaptive { observed_latency, multiplier } => {
                let base_ms = observed_latency.as_millis() as u64;
                let amplified = (base_ms as f64 * multiplier) as u64;

                // Random delay up to amplified latency
                let delay_ms = rng.gen_range(0..=amplified);
                Duration::from_millis(delay_ms)
            }
        }
    }

    /// Async sleep with this delay strategy
    pub async fn sleep(&self) {
        let delay = self.calculate();
        tokio::time::sleep(delay).await;
    }
}

/// Helper for managing multiple delays in sequence
#[derive(Debug)]
pub struct DelaySequence {
    strategies: Vec<DelayStrategy>,
    current: usize,
}

impl DelaySequence {
    pub fn new(strategies: Vec<DelayStrategy>) -> Self {
        Self {
            strategies,
            current: 0,
        }
    }

    /// Create a Dandelion++ STEM sequence (1-4 random hops)
    pub fn dandelion_stem() -> Self {
        let mut rng = rand::thread_rng();
        let hops = rng.gen_range(1..=4);

        let strategies = (0..hops)
            .map(|_| DelayStrategy::dandelion_stem())
            .collect();

        Self::new(strategies)
    }

    /// Get next delay, returns None when sequence is exhausted
    pub fn next(&mut self) -> Option<&DelayStrategy> {
        if self.current < self.strategies.len() {
            let strategy = &self.strategies[self.current];
            self.current += 1;
            Some(strategy)
        } else {
            None
        }
    }

    /// Reset sequence to beginning
    pub fn reset(&mut self) {
        self.current = 0;
    }

    /// Total number of delays in sequence
    pub fn len(&self) -> usize {
        self.strategies.len()
    }

    pub fn is_empty(&self) -> bool {
        self.strategies.is_empty()
    }
}

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

    #[test]
    fn test_fixed_delay() {
        let strategy = DelayStrategy::Fixed(Duration::from_secs(10));
        let delay = strategy.calculate();
        assert_eq!(delay, Duration::from_secs(10));
    }

    #[test]
    fn test_random_delay() {
        let strategy = DelayStrategy::Random {
            min: Duration::from_secs(5),
            max: Duration::from_secs(10),
        };

        for _ in 0..100 {
            let delay = strategy.calculate();
            assert!(delay >= Duration::from_secs(5));
            assert!(delay <= Duration::from_secs(10));
        }
    }

    #[test]
    fn test_exponential_delay() {
        let base = Duration::from_secs(1);
        let max = Duration::from_secs(100);

        for attempt in 0..5 {
            let strategy = DelayStrategy::Exponential {
                base,
                max,
                attempt,
            };
            let delay = strategy.calculate();
            assert!(delay <= max);
        }
    }

    #[test]
    fn test_tor_adaptive_delay() {
        let observed = Duration::from_secs(2);
        let strategy = DelayStrategy::from_tor_latency(observed);

        for _ in 0..100 {
            let delay = strategy.calculate();
            // Should be between 0 and 2x observed latency
            assert!(delay <= Duration::from_secs(4));
        }
    }

    #[test]
    fn test_delay_sequence() {
        let mut sequence = DelaySequence::dandelion_stem();
        let len = sequence.len();

        assert!(len >= 1 && len <= 4);

        let mut count = 0;
        while sequence.next().is_some() {
            count += 1;
        }

        assert_eq!(count, len);
    }

    #[tokio::test]
    async fn test_async_sleep() {
        let strategy = DelayStrategy::Fixed(Duration::from_millis(10));
        let start = std::time::Instant::now();
        strategy.sleep().await;
        let elapsed = start.elapsed();

        assert!(elapsed >= Duration::from_millis(10));
    }
}
