//! Operating mode state machine for the trading pipeline.
//!
//! Four modes with validated transitions:
//! Backtest -> Paper -> Shadow -> Live
//! Any mode -> Backtest (always allowed for testing)
//! No skipping forward (e.g. Backtest -> Live is not allowed)

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

/// Operating mode for the trading pipeline.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OperatingMode {
    /// Historical replay, no real orders.
    Backtest,
    /// Simulated orders against live data.
    Paper,
    /// Real-time signals but no execution, compared against paper.
    Shadow,
    /// Live trading with real orders.
    Live,
}

impl fmt::Display for OperatingMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Backtest => write!(f, "Backtest"),
            Self::Paper => write!(f, "Paper"),
            Self::Shadow => write!(f, "Shadow"),
            Self::Live => write!(f, "Live"),
        }
    }
}

/// Error when an invalid mode transition is attempted.
#[derive(Debug, Clone)]
pub struct TransitionError {
    pub from: OperatingMode,
    pub to: OperatingMode,
    pub reason: String,
}

impl fmt::Display for TransitionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Invalid transition {} -> {}: {}",
            self.from, self.to, self.reason
        )
    }
}

impl std::error::Error for TransitionError {}

/// Validate whether a mode transition is allowed.
pub fn validate_transition(from: OperatingMode, to: OperatingMode) -> Result<(), TransitionError> {
    if from == to {
        return Ok(()); // No-op
    }

    // Any mode -> Backtest is always allowed
    if to == OperatingMode::Backtest {
        return Ok(());
    }

    let allowed = matches!(
        (from, to),
        (OperatingMode::Backtest, OperatingMode::Paper)
            | (OperatingMode::Paper, OperatingMode::Shadow)
            | (OperatingMode::Shadow, OperatingMode::Live)
    );

    if allowed {
        Ok(())
    } else {
        Err(TransitionError {
            from,
            to,
            reason:
                "Only sequential forward transitions or fallback to Backtest are allowed"
                    .to_string(),
        })
    }
}

/// Operating mode state machine.
#[derive(Debug, Clone)]
pub struct ModeStateMachine {
    current: OperatingMode,
    history: Vec<(OperatingMode, OperatingMode)>, // (from, to) pairs
}

impl ModeStateMachine {
    pub fn new(initial: OperatingMode) -> Self {
        Self {
            current: initial,
            history: Vec::new(),
        }
    }

    /// Attempt to transition to a new mode.
    pub fn transition(&mut self, to: OperatingMode) -> Result<(), TransitionError> {
        validate_transition(self.current, to)?;
        let from = self.current;
        self.current = to;
        self.history.push((from, to));
        Ok(())
    }

    /// Current operating mode.
    pub fn current(&self) -> OperatingMode {
        self.current
    }

    /// Transition history.
    pub fn history(&self) -> &[(OperatingMode, OperatingMode)] {
        &self.history
    }

    /// Whether the system is in live trading mode.
    pub fn is_live(&self) -> bool {
        self.current == OperatingMode::Live
    }
}

impl Default for ModeStateMachine {
    fn default() -> Self {
        Self::new(OperatingMode::Backtest)
    }
}

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

    #[test]
    fn test_sequential_forward_transitions() {
        let mut fsm = ModeStateMachine::default();
        assert_eq!(fsm.current(), OperatingMode::Backtest);
        assert!(fsm.transition(OperatingMode::Paper).is_ok());
        assert!(fsm.transition(OperatingMode::Shadow).is_ok());
        assert!(fsm.transition(OperatingMode::Live).is_ok());
        assert!(fsm.is_live());
    }

    #[test]
    fn test_skip_forward_rejected() {
        let mut fsm = ModeStateMachine::default();
        let result = fsm.transition(OperatingMode::Shadow);
        assert!(result.is_err());
        assert_eq!(fsm.current(), OperatingMode::Backtest); // unchanged
    }

    #[test]
    fn test_backtest_fallback_always_allowed() {
        let mut fsm = ModeStateMachine::new(OperatingMode::Live);
        assert!(fsm.transition(OperatingMode::Backtest).is_ok());
        assert_eq!(fsm.current(), OperatingMode::Backtest);
    }

    #[test]
    fn test_same_mode_noop() {
        let mut fsm = ModeStateMachine::default();
        assert!(fsm.transition(OperatingMode::Backtest).is_ok());
        assert_eq!(fsm.current(), OperatingMode::Backtest);
    }

    #[test]
    fn test_history_tracked() {
        let mut fsm = ModeStateMachine::default();
        fsm.transition(OperatingMode::Paper).ok();
        fsm.transition(OperatingMode::Shadow).ok();
        assert_eq!(fsm.history().len(), 2);
    }

    #[test]
    fn test_transition_error_display() {
        let err = TransitionError {
            from: OperatingMode::Backtest,
            to: OperatingMode::Live,
            reason:
                "Only sequential forward transitions or fallback to Backtest are allowed"
                    .to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("Backtest"));
        assert!(msg.contains("Live"));
    }
}
