use crate::error::RiskViolation;
use crate::rate_limit::TokenBucket;
use crate::types::{OrderRequest, RiskConfig};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Mutex, RwLock};
use std::time::Instant;

/// Central risk manager — every order must pass through check() before submission.
pub struct RiskManager {
    config: RiskConfig,
    /// Uses AtomicU64 with f64::to_bits/from_bits to avoid RwLock on the hot path.
    daily_pnl: AtomicU64,
    daily_baseline: AtomicU64,
    rate_limiter: TokenBucket,
    kill_switch: AtomicBool,
    kill_switch_reason: RwLock<Option<String>>,
    recent_orders: Mutex<HashMap<u64, Instant>>,
}

impl RiskManager {
    pub fn new(config: RiskConfig) -> Self {
        let rate_limiter = TokenBucket::new(config.rate_limit_sustained, config.rate_limit_burst);
        Self {
            config,
            daily_pnl: AtomicU64::new(0.0f64.to_bits()),
            daily_baseline: AtomicU64::new(0.0f64.to_bits()),
            rate_limiter,
            kill_switch: AtomicBool::new(false),
            kill_switch_reason: RwLock::new(None),
            recent_orders: Mutex::new(HashMap::new()),
        }
    }

    /// Run all 8 risk checks against an order request.
    /// market_exposure and total_notional are passed in from the position tracker.
    #[inline]
    pub fn check(
        &self,
        request: &OrderRequest,
        market_exposure: f64,
        total_notional: f64,
    ) -> Result<(), RiskViolation> {
        // 1. Kill switch
        if self.kill_switch.load(Ordering::Acquire) {
            let reason = self
                .kill_switch_reason
                .read()
                .unwrap_or_else(|e| e.into_inner())
                .clone()
                .unwrap_or_else(|| "unknown".to_string());
            return Err(RiskViolation::KillSwitchActive { reason });
        }

        // 2. Price sanity (skip for market orders — they execute at best available)
        if request.order_type == crate::types::OrderType::Limit {
            if request.price.is_nan() || request.price < 0.01 || request.price > 0.99 {
                return Err(RiskViolation::InvalidPrice {
                    price: request.price,
                });
            }
        }

        // 3. Size sanity (NaN comparisons are always false, so check explicitly)
        if request.size.is_nan() || request.size <= 0.0 || request.size > self.config.max_order_size {
            return Err(RiskViolation::InvalidSize {
                size: request.size,
                max: self.config.max_order_size,
            });
        }

        // 4. Position limit (sells reduce exposure, buys increase it)
        let projected = match request.order_side {
            crate::types::OrderSide::Buy => market_exposure + request.size,
            crate::types::OrderSide::Sell => (market_exposure - request.size).max(0.0),
        };
        if projected > self.config.max_position_per_market {
            return Err(RiskViolation::PositionLimitExceeded {
                market_id: request.market_id.clone(),
                projected,
                limit: self.config.max_position_per_market,
            });
        }

        // 5. Portfolio notional limit (sells reduce notional, buys increase it)
        // Market orders have price=0 — use worst-case price (1.0) for prediction markets
        let effective_price = if request.order_type == crate::types::OrderType::Market && request.price <= 0.0 {
            1.0
        } else {
            request.price
        };
        if !effective_price.is_finite() {
            return Err(RiskViolation::InvalidPrice {
                price: effective_price,
            });
        }
        let order_notional = request.size * effective_price;
        let projected_notional = match request.order_side {
            crate::types::OrderSide::Buy => total_notional + order_notional,
            crate::types::OrderSide::Sell => (total_notional - order_notional).max(0.0),
        };
        if projected_notional > self.config.max_portfolio_notional {
            return Err(RiskViolation::PortfolioNotionalExceeded {
                projected: projected_notional,
                limit: self.config.max_portfolio_notional,
            });
        }

        // 6. Daily drawdown
        let daily_pnl = f64::from_bits(self.daily_pnl.load(Ordering::Acquire));
        let baseline = f64::from_bits(self.daily_baseline.load(Ordering::Acquire));
        if baseline.abs() > f64::EPSILON {
            let drawdown_pct = ((baseline - daily_pnl) / baseline.abs()) * 100.0;
            if drawdown_pct > self.config.max_daily_drawdown_pct {
                return Err(RiskViolation::DailyDrawdownExceeded {
                    current_pct: drawdown_pct,
                    limit_pct: self.config.max_daily_drawdown_pct,
                });
            }
        }

        // 7. Duplicate detection (skip for market orders — identical market orders are legitimate)
        if request.order_type == crate::types::OrderType::Limit {
            let hash = request.dedup_hash();
            let now = Instant::now();
            let dedup_window =
                std::time::Duration::from_millis(self.config.dedup_window_ms);
            let mut recent = self.recent_orders.lock().unwrap_or_else(|e| e.into_inner());

            // Periodic cleanup: only retain every 64 checks to avoid O(n) scan per order
            if recent.len() > 128 {
                recent.retain(|_, v| now.duration_since(*v) < dedup_window);
            }

            if let Some(prev) = recent.get(&hash) {
                if now.duration_since(*prev) < dedup_window {
                    return Err(RiskViolation::DuplicateOrder);
                }
            }
            recent.insert(hash, now);
        }

        // 8. Rate limit
        if !self.rate_limiter.try_acquire() {
            return Err(RiskViolation::RateLimitExceeded);
        }

        Ok(())
    }

