//! RiskGate pipeline stage.
//!
//! Aggregates risk checks (kill switch, drawdown, correlation, drift, position limits)
//! and either logs or enforces them depending on the operating mode.

use super::messages::{PipelineMessage, RiskCheckedOrder};
use super::stages::{PipelineStage, StageResult};

/// Risk gate operating mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiskGateMode {
    /// Log risk violations but allow orders through.
    LogOnly,
    /// Enforce risk limits -- reject orders that fail checks.
    Enforcing,
}

/// Result of a single risk check.
#[derive(Debug, Clone)]
pub struct RiskCheckResult {
    pub check_name: String,
    pub passed: bool,
    pub message: String,
}

/// Configuration for the risk gate.
#[derive(Debug)]
pub struct RiskGateConfig {
    pub mode: RiskGateMode,
    /// Maximum allowed drawdown (as fraction, e.g. 0.15 = 15%).
    pub max_drawdown: f64,
    /// Maximum position size as fraction of portfolio.
    pub max_position_pct: f64,
}

impl Default for RiskGateConfig {
    fn default() -> Self {
        Self {
            mode: RiskGateMode::LogOnly,
            max_drawdown: 0.15,
            max_position_pct: 0.10,
        }
    }
}

/// RiskGate pipeline stage.
///
/// Aggregates multiple lightweight risk checks and either logs violations
/// (in `LogOnly` mode) or rejects orders that fail checks (in `Enforcing` mode).
pub struct RiskGate {
    config: RiskGateConfig,
    current_drawdown: f64,
    kill_switch_active: bool,
}

impl std::fmt::Debug for RiskGate {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RiskGate")
            .field("mode", &self.config.mode)
            .field("max_drawdown", &self.config.max_drawdown)
            .field("current_drawdown", &self.current_drawdown)
            .field("kill_switch_active", &self.kill_switch_active)
            .finish()
    }
}

impl RiskGate {
    /// Create a new `RiskGate` with the given configuration.
    pub fn new(config: RiskGateConfig) -> Self {
        Self {
            config,
            current_drawdown: 0.0,
            kill_switch_active: false,
        }
    }

    /// Update the current drawdown value.
    pub fn set_drawdown(&mut self, drawdown: f64) {
        self.current_drawdown = drawdown;
    }

    /// Set kill switch state.
    pub fn set_kill_switch(&mut self, active: bool) {
        self.kill_switch_active = active;
    }

    /// Run all risk checks for a given symbol and quantity.
    fn check_risks(&self, symbol: &str, quantity: f64) -> Vec<RiskCheckResult> {
        let mut results = Vec::new();

        // Kill switch check
        results.push(RiskCheckResult {
            check_name: "kill_switch".to_string(),
            passed: !self.kill_switch_active,
            message: if self.kill_switch_active {
                "Kill switch is active".to_string()
            } else {
                "Kill switch inactive".to_string()
            },
        });

        // Drawdown check
        results.push(RiskCheckResult {
            check_name: "drawdown".to_string(),
            passed: self.current_drawdown < self.config.max_drawdown,
            message: format!(
                "Drawdown {:.2}% vs limit {:.2}%",
                self.current_drawdown * 100.0,
                self.config.max_drawdown * 100.0
            ),
        });

        // Position size check (simplified: quantity vs max_position_pct * notional base)
        let _ = symbol; // Used for logging context in production
        let position_ok = quantity.abs() <= self.config.max_position_pct * 1_000_000.0;
        results.push(RiskCheckResult {
            check_name: "position_size".to_string(),
            passed: position_ok,
            message: format!("Position size {quantity:.2} checked"),
        });

        results
    }
}

impl PipelineStage for RiskGate {
    fn process(&self, msg: PipelineMessage) -> StageResult {
        match msg {
            PipelineMessage::Sized(order) => {
                let checks = self.check_risks(&order.symbol, order.quantity);
                let all_passed = checks.iter().all(|c| c.passed);
                let rejection_reason = if all_passed {
                    None
                } else {
                    Some(
                        checks
                            .iter()
                            .filter(|c| !c.passed)
                            .map(|c| format!("{}: {}", c.check_name, c.message))
                            .collect::<Vec<_>>()
                            .join("; "),
                    )
                };

                match self.config.mode {
                    RiskGateMode::LogOnly => {
                        // Log but pass through -- always approved in log-only mode
                        Ok(PipelineMessage::RiskChecked(RiskCheckedOrder {
                            order,
                            risk_approved: true,
                            rejection_reason: None,
                        }))
                    }
                    RiskGateMode::Enforcing => {
                        if all_passed {
                            Ok(PipelineMessage::RiskChecked(RiskCheckedOrder {
                                order,
                                risk_approved: true,
                                rejection_reason: None,
                            }))
                        } else {
                            Ok(PipelineMessage::Rejected {
                                reason: rejection_reason
                                    .unwrap_or_else(|| "Unknown risk failure".to_string()),
                                original_symbol: order.symbol,
                            })
                        }
                    }
                }
            }
            other => {
                // Pass through non-Sized messages unchanged
                Ok(other)
            }
        }
    }

