//! Price Circuit Breakers
//!
//! Automatic trading halts when prices move too rapidly.
//! Protects against flash crashes, oracle manipulation, and black swan events.

use crate::error::{EconomicsError, Result};
use crate::SynorDecimal;
use chrono::{DateTime, Duration, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};

/// Circuit breaker state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CircuitState {
    /// Normal operation
    Closed,
    /// Temporarily halted, monitoring for recovery
    Open,
    /// Testing if conditions are stable enough to resume
    HalfOpen,
}

/// Circuit breaker trigger reason
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TriggerReason {
    /// Price moved too much in short time
    RapidPriceChange {
        change_percent: Decimal,
        window_seconds: i64,
    },
    /// Price deviated too far from reference
    ExcessiveDeviation {
        deviation_percent: Decimal,
        reference_price: SynorDecimal,
    },
    /// Liquidity dropped below threshold
    LowLiquidity {
        current: SynorDecimal,
        threshold: SynorDecimal,
    },
    /// Multiple oracle sources disagree
    OracleDisagreement {
        spread_percent: Decimal,
    },
    /// Manual trigger by admin
    ManualHalt {
        reason: String,
    },
    /// Cascade from related market
    CascadeTrigger {
        source_pair: String,
    },
}

/// Circuit breaker event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitEvent {
    /// Affected trading pair
    pub pair: String,
    /// Previous state
    pub from_state: CircuitState,
    /// New state
    pub to_state: CircuitState,
    /// Trigger reason
    pub reason: TriggerReason,
    /// Event timestamp
    pub timestamp: DateTime<Utc>,
    /// Duration before auto-recovery (if applicable)
    pub cooldown: Option<Duration>,
}

/// Configuration for circuit breakers
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitBreakerConfig {
    /// Maximum price change in 1 minute (percentage)
    pub max_1m_change: Decimal,
    /// Maximum price change in 5 minutes (percentage)
    pub max_5m_change: Decimal,
    /// Maximum price change in 1 hour (percentage)
    pub max_1h_change: Decimal,
    /// Maximum deviation from 24h TWAP (percentage)
    pub max_twap_deviation: Decimal,
    /// Minimum liquidity threshold
    pub min_liquidity: SynorDecimal,
    /// Maximum oracle spread (percentage)
    pub max_oracle_spread: Decimal,
    /// Cooldown duration after trigger
    pub cooldown_duration: Duration,
    /// Number of stable checks before half-open → closed
    pub recovery_checks: usize,
    /// Enable cascade triggers from related markets
    pub cascade_enabled: bool,
    /// Related markets for cascade (e.g., ETH/USD affects ETH/SYNOR)
    pub cascade_pairs: Vec<(String, String)>,
}

impl Default for CircuitBreakerConfig {
    fn default() -> Self {
        Self {
            max_1m_change: Decimal::new(10, 2),    // 10%
            max_5m_change: Decimal::new(20, 2),    // 20%
            max_1h_change: Decimal::new(50, 2),    // 50%
            max_twap_deviation: Decimal::new(30, 2), // 30%
            min_liquidity: Decimal::new(10000, 0),  // $10k
            max_oracle_spread: Decimal::new(5, 2),  // 5%
            cooldown_duration: Duration::minutes(5),
            recovery_checks: 3,
            cascade_enabled: true,
            cascade_pairs: vec![
                ("BTC/USD".to_string(), "BTC/SYNOR".to_string()),
                ("ETH/USD".to_string(), "ETH/SYNOR".to_string()),
            ],
        }
    }
}

/// Price snapshot for tracking
#[derive(Debug, Clone)]
struct PriceSnapshot {
    price: SynorDecimal,
    timestamp: DateTime<Utc>,
    liquidity: Option<SynorDecimal>,
}

/// Per-pair circuit breaker state
#[derive(Debug)]
struct PairCircuitBreaker {
    state: CircuitState,
    /// Recent price history
    price_history: VecDeque<PriceSnapshot>,
    /// When breaker was triggered
    triggered_at: Option<DateTime<Utc>>,
    /// Trigger reason
    trigger_reason: Option<TriggerReason>,
    /// Recovery check count
    recovery_checks: usize,
    /// 24h TWAP reference
    twap_24h: Option<SynorDecimal>,
    /// Event history
    events: Vec<CircuitEvent>,
}

