//! Drawdown monitoring system for real-time risk tracking
// #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // COMMENTED: Crate-level allows applied

use std::collections::HashMap;

use chrono::{DateTime, Utc};
// REMOVED: Direct Decimal usage - use canonical types
use tokio::sync::{broadcast, RwLock};
use tracing::warn;

use crate::error::{RiskError, RiskResult};
use crate::risk_types::{DrawdownAlertConfig, PnLMetrics, PortfolioId, RiskSeverity};
// Import canonical types

/// Drawdown alert event
#[derive(Debug, Clone)]
pub struct DrawdownAlert {
    /// Portfolio identifier that triggered the alert
    pub portfolio_id: PortfolioId,
    /// Severity level of the drawdown alert
    pub severity: RiskSeverity,
    /// Current drawdown percentage from high water mark
    pub current_drawdown_pct: f64,
    /// Threshold percentage that was breached
    pub threshold_pct: f64,
    /// Human-readable alert message
    pub message: String,
    /// Timestamp when the alert was generated
    pub timestamp: DateTime<Utc>,
}

/// Drawdown statistics for a portfolio
#[derive(Debug, Clone)]
pub struct DrawdownStats {
    /// Current drawdown percentage from peak
    pub current_drawdown_pct: f64,
    /// Maximum drawdown percentage ever recorded
    pub max_drawdown_pct: f64,
    /// Highest portfolio value achieved (high water mark)
    pub high_water_mark: f64,
    /// Number of consecutive days in drawdown
    pub days_in_drawdown: i32,
}

/// Drawdown monitor for tracking portfolio drawdowns
#[derive(Debug)]
pub struct DrawdownMonitor {
    /// Configuration for drawdown alerts per portfolio
    alert_configs: RwLock<HashMap<PortfolioId, DrawdownAlertConfig>>,
    /// Broadcast channel for alerts
    alert_sender: broadcast::Sender<DrawdownAlert>,
    /// Historical P&L tracking for drawdown calculation
    pnl_history: RwLock<HashMap<PortfolioId, Vec<PnLMetrics>>>,
    /// Internal high-water mark tracking per portfolio.
    /// This ensures drawdown is never understated by stale caller-supplied HWM.
    internal_hwm: RwLock<HashMap<PortfolioId, f64>>,
}

impl Default for DrawdownMonitor {
    fn default() -> Self {
        let (alert_sender, _) = broadcast::channel(1000);
        Self {
            alert_configs: RwLock::new(HashMap::new()),
            alert_sender,
            pnl_history: RwLock::new(HashMap::new()),
            internal_hwm: RwLock::new(HashMap::new()),
        }
    }
}

impl DrawdownMonitor {
    /// Create a new `DrawdownMonitor`
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Configure alerts for a portfolio
    pub async fn configure_alerts(&self, config: DrawdownAlertConfig) -> RiskResult<()> {
        let mut configs = self.alert_configs.write().await;
        configs.insert(config.portfolio_id.clone().unwrap_or_default(), config);
        Ok(())
    }

    /// Update P&L and return any alerts triggered
    pub async fn update_pnl(&self, metrics: &PnLMetrics) -> RiskResult<Vec<DrawdownAlert>> {
        let mut alerts = Vec::new();

        // Get current alert subscriber to catch any alerts
        let mut alert_receiver = self.subscribe_alerts();

        // Process the P&L
        self.process_pnl(metrics).await?;

        // Try to receive any alerts that were sent
        while let Ok(alert) = alert_receiver.try_recv() {
            alerts.push(alert);
        }

        Ok(alerts)
    }

    /// Process P&L metrics and check for drawdown alerts
    pub async fn process_pnl(&self, metrics: &PnLMetrics) -> RiskResult<()> {
        // Store P&L history
        {
            let mut history = self.pnl_history.write().await;
            let portfolio_history = history
                .entry(metrics.portfolio_id.clone())
                .or_insert_with(Vec::new);
            portfolio_history.push(metrics.clone());

            // Keep only recent history (last 1000 entries)
            if portfolio_history.len() > 1000 {
                portfolio_history.drain(0..portfolio_history.len() - 1000);
            }
        }

        // Check for drawdown alerts
        let configs = self.alert_configs.read().await;
        if let Some(config) = configs.get(&metrics.portfolio_id) {
            if config.enabled {
                self.check_drawdown_thresholds(metrics, config).await?;
            }
        }

        Ok(())
    }

