//! Daemon state machine implementation.
//!
//! Only tracks state
//! All game data is stored in events and processed at game end.

use std::sync::Arc;

use parking_lot::RwLock;
use tracing::{info, trace, warn};

use super::DaemonStatus;
use crate::lqp::GameflowPhase;

/// Internal daemon state.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DaemonState {
    /// Idle - League Client not detected.
    Idle,
    /// Monitoring - League Client running, waiting for game.
    Monitoring,
    /// Recording - Active recording in progress.
    Recording,
    /// Error - Recoverable error state.
    Error,
    /// ShuttingDown - Daemon is shutting down.
    ShuttingDown,
}

impl From<DaemonState> for DaemonStatus {
    fn from(state: DaemonState) -> Self {
        match state {
            DaemonState::Idle => DaemonStatus::Idle,
            DaemonState::Monitoring => DaemonStatus::Monitoring,
            DaemonState::Recording => DaemonStatus::Recording,
            DaemonState::Error => DaemonStatus::Error,
            DaemonState::ShuttingDown => DaemonStatus::ShuttingDown,
        }
    }
}

/// State transition event.
///
/// Simplified: Only tracks state changes, no data passing.
/// Data is stored in events and processed at game end.
#[derive(Debug, Clone)]
pub enum StateTransition {
    /// League Client started.
    ClientStarted,
    /// League Client stopped.
    ClientStopped,
    /// Game started
    GameStarted,
    /// Game ended
    GameEnded,
    /// Error occurred.
    Error(String),
    /// Error recovered.
    Recovered,
    /// Shutdown requested.
    Shutdown,
    /// Gameflow phase changed.
    PhaseChanged(GameflowPhase),
}

/// State machine for daemon state management.
pub struct DaemonStateMachine {
    /// Current state.
    state: Arc<RwLock<DaemonState>>,
    /// Last error message.
    last_error: Arc<RwLock<Option<String>>>,
    /// Current game ID (if recording).
    current_game_id: Arc<RwLock<Option<u64>>>,
    /// Current champion (if recording).
    current_champion: Arc<RwLock<Option<String>>>,
}

impl DaemonStateMachine {
    /// Create a new state machine.
    pub fn new() -> Self {
        Self {
            state: Arc::new(RwLock::new(DaemonState::Idle)),
            last_error: Arc::new(RwLock::new(None)),
            current_game_id: Arc::new(RwLock::new(None)),
            current_champion: Arc::new(RwLock::new(None)),
        }
    }

    /// Get the current state.
    pub fn current_state(&self) -> DaemonState {
        *self.state.read()
    }

    /// Get the current status (for external reporting).
    pub fn status(&self) -> DaemonStatus {
        self.current_state().into()
    }

    /// Get the current game ID.
    pub fn current_game_id(&self) -> Option<u64> {
        *self.current_game_id.read()
    }

    /// Get the current champion.
    pub fn current_champion(&self) -> Option<String> {
        self.current_champion.read().clone()
    }

    /// Get the last error.
    pub fn last_error(&self) -> Option<String> {
        self.last_error.read().clone()
    }

    /// Check if currently recording.
    pub fn is_recording(&self) -> bool {
        *self.state.read() == DaemonState::Recording
    }

    /// Check if monitoring for games.
    pub fn is_monitoring(&self) -> bool {
        matches!(
            *self.state.read(),
            DaemonState::Monitoring | DaemonState::Recording
        )
    }

    /// Process a state transition.
    ///
    /// Returns the new state if the transition was valid.
    pub fn transition(&self, transition: StateTransition) -> Option<DaemonState> {
        let current = self.current_state();
        let new_state = self.apply_transition(current, transition.clone())?;

        if new_state != current {
            info!(
                "State transition: {:?} -> {:?} (via {:?})",
                current, new_state, transition
            );
            *self.state.write() = new_state;

            // Update related state
            match &transition {
                StateTransition::GameStarted => {
                    // Game ID and champion are now tracked in events, not state
                }
                StateTransition::GameEnded => {
                    // Clear state tracking
                    *self.current_game_id.write() = None;
                    *self.current_champion.write() = None;
                }
                StateTransition::Error(msg) => {
                    *self.last_error.write() = Some(msg.clone());
                }
                StateTransition::Recovered => {
                    *self.last_error.write() = None;
                }
                _ => {}
            }
        }

        Some(new_state)
    }