impl PairCircuitBreaker {
    fn new() -> Self {
        Self {
            state: CircuitState::Closed,
            price_history: VecDeque::with_capacity(1000),
            triggered_at: None,
            trigger_reason: None,
            recovery_checks: 0,
            twap_24h: None,
            events: Vec::new(),
        }
    }

    fn record_price(&mut self, price: SynorDecimal, liquidity: Option<SynorDecimal>) {
        self.record_price_at(price, liquidity, Utc::now());
    }

    fn record_price_at(
        &mut self,
        price: SynorDecimal,
        liquidity: Option<SynorDecimal>,
        timestamp: DateTime<Utc>,
    ) {
        let snapshot = PriceSnapshot {
            price,
            timestamp,
            liquidity,
        };
        self.price_history.push_back(snapshot);

        // Keep only last 24 hours
        let cutoff = Utc::now() - Duration::hours(24);
        while self.price_history.front().map(|s| s.timestamp < cutoff).unwrap_or(false) {
            self.price_history.pop_front();
        }

        // Update 24h TWAP
        self.update_twap();
    }

    fn update_twap(&mut self) {
        if self.price_history.is_empty() {
            return;
        }

        let sum: Decimal = self.price_history.iter().map(|s| s.price).sum();
        self.twap_24h = Some(sum / Decimal::from(self.price_history.len()));
    }

    fn get_price_at(&self, seconds_ago: i64) -> Option<SynorDecimal> {
        let target = Utc::now() - Duration::seconds(seconds_ago);
        self.price_history.iter()
            .rev()
            .find(|s| s.timestamp <= target)
            .map(|s| s.price)
    }

    fn current_price(&self) -> Option<SynorDecimal> {
        self.price_history.back().map(|s| s.price)
    }

    fn current_liquidity(&self) -> Option<SynorDecimal> {
        self.price_history.back().and_then(|s| s.liquidity)
    }
}

/// Circuit breaker manager for all trading pairs
pub struct CircuitBreakerManager {
    config: CircuitBreakerConfig,
    breakers: HashMap<String, PairCircuitBreaker>,
}

impl CircuitBreakerManager {
    pub fn new() -> Self {
        Self::with_config(CircuitBreakerConfig::default())
    }

    pub fn with_config(config: CircuitBreakerConfig) -> Self {
        Self {
            config,
            breakers: HashMap::new(),
        }
    }

    /// Record a new price and check circuit breakers
    pub fn record_price(
        &mut self,
        pair: &str,
        price: SynorDecimal,
        liquidity: Option<SynorDecimal>,
    ) -> Result<CircuitState> {
        let breaker = self.breakers.entry(pair.to_string())
            .or_insert_with(PairCircuitBreaker::new);

        // Use the convenience method for real-time price recording
        breaker.record_price(price, liquidity);

        // Check breakers if currently closed
        if breaker.state == CircuitState::Closed {
            self.check_triggers(pair)?;
        } else {
            self.check_recovery(pair)?;
        }

        Ok(self.get_state(pair))
    }

    /// Record a price at a specific timestamp (useful for testing)
    pub fn record_price_at(
        &mut self,
        pair: &str,
        price: SynorDecimal,
        liquidity: Option<SynorDecimal>,
        timestamp: DateTime<Utc>,
    ) -> Result<CircuitState> {
        let breaker = self.breakers.entry(pair.to_string())
            .or_insert_with(PairCircuitBreaker::new);

        breaker.record_price_at(price, liquidity, timestamp);

        // Check breakers if currently closed
        if breaker.state == CircuitState::Closed {
            self.check_triggers(pair)?;
        } else {
            self.check_recovery(pair)?;
        }

        Ok(self.get_state(pair))
    }