    /// Check if drawdown has exceeded configured thresholds
    ///
    /// Uses internally tracked high-water mark instead of the caller-supplied
    /// value to prevent stale or incorrect HWM from understating drawdown.
    async fn check_drawdown_thresholds(
        &self,
        metrics: &PnLMetrics,
        config: &DrawdownAlertConfig,
    ) -> RiskResult<()> {
        let current_pnl = metrics.total_pnl.to_f64();
        let caller_hwm = metrics.high_water_mark.to_f64();

        // Use internal HWM tracking: always update to the running maximum
        // of (current_pnl, caller-supplied HWM, previously tracked HWM).
        // This ensures drawdown is never understated by stale caller data,
        // while still accepting a higher caller HWM on the first update
        // (e.g. when the portfolio already had a peak before monitoring started).
        let hwm = {
            let mut hwm_map = self.internal_hwm.write().await;
            let initial = current_pnl.max(caller_hwm);
            let entry = hwm_map
                .entry(metrics.portfolio_id.clone())
                .or_insert(initial);
            // On subsequent updates, only allow HWM to increase
            let candidate = current_pnl.max(caller_hwm);
            if candidate > *entry {
                *entry = candidate;
            }
            *entry
        };

        let current_drawdown_pct = if hwm > 0.0 {
            ((hwm - current_pnl) / hwm) * 100.0
        } else {
            0.0
        };

        let current_drawdown_pct = current_drawdown_pct.abs();

        // Check thresholds in order of severity
        if current_drawdown_pct >= config.emergency_threshold {
            self.send_alert(
                &metrics.portfolio_id,
                RiskSeverity::Critical,
                current_drawdown_pct,
                config.emergency_threshold,
                "Emergency drawdown threshold exceeded",
            )
            .await;
        } else if current_drawdown_pct >= config.critical_threshold {
            self.send_alert(
                &metrics.portfolio_id,
                RiskSeverity::High,
                current_drawdown_pct,
                config.critical_threshold,
                "Critical drawdown threshold exceeded",
            )
            .await;
        } else if current_drawdown_pct >= config.warning_threshold {
            self.send_alert(
                &metrics.portfolio_id,
                RiskSeverity::Medium,
                current_drawdown_pct,
                config.warning_threshold,
                "Warning drawdown threshold exceeded",
            )
            .await;
        }

        Ok(())
    }

    /// Send a drawdown alert
    async fn send_alert(
        &self,
        portfolio_id: &str,
        severity: RiskSeverity,
        current_pct: f64,
        threshold_pct: f64,
        message: &str,
    ) {
        let alert = DrawdownAlert {
            portfolio_id: portfolio_id.to_owned(),
            severity,
            current_drawdown_pct: current_pct,
            threshold_pct,
            message: message.to_owned(),
            timestamp: Utc::now(),
        };

        if let Err(e) = self.alert_sender.send(alert) {
            warn!("Failed to send drawdown alert: {}", e);
        }
    }

    /// Subscribe to drawdown alerts
    pub fn subscribe_alerts(&self) -> broadcast::Receiver<DrawdownAlert> {
        self.alert_sender.subscribe()
    }

    /// Get current alert configuration for a portfolio
    pub async fn get_alert_config(&self, portfolio_id: &str) -> Option<DrawdownAlertConfig> {
        let configs = self.alert_configs.read().await;
        configs.get(portfolio_id).cloned()
    }

    /// Get P&L history for a portfolio
    pub async fn get_pnl_history(&self, portfolio_id: &str) -> Vec<PnLMetrics> {
        let history = self.pnl_history.read().await;
        history.get(portfolio_id).cloned().unwrap_or_default()
    }

    /// Get drawdown statistics for a portfolio
    pub async fn get_drawdown_stats(&self, portfolio_id: &str) -> RiskResult<DrawdownStats> {
        let history = self.pnl_history.read().await;
        let empty_vec = Vec::new();
        let portfolio_history = history.get(portfolio_id).unwrap_or(&empty_vec);

        if portfolio_history.is_empty() {
            return Ok(DrawdownStats {
                current_drawdown_pct: 0.0,
                max_drawdown_pct: 0.0,
                high_water_mark: 0.0,
                days_in_drawdown: 0,
            });
        }

        let latest = portfolio_history
            .last()
            .ok_or_else(|| RiskError::CalculationError("Portfolio history is empty".to_owned()))?;

        let current_pnl = latest.total_pnl.to_f64();

        // Use internal HWM if available, falling back to caller-supplied value
        let hwm = {
            let hwm_map = self.internal_hwm.read().await;
            hwm_map
                .get(portfolio_id)
                .copied()
                .unwrap_or_else(|| latest.high_water_mark.to_f64())
        };

        // Calculate drawdown percentage properly
        // Drawdown = (HWM - Current P&L) / HWM * 100
        let current_drawdown_pct = if hwm > 0.0 {
            ((hwm - current_pnl) / hwm) * 100.0
        } else {
            0.0
        };

        // For max drawdown, use the max_drawdown value from metrics
        let max_dd = latest.max_drawdown.to_f64();
        let max_drawdown_pct = if hwm > 0.0 {
            (max_dd.abs() / hwm) * 100.0
        } else {
            0.0
        };

        Ok(DrawdownStats {
            current_drawdown_pct,
            max_drawdown_pct,
            high_water_mark: hwm,
            days_in_drawdown: 0, // Would need more complex calculation based on history
        })
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp, clippy::get_first, clippy::str_to_string)]
mod tests {
    use super::*;
    use common::Price;
    // operations module removed - use direct imports from common

