//! Workspace lifecycle states and transitions.
//!
//! Manages the state machine for workspace lifecycles:
//! Initializing → Active ⇄ Suspended → Disposed

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

use tuppu_persistence::WorkspaceId;

// =============================================================================
// WorkspaceState
// =============================================================================

/// State of a workspace in its lifecycle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WorkspaceState {
    /// Being initialized, not yet ready.
    Initializing,
    /// Active and ready for use.
    Active,
    /// Temporarily suspended (can be resumed).
    Suspended,
    /// Permanently disposed (terminal state).
    Disposed,
}

impl Default for WorkspaceState {
    fn default() -> Self {
        WorkspaceState::Initializing
    }
}

impl WorkspaceState {
    /// Check if this state allows evaluation.
    pub fn can_evaluate(&self) -> bool {
        matches!(self, WorkspaceState::Active)
    }

    /// Check if this state allows modification.
    pub fn can_modify(&self) -> bool {
        matches!(self, WorkspaceState::Active | WorkspaceState::Initializing)
    }

    /// Check if this is a terminal state.
    pub fn is_terminal(&self) -> bool {
        matches!(self, WorkspaceState::Disposed)
    }

    /// Check if transition to another state is valid.
    pub fn can_transition_to(&self, to: WorkspaceState) -> bool {
        can_transition(*self, to)
    }
}

/// Check if a state transition is valid.
pub fn can_transition(from: WorkspaceState, to: WorkspaceState) -> bool {
    use WorkspaceState::*;
    matches!(
        (from, to),
        // From Initializing
        (Initializing, Active) |
        (Initializing, Disposed) |
        // From Active
        (Active, Suspended) |
        (Active, Disposed) |
        // From Suspended
        (Suspended, Active) |
        (Suspended, Disposed)
        // From Disposed: no transitions allowed
    )
}

/// Check if evaluation is allowed in a state.
pub fn can_evaluate(state: WorkspaceState) -> bool {
    state.can_evaluate()
}

/// Check if modification is allowed in a state.
pub fn can_modify(state: WorkspaceState) -> bool {
    state.can_modify()
}

/// Check if state is terminal.
pub fn is_terminal(state: WorkspaceState) -> bool {
    state.is_terminal()
}

// =============================================================================
// LifecycleEvent
// =============================================================================

/// A lifecycle state transition event.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LifecycleEvent {
    /// ID of the workspace.
    pub workspace_id: WorkspaceId,
    /// Previous state.
    pub from: WorkspaceState,
    /// New state.
    pub to: WorkspaceState,
    /// Timestamp (Unix epoch milliseconds).
    pub timestamp: u64,
    /// Optional reason for the transition.
    pub reason: Option<String>,
}

impl LifecycleEvent {
    /// Create a new lifecycle event.
    pub fn new(workspace_id: WorkspaceId, from: WorkspaceState, to: WorkspaceState) -> Self {
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis() as u64)
            .unwrap_or(0);
        
        Self {
            workspace_id,
            from,
            to,
            timestamp,
            reason: None,
        }
    }

    /// Create with a reason.
    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
        self.reason = Some(reason.into());
        self
    }
}

// =============================================================================
// LifecycleManager
// =============================================================================

/// Manages workspace lifecycle states.
#[derive(Debug, Clone, Default)]
pub struct LifecycleManager {
    /// Current states of all workspaces.
    states: HashMap<WorkspaceId, WorkspaceState>,
    /// History of all lifecycle events.
    history: Vec<LifecycleEvent>,
}

/// Errors during lifecycle transitions.
#[derive(Debug, Clone, thiserror::Error)]
pub enum LifecycleError {
    /// Workspace not found.
    #[error("workspace not found: {0}")]
    NotFound(WorkspaceId),
    
    /// Invalid state transition.
    #[error("invalid transition from {from:?} to {to:?}")]
    InvalidTransition {
        from: WorkspaceState,
        to: WorkspaceState,
    },
    
    /// Operation not allowed in current state.
    #[error("operation not allowed in state {state:?}: {operation}")]
    NotAllowed {
        state: WorkspaceState,
        operation: String,
    },
}