    /// Check all trigger conditions
    fn check_triggers(&mut self, pair: &str) -> Result<()> {
        let breaker = self.breakers.get(pair).ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        let current = breaker.current_price().ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        // Check 1-minute change
        if let Some(price_1m) = breaker.get_price_at(60) {
            let change = ((current - price_1m) / price_1m).abs();
            if change > self.config.max_1m_change {
                return self.trigger_breaker(pair, TriggerReason::RapidPriceChange {
                    change_percent: change * Decimal::ONE_HUNDRED,
                    window_seconds: 60,
                });
            }
        }

        // Check 5-minute change
        if let Some(price_5m) = breaker.get_price_at(300) {
            let change = ((current - price_5m) / price_5m).abs();
            if change > self.config.max_5m_change {
                return self.trigger_breaker(pair, TriggerReason::RapidPriceChange {
                    change_percent: change * Decimal::ONE_HUNDRED,
                    window_seconds: 300,
                });
            }
        }

        // Check 1-hour change
        if let Some(price_1h) = breaker.get_price_at(3600) {
            let change = ((current - price_1h) / price_1h).abs();
            if change > self.config.max_1h_change {
                return self.trigger_breaker(pair, TriggerReason::RapidPriceChange {
                    change_percent: change * Decimal::ONE_HUNDRED,
                    window_seconds: 3600,
                });
            }
        }

        // Check TWAP deviation
        if let Some(twap) = breaker.twap_24h {
            let deviation = ((current - twap) / twap).abs();
            if deviation > self.config.max_twap_deviation {
                return self.trigger_breaker(pair, TriggerReason::ExcessiveDeviation {
                    deviation_percent: deviation * Decimal::ONE_HUNDRED,
                    reference_price: twap,
                });
            }
        }

        // Check liquidity
        if let Some(liquidity) = breaker.current_liquidity() {
            if liquidity < self.config.min_liquidity {
                return self.trigger_breaker(pair, TriggerReason::LowLiquidity {
                    current: liquidity,
                    threshold: self.config.min_liquidity,
                });
            }
        }

        Ok(())
    }

    /// Trigger the circuit breaker
    fn trigger_breaker(&mut self, pair: &str, reason: TriggerReason) -> Result<()> {
        let breaker = self.breakers.get_mut(pair).ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        let event = CircuitEvent {
            pair: pair.to_string(),
            from_state: breaker.state,
            to_state: CircuitState::Open,
            reason: reason.clone(),
            timestamp: Utc::now(),
            cooldown: Some(self.config.cooldown_duration),
        };

        breaker.state = CircuitState::Open;
        breaker.triggered_at = Some(Utc::now());
        breaker.trigger_reason = Some(reason);
        breaker.recovery_checks = 0;
        breaker.events.push(event);

        // Check cascade triggers
        if self.config.cascade_enabled {
            let cascades: Vec<_> = self.config.cascade_pairs.iter()
                .filter(|(source, _)| source == pair)
                .map(|(_, target)| target.clone())
                .collect();

            for target in cascades {
                self.trigger_cascade(pair, &target)?;
            }
        }

        Ok(())
    }

    /// Trigger cascade to related market
    fn trigger_cascade(&mut self, source: &str, target: &str) -> Result<()> {
        if let Some(breaker) = self.breakers.get_mut(target) {
            if breaker.state == CircuitState::Closed {
                let event = CircuitEvent {
                    pair: target.to_string(),
                    from_state: breaker.state,
                    to_state: CircuitState::Open,
                    reason: TriggerReason::CascadeTrigger {
                        source_pair: source.to_string(),
                    },
                    timestamp: Utc::now(),
                    cooldown: Some(self.config.cooldown_duration),
                };

                breaker.state = CircuitState::Open;
                breaker.triggered_at = Some(Utc::now());
                breaker.trigger_reason = Some(TriggerReason::CascadeTrigger {
                    source_pair: source.to_string(),
                });
                breaker.events.push(event);
            }
        }
        Ok(())
    }

