//! Drawdown-to-action orchestrator.
//!
//! Maps drawdown severity levels to concrete risk actions using a registry
//! pattern. Maintains an audit log of all enforcement decisions.

use chrono::{DateTime, Utc};

use super::actions::{
    ActionOutcome, AlertSeverity, CloseAllPositionsAction, HaltNewEntriesAction,
    ReducePositionSizeAction, RiskAction, RiskContext,
};

/// Configuration for drawdown thresholds that trigger enforcement actions.
#[derive(Debug, Clone)]
pub struct EnforcerConfig {
    /// Drawdown fraction that triggers Warning (e.g. 0.05 = 5%).
    pub warning_threshold: f64,
    /// Drawdown fraction that triggers Alert (e.g. 0.10 = 10%).
    pub alert_threshold: f64,
    /// Drawdown fraction that triggers Emergency (e.g. 0.15 = 15%).
    pub emergency_threshold: f64,
}

impl Default for EnforcerConfig {
    fn default() -> Self {
        Self {
            warning_threshold: 0.05,
            alert_threshold: 0.10,
            emergency_threshold: 0.15,
        }
    }
}

/// A single entry in the enforcement audit trail.
#[derive(Debug, Clone)]
pub struct AuditEntry {
    /// When the action was executed.
    pub timestamp: DateTime<Utc>,
    /// Severity level that triggered the action.
    pub severity: AlertSeverity,
    /// Name of the action that was executed.
    pub action_name: String,
    /// Outcome of the action execution.
    pub outcome: ActionOutcome,
    /// The drawdown percentage that triggered the action.
    pub drawdown_pct: f64,
}

/// Orchestrates risk enforcement by mapping drawdown levels to actions.
///
/// Maintains a registry of [`RiskAction`] implementations, a shared
/// [`RiskContext`], and an audit log of all enforcement decisions.
pub struct RiskEnforcer {
    config: EnforcerConfig,
    context: RiskContext,
    actions: Vec<Box<dyn RiskAction>>,
    audit_log: Vec<AuditEntry>,
    current_level: Option<AlertSeverity>,
}

impl RiskEnforcer {
    /// Create a new enforcer with default actions:
    /// - `ReducePositionSizeAction(0.5)` at Warning
    /// - `HaltNewEntriesAction` at Alert
    /// - `CloseAllPositionsAction` at Emergency
    pub fn new(config: EnforcerConfig) -> Self {
        let actions: Vec<Box<dyn RiskAction>> = vec![
            Box::new(ReducePositionSizeAction::new(0.5)),
            Box::new(HaltNewEntriesAction),
            Box::new(CloseAllPositionsAction),
        ];
        Self {
            config,
            context: RiskContext::default(),
            actions,
            audit_log: Vec::new(),
            current_level: None,
        }
    }

    /// Determine severity from drawdown percentage and execute matching actions.
    ///
    /// Uses absolute drawdown (positive number = loss). If drawdown drops below
    /// the warning threshold and the enforcer is currently in an elevated state,
    /// triggers recovery by rolling back all actions at or above the previous level.
    ///
    /// Returns all action outcomes produced.
    pub fn on_drawdown(&mut self, drawdown_pct: f64) -> Vec<ActionOutcome> {
        let new_level = self.classify_drawdown(drawdown_pct);

        match (&self.current_level, &new_level) {
            // Below warning and we were elevated -- recover
            (Some(_), None) => {
                let outcomes = self.execute_recovery(drawdown_pct);
                self.current_level = None;
                outcomes
            }
            // Below warning and we were already clear -- nothing
            (None, None) => Vec::new(),
            // At or above warning -- execute actions for this level
            (_, Some(level)) => {
                let lvl = *level;
                let outcomes = self.execute_actions(lvl, drawdown_pct);
                self.current_level = Some(lvl);
                outcomes
            }
        }
    }

    /// Read-only access to the current risk context.
    pub fn context(&self) -> &RiskContext {
        &self.context
    }

    /// Read-only access to the audit log.
    pub fn audit_log(&self) -> &[AuditEntry] {
        &self.audit_log
    }

    /// Current severity level, or `None` if drawdown is below warning.
    pub fn current_level(&self) -> Option<AlertSeverity> {
        self.current_level
    }

    // --- private helpers ---