    /// Apply a transition to determine the new state.
    fn apply_transition(
        &self,
        current: DaemonState,
        transition: StateTransition,
    ) -> Option<DaemonState> {
        match (current, &transition) {
            // From Idle
            (DaemonState::Idle, StateTransition::ClientStarted) => Some(DaemonState::Monitoring),
            (DaemonState::Idle, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),

            // From Monitoring
            (DaemonState::Monitoring, StateTransition::ClientStopped) => Some(DaemonState::Idle),
            (DaemonState::Monitoring, StateTransition::GameStarted) => Some(DaemonState::Recording),
            (DaemonState::Monitoring, StateTransition::Error(_)) => Some(DaemonState::Error),
            (DaemonState::Monitoring, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),

            // From Recording
            (DaemonState::Recording, StateTransition::GameEnded) => Some(DaemonState::Monitoring),
            // Allow GameStarted from Recording (handles case where GameEnded wasn't received)
            (DaemonState::Recording, StateTransition::GameStarted) => Some(DaemonState::Recording),
            (DaemonState::Recording, StateTransition::ClientStopped) => Some(DaemonState::Idle),
            (DaemonState::Recording, StateTransition::Error(_)) => Some(DaemonState::Error),
            (DaemonState::Recording, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),

            // From Error
            (DaemonState::Error, StateTransition::Recovered) => Some(DaemonState::Idle),
            (DaemonState::Error, StateTransition::ClientStopped) => Some(DaemonState::Idle),
            (DaemonState::Error, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),

            // Invalid transitions
            _ => {
                warn!(
                    "Invalid state transition: {:?} with {:?}",
                    current, transition
                );
                None
            }
        }
    }

    /// Process a game event and potentially trigger a transition.
    ///
    /// Uses event_type string and raw_data instead of typed GameEvent.
    /// Only returns state transitions — all data is stored in raw events.
    pub fn process_event(
        &self,
        event_type: &str,
        raw_data: &serde_json::Value,
    ) -> Option<StateTransition> {
        trace!(
            "Processing event in state {:?}: type={}",
            self.current_state(),
            event_type
        );

        match event_type {
            crate::lqp::EVENT_TYPE_GAME_START => Some(StateTransition::GameStarted),
            crate::lqp::EVENT_TYPE_GAME_END => Some(StateTransition::GameEnded),
            crate::lqp::EVENT_TYPE_PHASE_CHANGE => {
                // Only trigger GameEnded on EndOfGame phase (stats are available by then)
                let phase = raw_data
                    .as_str()
                    .or_else(|| raw_data.get("phase").and_then(|v| v.as_str()))
                    .unwrap_or("");
                if phase == "EndOfGame" && self.is_recording() {
                    Some(StateTransition::GameEnded)
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    /// Force a state (for testing or recovery).
    pub fn force_state(&self, state: DaemonState) {
        *self.state.write() = state;
    }
}

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

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

    #[test]
    fn test_initial_state() {
        let machine = DaemonStateMachine::new();
        assert_eq!(machine.current_state(), DaemonState::Idle);
    }

    #[test]
    fn test_client_start_transition() {
        let machine = DaemonStateMachine::new();

        let new_state = machine.transition(StateTransition::ClientStarted);
        assert_eq!(new_state, Some(DaemonState::Monitoring));
        assert_eq!(machine.current_state(), DaemonState::Monitoring);
    }

    #[test]
    fn test_game_start_transition() {
        let machine = DaemonStateMachine::new();

        machine.transition(StateTransition::ClientStarted);
        let new_state = machine.transition(StateTransition::GameStarted);

        assert_eq!(new_state, Some(DaemonState::Recording));
    }

    #[test]
    fn test_invalid_transition() {
        let machine = DaemonStateMachine::new();

        // Can't start recording from Idle
        let result = machine.transition(StateTransition::GameStarted);

        assert_eq!(result, None);
        assert_eq!(machine.current_state(), DaemonState::Idle);
    }

    #[test]
    fn test_error_recovery() {
        let machine = DaemonStateMachine::new();

        machine.transition(StateTransition::ClientStarted);
        machine.transition(StateTransition::Error("Test error".to_string()));

        assert_eq!(machine.current_state(), DaemonState::Error);
        assert_eq!(machine.last_error(), Some("Test error".to_string()));

        machine.transition(StateTransition::Recovered);
        assert_eq!(machine.current_state(), DaemonState::Idle);
        assert_eq!(machine.last_error(), None);
    }

    #[test]
    fn test_process_event_game_start() {
        let machine = DaemonStateMachine::new();
        machine.transition(StateTransition::ClientStarted);

        let raw = serde_json::json!({"gameId": 12345});
        let transition = machine.process_event(crate::lqp::EVENT_TYPE_GAME_START, &raw);
        assert!(matches!(transition, Some(StateTransition::GameStarted)));
    }

    #[test]
    fn test_process_event_phase_change_end_of_game() {
        let machine = DaemonStateMachine::new();
        machine.transition(StateTransition::ClientStarted);
        machine.transition(StateTransition::GameStarted);

        // Phase change to EndOfGame while recording should trigger GameEnded
        let raw = serde_json::json!("EndOfGame");
        let transition = machine.process_event(crate::lqp::EVENT_TYPE_PHASE_CHANGE, &raw);
        assert!(matches!(transition, Some(StateTransition::GameEnded)));
    }
}