    /// Check if breaker can recover
    fn check_recovery(&mut self, pair: &str) -> Result<()> {
        let cooldown = self.config.cooldown_duration;
        let recovery_needed = self.config.recovery_checks;

        // Get current state first (immutable borrow)
        let (current_state, triggered_at, trigger_reason) = {
            let breaker = self.breakers.get(pair).ok_or_else(||
                EconomicsError::PriceFeedUnavailable(pair.to_string())
            )?;
            (breaker.state, breaker.triggered_at, breaker.trigger_reason.clone())
        };

        // Check stability for half-open state (immutable borrow)
        let is_stable = if current_state == CircuitState::HalfOpen {
            self.is_stable(pair)?
        } else {
            false
        };

        // Now get mutable reference for updates
        let breaker = self.breakers.get_mut(pair).ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        match current_state {
            CircuitState::Open => {
                // Check if cooldown expired
                if let Some(triggered) = triggered_at {
                    if Utc::now() - triggered >= cooldown {
                        // Move to half-open
                        let event = CircuitEvent {
                            pair: pair.to_string(),
                            from_state: CircuitState::Open,
                            to_state: CircuitState::HalfOpen,
                            reason: trigger_reason.clone().unwrap_or(
                                TriggerReason::ManualHalt { reason: "Unknown".into() }
                            ),
                            timestamp: Utc::now(),
                            cooldown: None,
                        };
                        breaker.state = CircuitState::HalfOpen;
                        breaker.events.push(event);
                    }
                }
            }
            CircuitState::HalfOpen => {
                // Check if conditions are stable
                if is_stable {
                    breaker.recovery_checks += 1;

                    if breaker.recovery_checks >= recovery_needed {
                        // Fully recover
                        let event = CircuitEvent {
                            pair: pair.to_string(),
                            from_state: CircuitState::HalfOpen,
                            to_state: CircuitState::Closed,
                            reason: trigger_reason.unwrap_or(
                                TriggerReason::ManualHalt { reason: "Recovery".into() }
                            ),
                            timestamp: Utc::now(),
                            cooldown: None,
                        };
                        breaker.state = CircuitState::Closed;
                        breaker.triggered_at = None;
                        breaker.trigger_reason = None;
                        breaker.recovery_checks = 0;
                        breaker.events.push(event);
                    }
                } else {
                    // Reset recovery counter
                    breaker.recovery_checks = 0;
                }
            }
            CircuitState::Closed => {}
        }

        Ok(())
    }

    /// Check if market conditions are stable
    fn is_stable(&self, pair: &str) -> Result<bool> {
        let breaker = self.breakers.get(pair).ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        let current = match breaker.current_price() {
            Some(p) => p,
            None => return Ok(false),
        };

        // Check 1-minute stability (must be under half the trigger threshold)
        if let Some(price_1m) = breaker.get_price_at(60) {
            let change = ((current - price_1m) / price_1m).abs();
            if change > self.config.max_1m_change / Decimal::from(2) {
                return Ok(false);
            }
        }

        // Check liquidity
        if let Some(liquidity) = breaker.current_liquidity() {
            if liquidity < self.config.min_liquidity {
                return Ok(false);
            }
        }

        Ok(true)
    }

    /// Get current state for a pair
    pub fn get_state(&self, pair: &str) -> CircuitState {
        self.breakers.get(pair)
            .map(|b| b.state)
            .unwrap_or(CircuitState::Closed)
    }

    /// Check if trading is allowed
    pub fn is_trading_allowed(&self, pair: &str) -> bool {
        self.get_state(pair) == CircuitState::Closed
    }

    /// Manually trigger circuit breaker
    pub fn manual_halt(&mut self, pair: &str, reason: impl Into<String>) -> Result<()> {
        self.breakers.entry(pair.to_string())
            .or_insert_with(PairCircuitBreaker::new);

        self.trigger_breaker(pair, TriggerReason::ManualHalt {
            reason: reason.into(),
        })
    }

    /// Manually reset circuit breaker
    pub fn manual_reset(&mut self, pair: &str) -> Result<()> {
        let breaker = self.breakers.get_mut(pair).ok_or_else(||
            EconomicsError::PriceFeedUnavailable(pair.to_string())
        )?;

        let event = CircuitEvent {
            pair: pair.to_string(),
            from_state: breaker.state,
            to_state: CircuitState::Closed,
            reason: TriggerReason::ManualHalt { reason: "Manual reset".into() },
            timestamp: Utc::now(),
            cooldown: None,
        };

        breaker.state = CircuitState::Closed;
        breaker.triggered_at = None;
        breaker.trigger_reason = None;
        breaker.recovery_checks = 0;
        breaker.events.push(event);

        Ok(())
    }