    /// Activate the kill switch.
    pub fn activate_kill_switch(&self, reason: &str) {
        self.kill_switch.store(true, Ordering::Release);
        *self.kill_switch_reason.write().unwrap_or_else(|e| e.into_inner()) = Some(reason.to_string());
    }

    /// Deactivate the kill switch.
    pub fn deactivate_kill_switch(&self) {
        self.kill_switch.store(false, Ordering::Release);
        *self.kill_switch_reason.write().unwrap_or_else(|e| e.into_inner()) = None;
    }

    #[inline]
    pub fn is_kill_switch_active(&self) -> bool {
        self.kill_switch.load(Ordering::Acquire)
    }

    pub fn kill_switch_reason(&self) -> Option<String> {
        self.kill_switch_reason.read().unwrap_or_else(|e| e.into_inner()).clone()
    }

    /// Update the daily P&L tracker.
    pub fn update_daily_pnl(&self, pnl: f64) {
        self.daily_pnl.store(pnl.to_bits(), Ordering::Release);
    }

    /// Set the baseline for drawdown calculation.
    pub fn set_daily_baseline(&self, baseline: f64) {
        self.daily_baseline.store(baseline.to_bits(), Ordering::Release);
    }

    /// Check event-level position limit. Only fires when max_position_per_event is Some.
    #[inline]
    pub fn check_event_limit(
        &self,
        event_exposure: f64,
        order_size: f64,
        order_side: crate::types::OrderSide,
    ) -> Result<(), RiskViolation> {
        if let Some(limit) = self.config.max_position_per_event {
            let projected = match order_side {
                crate::types::OrderSide::Buy => event_exposure + order_size,
                crate::types::OrderSide::Sell => (event_exposure - order_size).max(0.0),
            };
            if projected > limit {
                return Err(RiskViolation::EventPositionLimitExceeded {
                    projected,
                    limit,
                });
            }
        }
        Ok(())
    }

    /// Get the current rate limiter usage info.
    pub fn rate_limit_available(&self) -> f64 {
        self.rate_limiter.available()
    }

    pub fn rate_limit_sustained(&self) -> u32 {
        self.rate_limiter.sustained_rate()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{OrderSide, OrderType, Side, TimeInForce};

    fn test_config() -> RiskConfig {
        RiskConfig {
            max_position_per_market: 100.0,
            max_portfolio_notional: 1000.0,
            max_daily_drawdown_pct: 5.0,
            max_order_size: 50.0,
            rate_limit_sustained: 50,
            rate_limit_burst: 300,
            dedup_window_ms: 1000,
            max_position_per_event: None,
        }
    }

    fn test_request() -> OrderRequest {
        OrderRequest {
            market_id: "mkt_1".into(),
            side: Side::Yes,
            order_side: OrderSide::Buy,
            size: 10.0,
            price: 0.55,
            order_type: OrderType::Limit,
            time_in_force: TimeInForce::GTC,
            post_only: true,
            token_id: None,
            neg_risk: false,
        }
    }

    #[test]
    fn valid_order_passes() {
        let risk = RiskManager::new(test_config());
        assert!(risk.check(&test_request(), 0.0, 0.0).is_ok());
    }

    #[test]
    fn kill_switch_rejects() {
        let risk = RiskManager::new(test_config());
        risk.activate_kill_switch("test");
        assert!(matches!(
            risk.check(&test_request(), 0.0, 0.0),
            Err(RiskViolation::KillSwitchActive { .. })
        ));
    }

    #[test]
    fn invalid_price_rejects() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.price = 1.50;
        assert!(matches!(
            risk.check(&req, 0.0, 0.0),
            Err(RiskViolation::InvalidPrice { .. })
        ));
    }