    /// Classify a drawdown percentage into a severity level.
    fn classify_drawdown(&self, drawdown_pct: f64) -> Option<AlertSeverity> {
        if drawdown_pct >= self.config.emergency_threshold {
            Some(AlertSeverity::Emergency)
        } else if drawdown_pct >= self.config.alert_threshold {
            Some(AlertSeverity::Alert)
        } else if drawdown_pct >= self.config.warning_threshold {
            Some(AlertSeverity::Warning)
        } else {
            None
        }
    }

    /// Execute all actions whose severity is at or below the given level.
    fn execute_actions(
        &mut self,
        level: AlertSeverity,
        drawdown_pct: f64,
    ) -> Vec<ActionOutcome> {
        let now = Utc::now();
        let mut outcomes = Vec::new();

        for i in 0..self.actions.len() {
            let action_severity = self.actions.get(i).map(|a| a.severity());
            let action_name = self.actions.get(i).map(|a| a.name().to_owned());

            if let (Some(sev), Some(name)) = (action_severity, action_name) {
                if severity_ord(sev) <= severity_ord(level) {
                    if let Some(action) = self.actions.get(i) {
                        let outcome = action.execute(&mut self.context);
                        self.audit_log.push(AuditEntry {
                            timestamp: now,
                            severity: sev,
                            action_name: name,
                            outcome: outcome.clone(),
                            drawdown_pct,
                        });
                        outcomes.push(outcome);
                    }
                }
            }
        }
        outcomes
    }

    /// Roll back all registered actions (recovery from elevated state).
    fn execute_recovery(&mut self, drawdown_pct: f64) -> Vec<ActionOutcome> {
        let now = Utc::now();
        let mut outcomes = Vec::new();

        for i in 0..self.actions.len() {
            let action_severity = self.actions.get(i).map(|a| a.severity());
            let action_name = self.actions.get(i).map(|a| a.name().to_owned());

            if let (Some(sev), Some(name)) = (action_severity, action_name) {
                if let Some(action) = self.actions.get(i) {
                    let outcome = action.rollback(&mut self.context);
                    self.audit_log.push(AuditEntry {
                        timestamp: now,
                        severity: sev,
                        action_name: name,
                        outcome: outcome.clone(),
                        drawdown_pct,
                    });
                    outcomes.push(outcome);
                }
            }
        }
        outcomes
    }
}

/// Map severity to an ordinal for comparison.
fn severity_ord(s: AlertSeverity) -> u8 {
    match s {
        AlertSeverity::Warning => 0,
        AlertSeverity::Alert => 1,
        AlertSeverity::Emergency => 2,
    }
}

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

    #[test]
    fn test_no_action_below_warning() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        let outcomes = enforcer.on_drawdown(0.02); // 2% -- below 5% warning
        assert!(outcomes.is_empty());
        assert!(enforcer.current_level().is_none());
    }

    #[test]
    fn test_warning_triggers_position_reduction() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        let outcomes = enforcer.on_drawdown(0.06); // 6% -- above 5% warning
        assert!(!outcomes.is_empty());
        assert!(enforcer.context().position_size_multiplier < 1.0);
        assert_eq!(enforcer.current_level(), Some(AlertSeverity::Warning));
    }

    #[test]
    fn test_alert_halts_entries() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        let outcomes = enforcer.on_drawdown(0.12); // 12% -- above 10% alert
        assert!(!outcomes.is_empty());
        assert!(enforcer.context().halt_new_entries);
        assert_eq!(enforcer.current_level(), Some(AlertSeverity::Alert));
    }

    #[test]
    fn test_emergency_closes_all() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        let outcomes = enforcer.on_drawdown(0.20); // 20% -- above 15% emergency
        assert!(!outcomes.is_empty());
        assert!(enforcer.context().close_all);
        assert_eq!(enforcer.current_level(), Some(AlertSeverity::Emergency));
    }

    #[test]
    fn test_audit_log_populated() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        enforcer.on_drawdown(0.06);
        assert!(!enforcer.audit_log().is_empty());
    }

    #[test]
    fn test_recovery_on_drawdown_below_warning() {
        let mut enforcer = RiskEnforcer::new(EnforcerConfig::default());
        enforcer.on_drawdown(0.06); // trigger warning
        assert!(enforcer.context().position_size_multiplier < 1.0);
        let outcomes = enforcer.on_drawdown(0.01); // recovery
        assert!(!outcomes.is_empty()); // rollback actions
        assert!(enforcer.current_level().is_none());
    }
}