    /// Record oracle disagreement
    pub fn record_oracle_spread(&mut self, pair: &str, spread: Decimal) -> Result<()> {
        if spread > self.config.max_oracle_spread {
            self.breakers.entry(pair.to_string())
                .or_insert_with(PairCircuitBreaker::new);

            self.trigger_breaker(pair, TriggerReason::OracleDisagreement {
                spread_percent: spread * Decimal::ONE_HUNDRED,
            })?;
        }
        Ok(())
    }

    /// Get event history for a pair
    pub fn get_events(&self, pair: &str) -> Vec<CircuitEvent> {
        self.breakers.get(pair)
            .map(|b| b.events.clone())
            .unwrap_or_default()
    }

    /// Get all currently halted pairs
    pub fn get_halted_pairs(&self) -> Vec<(String, CircuitState, Option<TriggerReason>)> {
        self.breakers.iter()
            .filter(|(_, b)| b.state != CircuitState::Closed)
            .map(|(pair, b)| (pair.clone(), b.state, b.trigger_reason.clone()))
            .collect()
    }

    /// Get summary statistics
    pub fn get_stats(&self) -> CircuitBreakerStats {
        let total = self.breakers.len();
        let open = self.breakers.values().filter(|b| b.state == CircuitState::Open).count();
        let half_open = self.breakers.values().filter(|b| b.state == CircuitState::HalfOpen).count();
        let total_events: usize = self.breakers.values().map(|b| b.events.len()).sum();

        CircuitBreakerStats {
            total_pairs: total,
            open_breakers: open,
            half_open_breakers: half_open,
            closed_breakers: total - open - half_open,
            total_events,
        }
    }
}

impl Default for CircuitBreakerManager {
    fn default() -> Self {
        Self::new()
    }
}

/// Circuit breaker statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircuitBreakerStats {
    pub total_pairs: usize,
    pub open_breakers: usize,
    pub half_open_breakers: usize,
    pub closed_breakers: usize,
    pub total_events: usize,
}

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

    #[test]
    fn test_normal_price_movement() {
        let mut manager = CircuitBreakerManager::new();

        // Normal price movements should not trigger
        for i in 0..10 {
            let price = dec!(100) + Decimal::from(i);
            let state = manager.record_price("SYNOR/USD", price, Some(dec!(100000))).unwrap();
            assert_eq!(state, CircuitState::Closed);
        }
    }

    #[test]
    fn test_flash_crash() {
        use chrono::Duration;

        let mut manager = CircuitBreakerManager::new();
        let now = Utc::now();

        // Record baseline 2 minutes ago
        manager.record_price_at("SYNOR/USD", dec!(100), Some(dec!(100000)), now - Duration::minutes(2)).unwrap();

        // Simulate 15% drop (exceeds 10% 1-minute threshold)
        let state = manager.record_price_at("SYNOR/USD", dec!(85), Some(dec!(100000)), now).unwrap();
        assert_eq!(state, CircuitState::Open);
    }

    #[test]
    fn test_low_liquidity() {
        let mut manager = CircuitBreakerManager::new();

        // Record with very low liquidity
        let state = manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100))).unwrap();
        assert_eq!(state, CircuitState::Open);
    }

    #[test]
    fn test_manual_halt_and_reset() {
        let mut manager = CircuitBreakerManager::new();

        manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100000))).unwrap();
        assert!(manager.is_trading_allowed("SYNOR/USD"));

        // Manual halt
        manager.manual_halt("SYNOR/USD", "Scheduled maintenance").unwrap();
        assert!(!manager.is_trading_allowed("SYNOR/USD"));

        // Manual reset
        manager.manual_reset("SYNOR/USD").unwrap();
        assert!(manager.is_trading_allowed("SYNOR/USD"));
    }

    #[test]
    fn test_oracle_disagreement() {
        let mut manager = CircuitBreakerManager::new();

        // Initialize
        manager.record_price("SYNOR/USD", dec!(100), Some(dec!(100000))).unwrap();

        // Record 10% spread (exceeds 5% threshold)
        manager.record_oracle_spread("SYNOR/USD", dec!(0.10)).unwrap();

        assert_eq!(manager.get_state("SYNOR/USD"), CircuitState::Open);
    }
}