    #[test]
    fn oversized_order_rejects() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.size = 60.0;
        assert!(matches!(
            risk.check(&req, 0.0, 0.0),
            Err(RiskViolation::InvalidSize { .. })
        ));
    }

    #[test]
    fn position_limit_rejects() {
        let risk = RiskManager::new(test_config());
        assert!(matches!(
            risk.check(&test_request(), 95.0, 0.0),
            Err(RiskViolation::PositionLimitExceeded { .. })
        ));
    }

    #[test]
    fn duplicate_order_rejects() {
        let risk = RiskManager::new(test_config());
        assert!(risk.check(&test_request(), 0.0, 0.0).is_ok());
        assert!(matches!(
            risk.check(&test_request(), 0.0, 0.0),
            Err(RiskViolation::DuplicateOrder)
        ));
    }

    #[test]
    fn sell_order_passes_near_limit() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.order_side = OrderSide::Sell;
        req.size = 10.0;
        // At 95 exposure, a sell of 10 reduces to 85 — should pass
        assert!(risk.check(&req, 95.0, 0.0).is_ok());
    }

    #[test]
    fn buy_order_blocked_near_limit() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.order_side = OrderSide::Buy;
        req.size = 10.0;
        // At 95 exposure, a buy of 10 increases to 105 — should fail
        assert!(matches!(
            risk.check(&req, 95.0, 0.0),
            Err(RiskViolation::PositionLimitExceeded { .. })
        ));
    }

    #[test]
    fn drawdown_zero_baseline_passes() {
        let risk = RiskManager::new(test_config());
        risk.set_daily_baseline(0.0);
        risk.update_daily_pnl(-100.0);
        // With baseline=0, drawdown check is skipped
        assert!(risk.check(&test_request(), 0.0, 0.0).is_ok());
    }

    #[test]
    fn drawdown_negative_baseline_handled() {
        let risk = RiskManager::new(test_config());
        risk.set_daily_baseline(-10.0);
        risk.update_daily_pnl(-10.0);
        // baseline=-10, pnl=-10 => drawdown = 0% => should pass
        assert!(risk.check(&test_request(), 0.0, 0.0).is_ok());
    }

    #[test]
    fn market_order_skips_price_check() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.order_type = OrderType::Market;
        req.price = 0.0; // market orders have price=0
        // Should pass — price check is skipped for market orders
        assert!(risk.check(&req, 0.0, 0.0).is_ok());
    }

    #[test]
    fn market_order_skips_dedup() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.order_type = OrderType::Market;
        req.price = 0.0;
        // First market order passes
        assert!(risk.check(&req, 0.0, 0.0).is_ok());
        // Identical market order also passes (no dedup for market orders)
        assert!(risk.check(&req, 0.0, 0.0).is_ok());
    }

    #[test]
    fn nan_size_rejects() {
        let risk = RiskManager::new(test_config());
        let mut req = test_request();
        req.size = f64::NAN;
        assert!(matches!(
            risk.check(&req, 0.0, 0.0),
            Err(RiskViolation::InvalidSize { .. })
        ));
    }

    #[test]
    fn event_limit_passes_when_none() {
        let risk = RiskManager::new(test_config());
        // max_position_per_event = None, should always pass
        assert!(risk.check_event_limit(500.0, 100.0, OrderSide::Buy).is_ok());
    }

    #[test]
    fn event_limit_passes_under_limit() {
        let mut config = test_config();
        config.max_position_per_event = Some(200.0);
        let risk = RiskManager::new(config);
        // 50 + 10 = 60 < 200
        assert!(risk.check_event_limit(50.0, 10.0, OrderSide::Buy).is_ok());
    }

    #[test]
    fn event_limit_blocks_over_limit() {
        let mut config = test_config();
        config.max_position_per_event = Some(200.0);
        let risk = RiskManager::new(config);
        // 195 + 10 = 205 > 200
        assert!(matches!(
            risk.check_event_limit(195.0, 10.0, OrderSide::Buy),
            Err(RiskViolation::EventPositionLimitExceeded { .. })
        ));
    }

    #[test]
    fn event_limit_sell_reduces() {
        let mut config = test_config();
        config.max_position_per_event = Some(200.0);
        let risk = RiskManager::new(config);
        // 195 - 10 = 185 < 200 — sell reduces exposure
        assert!(risk.check_event_limit(195.0, 10.0, OrderSide::Sell).is_ok());
    }
}
