//! Maps drift detection results to risk response recommendations.
//!
//! Bridges the drift detector (which detects feature/prediction/performance drift)
//! to concrete action recommendations (position reduction, halting, retraining).
//!
//! Note: The `risk` crate's enforcement types (`AlertSeverity`, etc.) are in
//! `risk::enforcement::actions`. Since `ml` cannot depend on `risk` (circular
//! dependency), we define lightweight local types that mirror the risk actions.

use serde::{Deserialize, Serialize};

/// Type of drift detected.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DriftType {
    /// Input feature distribution has shifted.
    Feature,
    /// Model prediction distribution has shifted.
    Prediction,
    /// Trading performance metrics have degraded.
    Performance,
}

/// Severity of the recommended response.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ResponseSeverity {
    /// Monitor only, no action needed.
    Monitor,
    /// Reduce position sizes.
    ReduceExposure,
    /// Halt new entries, recommend retraining.
    HaltAndRetrain,
    /// Emergency: close positions.
    Emergency,
}

/// A recommended action from the drift responder.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriftResponse {
    pub drift_type: DriftType,
    pub score: f64,
    pub severity: ResponseSeverity,
    pub recommended_actions: Vec<String>,
    pub retrain_recommended: bool,
}

/// Configuration for drift response thresholds.
#[derive(Debug, Clone)]
pub struct DriftResponderConfig {
    /// Feature drift thresholds: (monitor, reduce, halt, emergency).
    pub feature_thresholds: (f64, f64, f64, f64),
    /// Prediction drift thresholds.
    pub prediction_thresholds: (f64, f64, f64, f64),
    /// Performance drift thresholds.
    pub performance_thresholds: (f64, f64, f64, f64),
}

impl Default for DriftResponderConfig {
    fn default() -> Self {
        Self {
            feature_thresholds: (0.3, 0.5, 0.7, 0.9),
            prediction_thresholds: (0.2, 0.4, 0.6, 0.8),
            performance_thresholds: (0.25, 0.45, 0.65, 0.85),
        }
    }
}

/// Maps drift detection results to risk response recommendations.
#[derive(Debug)]
pub struct DriftResponder {
    config: DriftResponderConfig,
}

impl DriftResponder {
    pub fn new(config: DriftResponderConfig) -> Self {
        Self { config }
    }

    /// Evaluate a drift detection result and return the recommended response.
    pub fn evaluate(&self, drift_type: DriftType, score: f64) -> DriftResponse {
        let thresholds = match drift_type {
            DriftType::Feature => &self.config.feature_thresholds,
            DriftType::Prediction => &self.config.prediction_thresholds,
            DriftType::Performance => &self.config.performance_thresholds,
        };

        let (severity, actions, retrain) = if score >= thresholds.3 {
            (
                ResponseSeverity::Emergency,
                vec![
                    "close_all_positions".to_owned(),
                    "halt_new_entries".to_owned(),
                    "trigger_retraining".to_owned(),
                ],
                true,
            )
        } else if score >= thresholds.2 {
            (
                ResponseSeverity::HaltAndRetrain,
                vec![
                    "halt_new_entries".to_owned(),
                    "trigger_retraining".to_owned(),
                ],
                true,
            )
        } else if score >= thresholds.1 {
            (
                ResponseSeverity::ReduceExposure,
                vec!["reduce_position_size".to_owned()],
                false,
            )
        } else if score >= thresholds.0 {
            (
                ResponseSeverity::Monitor,
                vec!["log_drift_event".to_owned()],
                false,
            )
        } else {
            (ResponseSeverity::Monitor, Vec::new(), false)
        };

        DriftResponse {
            drift_type,
            score,
            severity,
            recommended_actions: actions,
            retrain_recommended: retrain,
        }
    }

    /// Evaluate multiple drift signals and return the highest-severity response.
    pub fn evaluate_all(&self, signals: &[(DriftType, f64)]) -> Option<DriftResponse> {
        signals
            .iter()
            .map(|&(dt, score)| self.evaluate(dt, score))
            .max_by(|a, b| a.severity.cmp(&b.severity))
    }
}

impl Default for DriftResponder {
    fn default() -> Self {
        Self::new(DriftResponderConfig::default())
    }
}

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

    #[test]
    fn test_low_drift_is_monitor() {
        let responder = DriftResponder::default();
        let response = responder.evaluate(DriftType::Feature, 0.1);
        assert_eq!(response.severity, ResponseSeverity::Monitor);
        assert!(!response.retrain_recommended);
        assert!(response.recommended_actions.is_empty());
    }

    #[test]
    fn test_moderate_drift_reduces_exposure() {
        let responder = DriftResponder::default();
        let response = responder.evaluate(DriftType::Feature, 0.55);
        assert_eq!(response.severity, ResponseSeverity::ReduceExposure);
        assert!(!response.retrain_recommended);
        assert!(response
            .recommended_actions
            .iter()
            .any(|a| a.contains("reduce")));
    }

    #[test]
    fn test_high_drift_halts_and_retrains() {
        let responder = DriftResponder::default();
        let response = responder.evaluate(DriftType::Prediction, 0.65);
        assert_eq!(response.severity, ResponseSeverity::HaltAndRetrain);
        assert!(response.retrain_recommended);
    }

    #[test]
    fn test_extreme_drift_is_emergency() {
        let responder = DriftResponder::default();
        let response = responder.evaluate(DriftType::Performance, 0.90);
        assert_eq!(response.severity, ResponseSeverity::Emergency);
        assert!(response.retrain_recommended);
        assert!(response
            .recommended_actions
            .iter()
            .any(|a| a.contains("close")));
    }

    #[test]
    fn test_evaluate_all_returns_worst() {
        let responder = DriftResponder::default();
        let signals = vec![
            (DriftType::Feature, 0.1),     // Monitor
            (DriftType::Prediction, 0.65), // HaltAndRetrain
            (DriftType::Performance, 0.4), // Monitor threshold
        ];
        let response = responder.evaluate_all(&signals);
        assert!(response.is_some());
        if let Some(r) = response {
            assert_eq!(r.severity, ResponseSeverity::HaltAndRetrain);
        }
    }

    #[test]
    fn test_evaluate_all_empty_returns_none() {
        let responder = DriftResponder::default();
        let response = responder.evaluate_all(&[]);
        assert!(response.is_none());
    }
}
