//! System state observability snapshot.
//!
//! Aggregates health, risk, and model state into a single serializable
//! structure for monitoring and alerting (Prometheus/Grafana).

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Complete system state snapshot.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemState {
    /// When this snapshot was captured.
    pub timestamp: DateTime<Utc>,
    /// Pipeline health status.
    pub pipeline_health: PipelineHealth,
    /// Current drawdown state.
    pub drawdown: DrawdownState,
    /// Active positions summary.
    pub positions: PositionsSummary,
    /// Drift detection scores per model.
    pub drift_scores: HashMap<String, f64>,
    /// Model versions currently loaded.
    pub model_versions: HashMap<String, ModelInfo>,
    /// Risk enforcement state.
    pub risk_state: RiskState,
}

/// Pipeline processing health.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipelineHealth {
    /// Whether the pipeline is currently running.
    pub is_running: bool,
    /// Messages processed in the last minute.
    pub messages_per_minute: u64,
    /// Number of rejected messages in the last minute.
    pub rejections_per_minute: u64,
    /// Average latency in microseconds.
    pub avg_latency_us: f64,
}

impl Default for PipelineHealth {
    fn default() -> Self {
        Self {
            is_running: false,
            messages_per_minute: 0,
            rejections_per_minute: 0,
            avg_latency_us: 0.0,
        }
    }
}

/// Current drawdown information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DrawdownState {
    /// Current drawdown as fraction (0.05 = 5%).
    pub current_pct: f64,
    /// Maximum drawdown seen today.
    pub max_today_pct: f64,
    /// Whether any drawdown threshold is breached.
    pub threshold_breached: bool,
    /// Active alert severity, if any.
    pub active_severity: Option<String>,
}

impl Default for DrawdownState {
    fn default() -> Self {
        Self {
            current_pct: 0.0,
            max_today_pct: 0.0,
            threshold_breached: false,
            active_severity: None,
        }
    }
}

/// Summary of open positions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionsSummary {
    /// Number of open positions.
    pub open_count: u32,
    /// Total exposure in base currency.
    pub total_exposure: f64,
    /// Per-symbol exposure.
    pub per_symbol: HashMap<String, f64>,
}

impl Default for PositionsSummary {
    fn default() -> Self {
        Self {
            open_count: 0,
            total_exposure: 0.0,
            per_symbol: HashMap::new(),
        }
    }
}

/// Information about a loaded model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelInfo {
    /// Semantic version of the model.
    pub version: String,
    /// When the model was loaded.
    pub loaded_at: DateTime<Utc>,
    /// Total inferences run since load.
    pub inference_count: u64,
    /// Average inference latency in microseconds.
    pub avg_inference_us: f64,
}

/// Risk enforcement state.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskState {
    /// Whether the kill switch is active.
    pub kill_switch_active: bool,
    /// Current position size multiplier (1.0 = full size).
    pub position_multiplier: f64,
    /// Whether new entries are halted.
    pub entries_halted: bool,
    /// Whether in recovery mode.
    pub in_recovery: bool,
    /// Recovery multiplier if in recovery.
    pub recovery_multiplier: Option<f64>,
}

impl Default for RiskState {
    fn default() -> Self {
        Self {
            kill_switch_active: false,
            position_multiplier: 1.0,
            entries_halted: false,
            in_recovery: false,
            recovery_multiplier: None,
        }
    }
}

impl SystemState {
    /// Create a new system state snapshot at the current time.
    pub fn capture(
        pipeline_health: PipelineHealth,
        drawdown: DrawdownState,
        positions: PositionsSummary,
        drift_scores: HashMap<String, f64>,
        model_versions: HashMap<String, ModelInfo>,
        risk_state: RiskState,
    ) -> Self {
        Self {
            timestamp: Utc::now(),
            pipeline_health,
            drawdown,
            positions,
            drift_scores,
            model_versions,
            risk_state,
        }
    }

    /// Check if the system is in a healthy state.
    ///
    /// A system is healthy when:
    /// - The pipeline is running
    /// - The kill switch is not active
    /// - No drawdown threshold is breached
    /// - New entries are not halted
    pub fn is_healthy(&self) -> bool {
        self.pipeline_health.is_running
            && !self.risk_state.kill_switch_active
            && !self.drawdown.threshold_breached
            && !self.risk_state.entries_halted
    }

    /// Serialize to JSON string.
    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(self)
    }
}

