//! Pluggable risk actions that execute in response to risk signals.

use serde::{Deserialize, Serialize};
use std::fmt;

/// Alert severity levels matching drawdown monitor thresholds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AlertSeverity {
    Warning,
    Alert,
    Emergency,
}

impl fmt::Display for AlertSeverity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Warning => write!(f, "Warning"),
            Self::Alert => write!(f, "Alert"),
            Self::Emergency => write!(f, "Emergency"),
        }
    }
}

/// Outcome of a risk action execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionOutcome {
    pub action_name: String,
    pub success: bool,
    pub message: String,
}

/// Mutable risk context that actions modify.
#[derive(Debug, Clone)]
pub struct RiskContext {
    /// Multiplier for new position sizes (1.0 = full, 0.5 = half, 0.0 = none).
    pub position_size_multiplier: f64,
    /// Whether new entries are halted.
    pub halt_new_entries: bool,
    /// Whether all positions should be closed.
    pub close_all: bool,
    /// Whether stops should be tightened (and by how much, as fraction).
    pub tighten_stops_by: Option<f64>,
}

impl Default for RiskContext {
    fn default() -> Self {
        Self {
            position_size_multiplier: 1.0,
            halt_new_entries: false,
            close_all: false,
            tighten_stops_by: None,
        }
    }
}

/// Trait for pluggable risk response actions.
pub trait RiskAction: Send + Sync + fmt::Debug {
    /// Severity level this action responds to.
    fn severity(&self) -> AlertSeverity;

    /// Execute the action, modifying the risk context.
    fn execute(&self, context: &mut RiskContext) -> ActionOutcome;

    /// Rollback the action (for false triggers).
    fn rollback(&self, context: &mut RiskContext) -> ActionOutcome;

    /// Human-readable name.
    fn name(&self) -> &str;
}

/// Reduce new position sizes by a given fraction.
#[derive(Debug)]
pub struct ReducePositionSizeAction {
    reduction_factor: f64,
}

impl ReducePositionSizeAction {
    pub fn new(reduction_factor: f64) -> Self {
        Self {
            reduction_factor: reduction_factor.clamp(0.0, 1.0),
        }
    }
}

impl RiskAction for ReducePositionSizeAction {
    fn severity(&self) -> AlertSeverity {
        AlertSeverity::Warning
    }

    fn execute(&self, context: &mut RiskContext) -> ActionOutcome {
        context.position_size_multiplier *= self.reduction_factor;
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: format!(
                "Position size multiplier reduced to {:.2}",
                context.position_size_multiplier
            ),
        }
    }

    fn rollback(&self, context: &mut RiskContext) -> ActionOutcome {
        if self.reduction_factor > 0.0 {
            context.position_size_multiplier /= self.reduction_factor;
        }
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: format!(
                "Position size multiplier restored to {:.2}",
                context.position_size_multiplier
            ),
        }
    }

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

/// Halt all new trade entries.
#[derive(Debug)]
pub struct HaltNewEntriesAction;

impl RiskAction for HaltNewEntriesAction {
    fn severity(&self) -> AlertSeverity {
        AlertSeverity::Alert
    }

    fn execute(&self, context: &mut RiskContext) -> ActionOutcome {
        context.halt_new_entries = true;
        context.tighten_stops_by = Some(0.25);
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: "New entries halted, stops tightened by 25%".to_owned(),
        }
    }

    fn rollback(&self, context: &mut RiskContext) -> ActionOutcome {
        context.halt_new_entries = false;
        context.tighten_stops_by = None;
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: "New entries re-enabled, stops restored".to_owned(),
        }
    }

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

/// Close all open positions (emergency).
#[derive(Debug)]
pub struct CloseAllPositionsAction;

impl RiskAction for CloseAllPositionsAction {
    fn severity(&self) -> AlertSeverity {
        AlertSeverity::Emergency
    }

    fn execute(&self, context: &mut RiskContext) -> ActionOutcome {
        context.close_all = true;
        context.halt_new_entries = true;
        context.position_size_multiplier = 0.0;
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: "All positions marked for closure, trading halted".to_owned(),
        }
    }

    fn rollback(&self, context: &mut RiskContext) -> ActionOutcome {
        context.close_all = false;
        // Don't restore halt_new_entries or multiplier -- recovery mode handles that
        ActionOutcome {
            action_name: self.name().to_owned(),
            success: true,
            message: "Close-all flag cleared (recovery mode will restore trading)".to_owned(),
        }
    }

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

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

    #[test]
    fn test_reduce_position_action_severity() {
        let action = ReducePositionSizeAction::new(0.5);
        assert_eq!(action.severity(), AlertSeverity::Warning);
    }

    #[test]
    fn test_halt_new_entries_severity() {
        let action = HaltNewEntriesAction;
        assert_eq!(action.severity(), AlertSeverity::Alert);
    }

    #[test]
    fn test_close_all_positions_severity() {
        let action = CloseAllPositionsAction;
        assert_eq!(action.severity(), AlertSeverity::Emergency);
    }

    #[test]
    fn test_risk_context_default() {
        let ctx = RiskContext::default();
        assert!((ctx.position_size_multiplier - 1.0).abs() < f64::EPSILON);
        assert!(!ctx.halt_new_entries);
        assert!(!ctx.close_all);
    }

    #[test]
    fn test_reduce_position_execute_and_rollback() {
        let action = ReducePositionSizeAction::new(0.5);
        let mut ctx = RiskContext::default();

        let outcome = action.execute(&mut ctx);
        assert!(outcome.success);
        assert!((ctx.position_size_multiplier - 0.5).abs() < f64::EPSILON);

        let outcome = action.rollback(&mut ctx);
        assert!(outcome.success);
        assert!((ctx.position_size_multiplier - 1.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_halt_new_entries_execute_and_rollback() {
        let action = HaltNewEntriesAction;
        let mut ctx = RiskContext::default();

        let outcome = action.execute(&mut ctx);
        assert!(outcome.success);
        assert!(ctx.halt_new_entries);
        assert_eq!(ctx.tighten_stops_by, Some(0.25));

        let outcome = action.rollback(&mut ctx);
        assert!(outcome.success);
        assert!(!ctx.halt_new_entries);
        assert_eq!(ctx.tighten_stops_by, None);
    }

    #[test]
    fn test_close_all_positions_execute_and_rollback() {
        let action = CloseAllPositionsAction;
        let mut ctx = RiskContext::default();

        let outcome = action.execute(&mut ctx);
        assert!(outcome.success);
        assert!(ctx.close_all);
        assert!(ctx.halt_new_entries);
        assert!((ctx.position_size_multiplier).abs() < f64::EPSILON);

        let outcome = action.rollback(&mut ctx);
        assert!(outcome.success);
        assert!(!ctx.close_all);
    }

    #[test]
    fn test_reduction_factor_clamped() {
        let action = ReducePositionSizeAction::new(2.0);
        let mut ctx = RiskContext::default();
        action.execute(&mut ctx);
        // Factor clamped to 1.0, so multiplier stays at 1.0
        assert!((ctx.position_size_multiplier - 1.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_alert_severity_display() {
        assert_eq!(format!("{}", AlertSeverity::Warning), "Warning");
        assert_eq!(format!("{}", AlertSeverity::Alert), "Alert");
        assert_eq!(format!("{}", AlertSeverity::Emergency), "Emergency");
    }
}