    fn create_test_pnl_metrics(portfolio_id: &str, pnl: i64) -> PnLMetrics {
        PnLMetrics {
            portfolio_id: portfolio_id.to_string(),
            realized_pnl: Price::from_f64(pnl as f64 * 0.6).unwrap_or(Price::ZERO),
            unrealized_pnl: Price::from_f64(pnl as f64 * 0.4).unwrap_or(Price::ZERO),
            total_unrealized_pnl: Price::from_f64(pnl as f64 * 0.4).unwrap_or(Price::ZERO),
            total_pnl: Price::from_f64(pnl as f64).unwrap_or(Price::ZERO),
            daily_pnl: Price::from_f64(pnl as f64 * 0.1).unwrap_or(Price::ZERO),
            inception_pnl: Price::from_f64(pnl as f64).unwrap_or(Price::ZERO),
            max_drawdown: Price::ZERO,
            current_drawdown_pct: 0.0,
            high_water_mark: Price::from_f64(1000000.0).unwrap_or(Price::ZERO),
            roi_pct: 0.0,
            timestamp: Utc::now().timestamp(),
        }
    }

    #[tokio::test]
    async fn test_drawdown_monitor_creation() {
        let _monitor = DrawdownMonitor::default();
        // Test passes if no panic
    }

    #[tokio::test]
    async fn test_alert_configuration() {
        let monitor = DrawdownMonitor::default();

        let config = DrawdownAlertConfig {
            portfolio_id: Some("test_portfolio".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: true,
        };

        let _ = monitor.configure_alerts(config).await;

        let configs = monitor.alert_configs.read().await;
        assert!(configs.contains_key("test_portfolio"));
    }

    #[tokio::test]
    async fn test_drawdown_calculation() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        // Configure alerts
        let config = DrawdownAlertConfig {
            portfolio_id: Some("test_portfolio".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: true,
        };

        let _ = monitor.configure_alerts(config).await;

        // Simulate P&L progression with drawdown
        let mut pnl_metrics = create_test_pnl_metrics("test_portfolio", 1000000);
        monitor.update_pnl(&pnl_metrics).await?;

        // Simulate drawdown
        pnl_metrics.total_pnl = Price::from_f64(900000.0).unwrap_or(Price::ZERO); // 10% drawdown
        let alerts = monitor.update_pnl(&pnl_metrics).await?;

        assert!(!alerts.is_empty());
        assert_eq!(
            alerts.get(0).map(|a| &a.severity),
            Some(&RiskSeverity::High)
        ); // Should trigger critical alert

        let stats = monitor.get_drawdown_stats("test_portfolio").await?;
        assert!(stats.current_drawdown_pct >= 10.0);
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_emergency_threshold() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        let config = DrawdownAlertConfig {
            portfolio_id: Some("test_portfolio".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: true,
        };

        let _ = monitor.configure_alerts(config).await;

        // Simulate large drawdown
        let mut pnl_metrics = create_test_pnl_metrics("test_portfolio", 1000000);
        pnl_metrics.total_pnl = Price::from_f64(750000.0).unwrap_or(Price::ZERO); // 25% drawdown

        let alerts = monitor.update_pnl(&pnl_metrics).await?;

        assert!(!alerts.is_empty());
        assert_eq!(
            alerts.get(0).map(|a| &a.severity),
            Some(&RiskSeverity::Critical)
        ); // Should trigger emergency alert
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_disabled_alerts() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        let config = DrawdownAlertConfig {
            portfolio_id: Some("test_portfolio".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: false, // Disabled
        };

        let _ = monitor.configure_alerts(config).await;

        // Simulate drawdown
        let mut pnl_metrics = create_test_pnl_metrics("test_portfolio", 1000000);
        pnl_metrics.total_pnl = Price::from_f64(900000.0).unwrap_or(Price::ZERO);

        let alerts = monitor.update_pnl(&pnl_metrics).await?;

        assert!(alerts.is_empty()); // No alerts when disabled
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_zero_hwm() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        // Metrics with zero high water mark
        let mut pnl_metrics = create_test_pnl_metrics("test_portfolio", 1000000);
        pnl_metrics.high_water_mark = Price::ZERO;

        monitor.update_pnl(&pnl_metrics).await?;

        let stats = monitor.get_drawdown_stats("test_portfolio").await?;
        assert_eq!(stats.current_drawdown_pct, 0.0); // Should be 0 when HWM is 0
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_multiple_portfolios() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        // Configure alerts for multiple portfolios
        for i in 1..=3 {
            let config = DrawdownAlertConfig {
                portfolio_id: Some(format!("portfolio_{}", i)),
                warning_threshold: 5.0,
                critical_threshold: 10.0,
                emergency_threshold: 20.0,
                enabled: true,
            };
            let _ = monitor.configure_alerts(config).await;
        }

        // Update P&L for each portfolio
        for i in 1..=3 {
            let pnl_metrics =
                create_test_pnl_metrics(&format!("portfolio_{}", i), 1000000 - (i * 50000));
            monitor.update_pnl(&pnl_metrics).await?;
        }

        // Verify each portfolio has its own stats
        for i in 1..=3 {
            let stats = monitor
                .get_drawdown_stats(&format!("portfolio_{}", i))
                .await?;
            assert!(stats.high_water_mark > 0.0);
        }
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_history_limit() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        // Add more than 1000 P&L entries
        for i in 0..1100 {
            let pnl_metrics = create_test_pnl_metrics("test_portfolio", 1000000 - i);
            monitor.process_pnl(&pnl_metrics).await?;
        }

        let history = monitor.get_pnl_history("test_portfolio").await;
        assert!(history.len() <= 1000); // Should be capped at 1000
        Ok(())
    }

    #[tokio::test]
    async fn test_get_alert_config() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        let config = DrawdownAlertConfig {
            portfolio_id: Some("test_portfolio".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: true,
        };

        let _ = monitor.configure_alerts(config.clone()).await;

        let retrieved_config = monitor.get_alert_config("test_portfolio").await;
        assert!(retrieved_config.is_some());
        assert_eq!(retrieved_config.unwrap().warning_threshold, 5.0);
        Ok(())
    }

    #[tokio::test]
    async fn test_drawdown_stats_empty_portfolio() -> Result<(), Box<dyn std::error::Error>> {
        let monitor = DrawdownMonitor::default();

        let stats = monitor.get_drawdown_stats("nonexistent_portfolio").await?;
        assert_eq!(stats.current_drawdown_pct, 0.0);
        assert_eq!(stats.max_drawdown_pct, 0.0);
        assert_eq!(stats.high_water_mark, 0.0);
        Ok(())
    }

    #[tokio::test]
    async fn test_internal_hwm_ignores_stale_caller_value() -> Result<(), Box<dyn std::error::Error>>
    {
        let monitor = DrawdownMonitor::default();

        // Configure alerts so check_drawdown_thresholds runs
        let config = DrawdownAlertConfig {
            portfolio_id: Some("hwm_test".to_string()),
            warning_threshold: 5.0,
            critical_threshold: 10.0,
            emergency_threshold: 20.0,
            enabled: true,
        };
        let _ = monitor.configure_alerts(config).await;

        // Step 1: Portfolio reaches 1,000,000 total PnL (this sets the real HWM)
        let mut metrics = create_test_pnl_metrics("hwm_test", 1_000_000);
        metrics.high_water_mark = Price::from_f64(1_000_000.0).unwrap_or(Price::ZERO);
        monitor.update_pnl(&metrics).await?;

        // Step 2: Portfolio drops to 900,000 but caller sends stale HWM of 900,000
        // (understating the drawdown from 10% to 0%)
        metrics.total_pnl = Price::from_f64(900_000.0).unwrap_or(Price::ZERO);
        metrics.high_water_mark = Price::from_f64(900_000.0).unwrap_or(Price::ZERO); // STALE!
        let alerts = monitor.update_pnl(&metrics).await?;

        // The internal HWM should still be 1,000,000, so drawdown = 10%
        // which should trigger critical_threshold (10.0)
        assert!(
            !alerts.is_empty(),
            "Expected drawdown alert because internal HWM is 1,000,000 not stale 900,000"
        );

        // Verify get_drawdown_stats also uses the internal HWM
        let stats = monitor.get_drawdown_stats("hwm_test").await?;
        assert!(
            (stats.high_water_mark - 1_000_000.0).abs() < 1.0,
            "Internal HWM should be 1,000,000 but was {}",
            stats.high_water_mark
        );
        assert!(
            stats.current_drawdown_pct >= 9.0,
            "Drawdown should be ~10% but was {}",
            stats.current_drawdown_pct
        );

        Ok(())
    }
}