impl Default for SystemState {
    fn default() -> Self {
        Self {
            timestamp: Utc::now(),
            pipeline_health: PipelineHealth::default(),
            drawdown: DrawdownState::default(),
            positions: PositionsSummary::default(),
            drift_scores: HashMap::new(),
            model_versions: HashMap::new(),
            risk_state: RiskState::default(),
        }
    }
}

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

    #[test]
    fn test_default_system_state_not_healthy() {
        let state = SystemState::default();
        // Default pipeline is not running, so not healthy
        assert!(!state.is_healthy());
    }

    #[test]
    fn test_healthy_system() {
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                messages_per_minute: 100,
                rejections_per_minute: 0,
                avg_latency_us: 50.0,
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            HashMap::new(),
            HashMap::new(),
            RiskState::default(),
        );
        assert!(state.is_healthy());
    }

    #[test]
    fn test_kill_switch_makes_unhealthy() {
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            HashMap::new(),
            HashMap::new(),
            RiskState {
                kill_switch_active: true,
                ..RiskState::default()
            },
        );
        assert!(!state.is_healthy());
    }

    #[test]
    fn test_serialization_roundtrip() {
        let state = SystemState::default();
        let json = state.to_json();
        assert!(json.is_ok());
        if let Ok(json_str) = json {
            let deserialized: Result<SystemState, _> = serde_json::from_str(&json_str);
            assert!(deserialized.is_ok());
        }
    }

    #[test]
    fn test_drawdown_breach_makes_unhealthy() {
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState {
                threshold_breached: true,
                ..DrawdownState::default()
            },
            PositionsSummary::default(),
            HashMap::new(),
            HashMap::new(),
            RiskState::default(),
        );
        assert!(!state.is_healthy());
    }

    #[test]
    fn test_entries_halted_makes_unhealthy() {
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            HashMap::new(),
            HashMap::new(),
            RiskState {
                entries_halted: true,
                ..RiskState::default()
            },
        );
        assert!(!state.is_healthy());
    }

    #[test]
    fn test_pipeline_health_default() {
        let health = PipelineHealth::default();
        assert!(!health.is_running);
        assert_eq!(health.messages_per_minute, 0);
        assert_eq!(health.rejections_per_minute, 0);
        assert!((health.avg_latency_us - 0.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_risk_state_default() {
        let risk = RiskState::default();
        assert!(!risk.kill_switch_active);
        assert!((risk.position_multiplier - 1.0).abs() < f64::EPSILON);
        assert!(!risk.entries_halted);
        assert!(!risk.in_recovery);
        assert!(risk.recovery_multiplier.is_none());
    }

    #[test]
    fn test_positions_summary_default() {
        let pos = PositionsSummary::default();
        assert_eq!(pos.open_count, 0);
        assert!((pos.total_exposure - 0.0).abs() < f64::EPSILON);
        assert!(pos.per_symbol.is_empty());
    }

    #[test]
    fn test_drawdown_state_default() {
        let dd = DrawdownState::default();
        assert!((dd.current_pct - 0.0).abs() < f64::EPSILON);
        assert!((dd.max_today_pct - 0.0).abs() < f64::EPSILON);
        assert!(!dd.threshold_breached);
        assert!(dd.active_severity.is_none());
    }

    #[test]
    fn test_system_state_with_drift_scores() {
        let mut drift = HashMap::new();
        drift.insert("dqn_v1".to_string(), 0.03);
        drift.insert("ppo_v2".to_string(), 0.15);
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            drift,
            HashMap::new(),
            RiskState::default(),
        );
        assert!(state.is_healthy());
        assert_eq!(state.drift_scores.len(), 2);
    }

    #[test]
    fn test_system_state_with_model_versions() {
        let mut models = HashMap::new();
        models.insert(
            "dqn".to_string(),
            ModelInfo {
                version: "1.2.3".to_string(),
                loaded_at: Utc::now(),
                inference_count: 1000,
                avg_inference_us: 45.0,
            },
        );
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            HashMap::new(),
            models,
            RiskState::default(),
        );
        assert!(state.is_healthy());
        assert_eq!(state.model_versions.len(), 1);
    }

    #[test]
    fn test_recovery_mode_still_healthy() {
        // Recovery mode alone does not make the system unhealthy
        let state = SystemState::capture(
            PipelineHealth {
                is_running: true,
                ..PipelineHealth::default()
            },
            DrawdownState::default(),
            PositionsSummary::default(),
            HashMap::new(),
            HashMap::new(),
            RiskState {
                in_recovery: true,
                recovery_multiplier: Some(0.5),
                ..RiskState::default()
            },
        );
        assert!(state.is_healthy());
    }
}
