use crate::{TimeRange, TimeRangeProof, DelayStrategy, MixingPool};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Orchestrates all timing obfuscation strategies
#[derive(Debug)]
pub struct TimingObfuscator<T> {
    /// Mixing pool for batch releases
    mixing_pool: MixingPool<T>,

    /// Strategy for adding delays
    delay_strategy: DelayStrategy,

    /// Configuration
    config: ObfuscationConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObfuscationConfig {
    /// Enable mixing pool batching
    pub use_mixing_pool: bool,

    /// Enable random delays
    pub use_delays: bool,

    /// Enable fuzzy timestamps
    pub use_fuzzy_timestamps: bool,

    /// Ratio of fake items to inject (0.0 - 1.0)
    pub fake_item_ratio: f32,
}

impl Default for ObfuscationConfig {
    fn default() -> Self {
        Self {
            use_mixing_pool: true,
            use_delays: true,
            use_fuzzy_timestamps: true,
            fake_item_ratio: 0.3, // 30% fake traffic
        }
    }
}

impl<T> TimingObfuscator<T> {
    pub fn new(config: ObfuscationConfig) -> Self {
        Self {
            mixing_pool: MixingPool::default(),
            delay_strategy: DelayStrategy::node_rebroadcast(),
            config,
        }
    }

    /// Add item to mixing pool
    pub fn add_to_pool(&mut self, item: T) {
        if self.config.use_mixing_pool {
            self.mixing_pool.add(item);
        }
    }

    /// Check if should release batch
    pub fn should_release_batch(&self) -> bool {
        if !self.config.use_mixing_pool {
            return false;
        }
        self.mixing_pool.should_release()
    }

    /// Release shuffled batch
    pub fn release_batch(&mut self) -> Vec<T> {
        self.mixing_pool.release_batch()
    }

    /// Apply delay strategy
    pub async fn apply_delay(&self) {
        if self.config.use_delays {
            self.delay_strategy.sleep().await;
        }
    }

    /// Get current pool size
    pub fn pool_size(&self) -> usize {
        self.mixing_pool.len()
    }
}

/// Transaction timing metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimingMetadata {
    /// Fuzzy time range
    pub time_range: TimeRange,

    /// Zero-knowledge proof of creation time
    pub time_proof: TimeRangeProof,

    /// Optional: observed network delay (for adaptive strategies)
    pub network_delay_hint: Option<Duration>,
}

impl TimingMetadata {
    /// Create timing metadata for a transaction
    pub fn new(real_time: DateTime<Utc>, seed: &[u8; 32]) -> Result<Self, crate::timerange::TimeRangeError> {
        let time_range = TimeRange::new_fuzzy(real_time);
        let time_proof = TimeRangeProof::generate(seed, real_time, &time_range)?;

        Ok(Self {
            time_range,
            time_proof,
            network_delay_hint: None,
        })
    }

    /// Validate timing metadata
    pub fn validate(&self, current_time: DateTime<Utc>) -> bool {
        // Check time range is valid
        if !self.time_range.is_valid(current_time) {
            return false;
        }

        // Verify time proof
        if !self.time_proof.verify(&self.time_range) {
            return false;
        }

        true
    }

    /// Set network delay hint for adaptive strategies
    pub fn with_network_delay(mut self, delay: Duration) -> Self {
        self.network_delay_hint = Some(delay);
        self
    }
}

/// Strategies for transaction propagation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PropagationPhase {
    /// Dandelion STEM phase (anonymity)
    Stem,

    /// Dandelion FLUFF phase (broadcast)
    Fluff,
}

/// Transaction wrapper with timing information
#[derive(Debug, Clone)]
pub struct TimedItem<T> {
    pub item: T,
    pub timing: TimingMetadata,
    pub phase: PropagationPhase,
}

impl<T> TimedItem<T> {
    pub fn new(item: T, timing: TimingMetadata) -> Self {
        Self {
            item,
            timing,
            phase: PropagationPhase::Stem,
        }
    }

    pub fn transition_to_fluff(mut self) -> Self {
        self.phase = PropagationPhase::Fluff;
        self
    }

    pub fn is_stem(&self) -> bool {
        self.phase == PropagationPhase::Stem
    }

    pub fn is_fluff(&self) -> bool {
        self.phase == PropagationPhase::Fluff
    }
}

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

    #[test]
    fn test_timing_metadata_creation() {
        let now = Utc::now();
        let seed = [42u8; 32];

        let metadata = TimingMetadata::new(now, &seed).unwrap();
        assert!(metadata.validate(now));
    }

    #[test]
    fn test_timing_metadata_validation() {
        let now = Utc::now();
        let seed = [42u8; 32];

        let metadata = TimingMetadata::new(now, &seed).unwrap();

        // Should validate at current time
        assert!(metadata.validate(now));

        // Should validate slightly in the future
        let future = now + chrono::Duration::hours(1);
        assert!(metadata.validate(future));
    }

    #[test]
    fn test_obfuscator_pool() {
        let config = ObfuscationConfig::default();
        let mut obfuscator = TimingObfuscator::<u32>::new(config);

        obfuscator.add_to_pool(1);
        obfuscator.add_to_pool(2);
        obfuscator.add_to_pool(3);

        assert_eq!(obfuscator.pool_size(), 3);
    }

    #[test]
    fn test_timed_item_phases() {
        let now = Utc::now();
        let seed = [42u8; 32];
        let timing = TimingMetadata::new(now, &seed).unwrap();

        let item = TimedItem::new(42u32, timing);
        assert!(item.is_stem());
        assert!(!item.is_fluff());

        let item = item.transition_to_fluff();
        assert!(!item.is_stem());
        assert!(item.is_fluff());
    }

    #[tokio::test]
    async fn test_apply_delay() {
        let config = ObfuscationConfig::default();
        let obfuscator = TimingObfuscator::<u32>::new(config);

        let start = std::time::Instant::now();
        obfuscator.apply_delay().await;
        let elapsed = start.elapsed();

        // Should have some delay (at least 10s from node_rebroadcast strategy)
        assert!(elapsed >= Duration::from_secs(10));
    }
}