    fn name(&self) -> &str {
        "risk_gate"
    }
}

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

    fn make_order(symbol: &str, quantity: f64) -> PipelineMessage {
        PipelineMessage::Sized(SizedOrder {
            symbol: symbol.to_string(),
            direction: SignalDirection::Long,
            quantity,
            confidence: 0.8,
            model_id: "test".to_string(),
        })
    }

    #[test]
    fn test_log_only_always_approves() {
        let gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::LogOnly,
            max_drawdown: 0.05,
            ..RiskGateConfig::default()
        });
        // Even with violations, log-only should approve
        let msg = make_order("EURUSD", 1000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::RiskChecked(checked)) = result {
            assert!(checked.risk_approved);
        } else {
            panic!("Expected RiskChecked message");
        }
    }

    #[test]
    fn test_enforcing_rejects_on_kill_switch() {
        let mut gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::Enforcing,
            ..RiskGateConfig::default()
        });
        gate.set_kill_switch(true);
        let msg = make_order("EURUSD", 1000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::Rejected { reason, .. }) = result {
            assert!(reason.contains("kill_switch"));
        } else {
            panic!("Expected Rejected message");
        }
    }

    #[test]
    fn test_enforcing_rejects_on_drawdown() {
        let mut gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::Enforcing,
            max_drawdown: 0.10,
            ..RiskGateConfig::default()
        });
        gate.set_drawdown(0.12); // 12% > 10% limit
        let msg = make_order("EURUSD", 1000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::Rejected { reason, .. }) = result {
            assert!(reason.contains("drawdown"));
        } else {
            panic!("Expected Rejected message");
        }
    }

    #[test]
    fn test_enforcing_approves_clean_order() {
        let gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::Enforcing,
            ..RiskGateConfig::default()
        });
        let msg = make_order("EURUSD", 1000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::RiskChecked(checked)) = result {
            assert!(checked.risk_approved);
        } else {
            panic!("Expected RiskChecked message");
        }
    }

    #[test]
    fn test_passthrough_non_sized_messages() {
        let gate = RiskGate::new(RiskGateConfig::default());
        let msg = PipelineMessage::Prediction(PredictionSignal {
            symbol: "EURUSD".to_string(),
            direction: SignalDirection::Long,
            confidence: 0.8,
            model_id: "test".to_string(),
        });
        let result = gate.process(msg);
        assert!(result.is_ok());
    }

    #[test]
    fn test_risk_gate_debug_format() {
        let gate = RiskGate::new(RiskGateConfig::default());
        let debug_str = format!("{gate:?}");
        assert!(debug_str.contains("RiskGate"));
        assert!(debug_str.contains("LogOnly"));
    }

    #[test]
    fn test_risk_gate_name() {
        let gate = RiskGate::new(RiskGateConfig::default());
        assert_eq!(gate.name(), "risk_gate");
    }

    #[test]
    fn test_set_drawdown() {
        let mut gate = RiskGate::new(RiskGateConfig::default());
        gate.set_drawdown(0.05);
        let debug_str = format!("{gate:?}");
        assert!(debug_str.contains("0.05"));
    }

    #[test]
    fn test_enforcing_rejects_oversized_position() {
        let gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::Enforcing,
            max_position_pct: 0.01, // 1% of 1M = 10,000
            ..RiskGateConfig::default()
        });
        // Quantity 50,000 exceeds 10,000 limit
        let msg = make_order("EURUSD", 50_000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::Rejected { reason, .. }) = result {
            assert!(reason.contains("position_size"));
        } else {
            panic!("Expected Rejected message for oversized position");
        }
    }

    #[test]
    fn test_multiple_violations_combined() {
        let mut gate = RiskGate::new(RiskGateConfig {
            mode: RiskGateMode::Enforcing,
            max_drawdown: 0.05,
            max_position_pct: 0.01,
        });
        gate.set_kill_switch(true);
        gate.set_drawdown(0.10);
        let msg = make_order("EURUSD", 50_000.0);
        let result = gate.process(msg);
        assert!(result.is_ok());
        if let Ok(PipelineMessage::Rejected { reason, .. }) = result {
            // Should mention multiple failures
            assert!(reason.contains("kill_switch"));
            assert!(reason.contains("drawdown"));
        } else {
            panic!("Expected Rejected message with multiple violations");
        }
    }
}