impl LifecycleManager {
    /// Create a new lifecycle manager.
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a new workspace (starts in Initializing state).
    pub fn register(&mut self, id: WorkspaceId) -> WorkspaceState {
        let state = WorkspaceState::Initializing;
        self.states.insert(id, state);
        state
    }

    /// Get the current state of a workspace.
    pub fn get_state(&self, id: WorkspaceId) -> Option<WorkspaceState> {
        self.states.get(&id).copied()
    }

    /// Transition a workspace to a new state.
    pub fn transition(
        &mut self,
        id: WorkspaceId,
        to: WorkspaceState,
        reason: Option<String>,
    ) -> Result<LifecycleEvent, LifecycleError> {
        let from = self.states.get(&id)
            .copied()
            .ok_or(LifecycleError::NotFound(id))?;

        if !can_transition(from, to) {
            return Err(LifecycleError::InvalidTransition { from, to });
        }

        self.states.insert(id, to);

        let mut event = LifecycleEvent::new(id, from, to);
        if let Some(r) = reason {
            event = event.with_reason(r);
        }

        self.history.push(event.clone());
        Ok(event)
    }

    /// Activate a workspace.
    pub fn activate(&mut self, id: WorkspaceId) -> Result<LifecycleEvent, LifecycleError> {
        self.transition(id, WorkspaceState::Active, None)
    }

    /// Suspend a workspace.
    pub fn suspend(&mut self, id: WorkspaceId) -> Result<LifecycleEvent, LifecycleError> {
        self.transition(id, WorkspaceState::Suspended, None)
    }

    /// Dispose a workspace.
    pub fn dispose(&mut self, id: WorkspaceId, reason: Option<String>) -> Result<LifecycleEvent, LifecycleError> {
        self.transition(id, WorkspaceState::Disposed, reason)
    }

    /// Get history for a specific workspace.
    pub fn history_for(&self, id: WorkspaceId) -> Vec<&LifecycleEvent> {
        self.history.iter().filter(|e| e.workspace_id == id).collect()
    }

    /// Get all history.
    pub fn history(&self) -> &[LifecycleEvent] {
        &self.history
    }

    /// Get all workspaces in a specific state.
    pub fn workspaces_in_state(&self, state: WorkspaceState) -> Vec<WorkspaceId> {
        self.states
            .iter()
            .filter(|(_, &s)| s == state)
            .map(|(&id, _)| id)
            .collect()
    }

    /// Get all active workspace IDs.
    pub fn active_workspaces(&self) -> Vec<WorkspaceId> {
        self.workspaces_in_state(WorkspaceState::Active)
    }

    /// Check if operation is allowed for a workspace.
    pub fn check_can_evaluate(&self, id: WorkspaceId) -> Result<(), LifecycleError> {
        let state = self.states.get(&id)
            .copied()
            .ok_or(LifecycleError::NotFound(id))?;

        if !state.can_evaluate() {
            return Err(LifecycleError::NotAllowed {
                state,
                operation: "evaluate".to_string(),
            });
        }
        Ok(())
    }

    /// Check if modification is allowed for a workspace.
    pub fn check_can_modify(&self, id: WorkspaceId) -> Result<(), LifecycleError> {
        let state = self.states.get(&id)
            .copied()
            .ok_or(LifecycleError::NotFound(id))?;

        if !state.can_modify() {
            return Err(LifecycleError::NotAllowed {
                state,
                operation: "modify".to_string(),
            });
        }
        Ok(())
    }

    /// Remove a disposed workspace from tracking.
    pub fn remove(&mut self, id: WorkspaceId) -> bool {
        if let Some(state) = self.states.get(&id) {
            if *state == WorkspaceState::Disposed {
                self.states.remove(&id);
                return true;
            }
        }
        false
    }
}

// =============================================================================
// Tests
// =============================================================================

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

    #[test]
    fn state_initializing() {
        let state = WorkspaceState::Initializing;
        assert!(!state.can_evaluate());
        assert!(state.can_modify());
        assert!(!state.is_terminal());
    }

    #[test]
    fn state_active() {
        let state = WorkspaceState::Active;
        assert!(state.can_evaluate());
        assert!(state.can_modify());
        assert!(!state.is_terminal());
    }

    #[test]
    fn state_suspended() {
        let state = WorkspaceState::Suspended;
        assert!(!state.can_evaluate());
        assert!(!state.can_modify());
        assert!(!state.is_terminal());
    }

    #[test]
    fn state_disposed() {
        let state = WorkspaceState::Disposed;
        assert!(!state.can_evaluate());
        assert!(!state.can_modify());
        assert!(state.is_terminal());
    }

    #[test]
    fn valid_transitions() {
        use WorkspaceState::*;
        
        // Valid transitions
        assert!(can_transition(Initializing, Active));
        assert!(can_transition(Initializing, Disposed));
        assert!(can_transition(Active, Suspended));
        assert!(can_transition(Active, Disposed));
        assert!(can_transition(Suspended, Active));
        assert!(can_transition(Suspended, Disposed));
    }

    #[test]
    fn invalid_transitions() {
        use WorkspaceState::*;
        
        // Invalid transitions
        assert!(!can_transition(Disposed, Active));
        assert!(!can_transition(Disposed, Suspended));
        assert!(!can_transition(Disposed, Initializing));
        assert!(!can_transition(Active, Initializing));
        assert!(!can_transition(Suspended, Initializing));
    }

    #[test]
    fn transition_event() {
        let id = WorkspaceId::new();
        let event = LifecycleEvent::new(id, WorkspaceState::Active, WorkspaceState::Suspended);
        
        assert_eq!(event.workspace_id, id);
        assert_eq!(event.from, WorkspaceState::Active);
        assert_eq!(event.to, WorkspaceState::Suspended);
        assert!(event.timestamp > 0);
    }

    #[test]
    fn event_with_reason() {
        let id = WorkspaceId::new();
        let event = LifecycleEvent::new(id, WorkspaceState::Active, WorkspaceState::Disposed)
            .with_reason("cleanup");
        
        assert_eq!(event.reason, Some("cleanup".to_string()));
    }

    #[test]
    fn lifecycle_manager_record() {
        let mut mgr = LifecycleManager::new();
        let id = WorkspaceId::new();
        
        mgr.register(id);
        assert_eq!(mgr.get_state(id), Some(WorkspaceState::Initializing));
        
        mgr.activate(id).unwrap();
        assert_eq!(mgr.get_state(id), Some(WorkspaceState::Active));
    }

    #[test]
    fn lifecycle_manager_query() {
        let mut mgr = LifecycleManager::new();
        let id1 = WorkspaceId::new();
        let id2 = WorkspaceId::new();
        
        mgr.register(id1);
        mgr.register(id2);
        mgr.activate(id1).unwrap();
        mgr.activate(id2).unwrap();
        mgr.suspend(id2).unwrap();
        
        let active = mgr.active_workspaces();
        assert_eq!(active.len(), 1);
        assert!(active.contains(&id1));
    }

    #[test]
    fn event_history() {
        let mut mgr = LifecycleManager::new();
        let id = WorkspaceId::new();
        
        mgr.register(id);
        mgr.activate(id).unwrap();
        mgr.suspend(id).unwrap();
        mgr.activate(id).unwrap();
        
        let history = mgr.history_for(id);
        assert_eq!(history.len(), 3);
    }

    #[test]
    fn can_transition_active_to_suspended() {
        assert!(can_transition(WorkspaceState::Active, WorkspaceState::Suspended));
    }

    #[test]
    fn can_transition_suspended_to_active() {
        assert!(can_transition(WorkspaceState::Suspended, WorkspaceState::Active));
    }

    #[test]
    fn cannot_transition_disposed() {
        assert!(!can_transition(WorkspaceState::Disposed, WorkspaceState::Active));
        assert!(!can_transition(WorkspaceState::Disposed, WorkspaceState::Suspended));
    }

    #[test]
    fn can_evaluate_active() {
        assert!(can_evaluate(WorkspaceState::Active));
    }

    #[test]
    fn cannot_evaluate_suspended() {
        assert!(!can_evaluate(WorkspaceState::Suspended));
    }

    #[test]
    fn can_modify_active() {
        assert!(can_modify(WorkspaceState::Active));
    }

    #[test]
    fn is_terminal_disposed() {
        assert!(is_terminal(WorkspaceState::Disposed));
        assert!(!is_terminal(WorkspaceState::Active));
    }
}
