//! IAHU - The meta-registry for all workspaces.
//!
//! IAHU (from Sumerian "house/temple") manages workspace lifecycles,
//! cross-workspace evaluation, and the global namespace.

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

use tuppu_algebra::Signature;
use tuppu_persistence::WorkspaceId;
use tuppu_scalar::Scalar;

use crate::lifecycle::{LifecycleError, LifecycleManager, WorkspaceState};

// =============================================================================
// WorkspaceInfo
// =============================================================================

/// Core information about a workspace.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkspaceInfo {
    /// Unique identifier.
    pub id: WorkspaceId,
    /// Human-readable name.
    pub name: String,
    /// Geometric algebra signature.
    pub signature: Signature,
    /// Creation timestamp.
    pub created_at: u64,
}

impl WorkspaceInfo {
    /// Create new workspace info.
    pub fn new(name: impl Into<String>, signature: Signature) -> Self {
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);
        
        Self {
            id: WorkspaceId::new(),
            name: name.into(),
            signature,
            created_at: timestamp,
        }
    }
}

// =============================================================================
// Binding
// =============================================================================

/// A named binding in a workspace.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Binding {
    /// Binding name.
    pub name: String,
    /// Current value (if evaluated).
    pub value: Option<BindingValue>,
    /// Expression source (if derived).
    pub expression: Option<String>,
    /// Last modification timestamp.
    pub modified_at: u64,
}

/// Value of a binding.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BindingValue {
    /// Scalar value.
    Scalar(Scalar),
    /// Multivector value.
    Multivector(tuppu_algebra::ExactMv),
}

impl Binding {
    /// Create a scalar binding.
    pub fn scalar(name: impl Into<String>, value: Scalar) -> Self {
        Self {
            name: name.into(),
            value: Some(BindingValue::Scalar(value)),
            expression: None,
            modified_at: now(),
        }
    }

    /// Create a multivector binding.
    pub fn multivector(name: impl Into<String>, value: tuppu_algebra::ExactMv) -> Self {
        Self {
            name: name.into(),
            value: Some(BindingValue::Multivector(value)),
            expression: None,
            modified_at: now(),
        }
    }

    /// Create an expression binding.
    pub fn expression(name: impl Into<String>, expr: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            value: None,
            expression: Some(expr.into()),
            modified_at: now(),
        }
    }
}

fn now() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0)
}

// =============================================================================
// BindingRef
// =============================================================================

/// Reference to a binding, possibly in another workspace.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BindingRef {
    /// Workspace ID (None = current workspace).
    pub workspace: Option<WorkspaceId>,
    /// Binding name.
    pub name: String,
}

impl BindingRef {
    /// Create a local binding reference.
    pub fn local(name: impl Into<String>) -> Self {
        Self {
            workspace: None,
            name: name.into(),
        }
    }

    /// Create a cross-workspace binding reference.
    pub fn remote(workspace: WorkspaceId, name: impl Into<String>) -> Self {
        Self {
            workspace: Some(workspace),
            name: name.into(),
        }
    }

    /// Parse a reference string like "workspace::binding" or just "binding".
    pub fn parse(s: &str, resolver: impl Fn(&str) -> Option<WorkspaceId>) -> Option<Self> {
        if let Some((ws_name, binding)) = s.split_once("::") {
            let ws_id = resolver(ws_name)?;
            Some(Self::remote(ws_id, binding))
        } else {
            Some(Self::local(s))
        }
    }
}

// =============================================================================
// ManagedWorkspace
// =============================================================================

/// A workspace managed by IAHU.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManagedWorkspace {
    /// Core information.
    pub info: WorkspaceInfo,
    /// Current lifecycle state.
    pub state: WorkspaceState,
    /// User-defined tags.
    pub tags: Vec<String>,
    /// User-defined metadata.
    pub metadata: HashMap<String, String>,
    /// Bindings in this workspace.
    pub bindings: HashMap<String, Binding>,
}

impl ManagedWorkspace {
    /// Create a new managed workspace.
    pub fn new(info: WorkspaceInfo) -> Self {
        Self {
            info,
            state: WorkspaceState::Initializing,
            tags: Vec::new(),
            metadata: HashMap::new(),
            bindings: HashMap::new(),
        }
    }

    /// Get a binding by name.
    pub fn get_binding(&self, name: &str) -> Option<&Binding> {
        self.bindings.get(name)
    }

    /// Set a binding.
    pub fn set_binding(&mut self, binding: Binding) {
        self.bindings.insert(binding.name.clone(), binding);
    }

    /// Remove a binding.
    pub fn remove_binding(&mut self, name: &str) -> Option<Binding> {
        self.bindings.remove(name)
    }

    /// List all binding names.
    pub fn binding_names(&self) -> Vec<&str> {
        self.bindings.keys().map(|s| s.as_str()).collect()
    }
}

// =============================================================================
// IahuError
// =============================================================================

/// Errors from IAHU operations.
#[derive(Debug, Clone, thiserror::Error)]
pub enum IahuError {
    /// Workspace not found.
    #[error("workspace not found: {0}")]
    WorkspaceNotFound(WorkspaceId),
    
    /// Workspace name not found.
    #[error("workspace name not found: {0}")]
    NameNotFound(String),
    
    /// Binding not found.
    #[error("binding not found: {workspace}::{binding}")]
    BindingNotFound {
        workspace: WorkspaceId,
        binding: String,
    },
    
    /// Duplicate workspace name.
    #[error("duplicate workspace name: {0}")]
    DuplicateName(String),
    
    /// Lifecycle error.
    #[error("lifecycle error: {0}")]
    Lifecycle(#[from] LifecycleError),
    
    /// Evaluation error.
    #[error("evaluation error: {0}")]
    Evaluation(String),
    
    /// Cross-workspace error.
    #[error("cross-workspace error: {0}")]
    CrossWorkspace(String),
}

// =============================================================================
// Iahu
// =============================================================================

/// The IAHU meta-registry for all workspaces.
#[derive(Debug, Clone, Default)]
pub struct Iahu {
    /// All managed workspaces.
    workspaces: HashMap<WorkspaceId, ManagedWorkspace>,
    /// Name to ID index.
    name_index: HashMap<String, WorkspaceId>,
    /// Lifecycle manager.
    lifecycle: LifecycleManager,
    /// Currently active workspace (for context).
    active_workspace: Option<WorkspaceId>,
}

impl Iahu {
    /// Create a new empty IAHU registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a new workspace.
    pub fn create_workspace(
        &mut self,
        name: impl Into<String>,
        signature: Signature,
    ) -> Result<WorkspaceId, IahuError> {
        let name = name.into();
        
        if self.name_index.contains_key(&name) {
            return Err(IahuError::DuplicateName(name));
        }

        let info = WorkspaceInfo::new(&name, signature);
        let id = info.id;
        
        let workspace = ManagedWorkspace::new(info);
        self.workspaces.insert(id, workspace);
        self.name_index.insert(name, id);
        self.lifecycle.register(id);

        Ok(id)
    }

    /// Get a workspace by ID.
    pub fn get(&self, id: WorkspaceId) -> Option<&ManagedWorkspace> {
        self.workspaces.get(&id)
    }

    /// Get a mutable workspace by ID.
    pub fn get_mut(&mut self, id: WorkspaceId) -> Option<&mut ManagedWorkspace> {
        self.workspaces.get_mut(&id)
    }

    /// Get a workspace by name.
    pub fn get_by_name(&self, name: &str) -> Option<&ManagedWorkspace> {
        let id = self.name_index.get(name)?;
        self.workspaces.get(id)
    }

    /// Get workspace ID by name.
    pub fn id_by_name(&self, name: &str) -> Option<WorkspaceId> {
        self.name_index.get(name).copied()
    }

    /// Activate a workspace.
    pub fn activate(&mut self, id: WorkspaceId) -> Result<(), IahuError> {
        self.lifecycle.activate(id)?;
        if let Some(ws) = self.workspaces.get_mut(&id) {
            ws.state = WorkspaceState::Active;
        }
        Ok(())
    }

    /// Suspend a workspace.
    pub fn suspend(&mut self, id: WorkspaceId) -> Result<(), IahuError> {
        self.lifecycle.suspend(id)?;
        if let Some(ws) = self.workspaces.get_mut(&id) {
            ws.state = WorkspaceState::Suspended;
        }
        Ok(())
    }

    /// Dispose a workspace.
    pub fn dispose(&mut self, id: WorkspaceId) -> Result<(), IahuError> {
        self.lifecycle.dispose(id, None)?;
        if let Some(ws) = self.workspaces.get_mut(&id) {
            ws.state = WorkspaceState::Disposed;
        }
        Ok(())
    }

    /// Resume a suspended workspace.
    pub fn resume(&mut self, id: WorkspaceId) -> Result<(), IahuError> {
        self.activate(id)
    }

    /// Get all active workspace IDs.
    pub fn active_workspace_ids(&self) -> Vec<WorkspaceId> {
        self.lifecycle.active_workspaces()
    }

    /// Set metadata on a workspace.
    pub fn set_metadata(
        &mut self,
        id: WorkspaceId,
        key: impl Into<String>,
        value: impl Into<String>,
    ) -> Result<(), IahuError> {
        let ws = self.workspaces.get_mut(&id)
            .ok_or(IahuError::WorkspaceNotFound(id))?;
        ws.metadata.insert(key.into(), value.into());
        Ok(())
    }

    /// Get metadata from a workspace.
    pub fn get_metadata(&self, id: WorkspaceId, key: &str) -> Option<&str> {
        self.workspaces.get(&id)?.metadata.get(key).map(|s| s.as_str())
    }

    /// Add a tag to a workspace.
    pub fn add_tag(&mut self, id: WorkspaceId, tag: impl Into<String>) -> Result<(), IahuError> {
        let ws = self.workspaces.get_mut(&id)
            .ok_or(IahuError::WorkspaceNotFound(id))?;
        let tag = tag.into();
        if !ws.tags.contains(&tag) {
            ws.tags.push(tag);
        }
        Ok(())
    }

    /// Remove a tag from a workspace.
    pub fn remove_tag(&mut self, id: WorkspaceId, tag: &str) -> Result<bool, IahuError> {
        let ws = self.workspaces.get_mut(&id)
            .ok_or(IahuError::WorkspaceNotFound(id))?;
        let len_before = ws.tags.len();
        ws.tags.retain(|t| t != tag);
        Ok(ws.tags.len() < len_before)
    }

    /// Get tags for a workspace.
    pub fn get_tags(&self, id: WorkspaceId) -> Option<&[String]> {
        self.workspaces.get(&id).map(|ws| ws.tags.as_slice())
    }

    /// Set the active workspace context.
    pub fn set_active_workspace(&mut self, id: Option<WorkspaceId>) {
        self.active_workspace = id;
    }

    /// Get the active workspace context.
    pub fn active_workspace(&self) -> Option<WorkspaceId> {
        self.active_workspace
    }

    // =========================================================================
    // Binding operations
    // =========================================================================

    /// Set a binding in a workspace.
    pub fn set_binding(
        &mut self,
        ws_id: WorkspaceId,
        binding: Binding,
    ) -> Result<(), IahuError> {
        self.lifecycle.check_can_modify(ws_id)?;
        
        let ws = self.workspaces.get_mut(&ws_id)
            .ok_or(IahuError::WorkspaceNotFound(ws_id))?;
        ws.set_binding(binding);
        Ok(())
    }

    /// Get a binding from a workspace.
    pub fn get_binding(&self, ws_id: WorkspaceId, name: &str) -> Result<&Binding, IahuError> {
        let ws = self.workspaces.get(&ws_id)
            .ok_or(IahuError::WorkspaceNotFound(ws_id))?;
        ws.get_binding(name)
            .ok_or(IahuError::BindingNotFound {
                workspace: ws_id,
                binding: name.to_string(),
            })
    }

    // =========================================================================
    // Cross-workspace evaluation
    // =========================================================================

    /// Resolve a binding reference to its value.
    pub fn resolve_reference(
        &self,
        reference: &BindingRef,
        context: WorkspaceId,
    ) -> Result<&BindingValue, IahuError> {
        let ws_id = reference.workspace.unwrap_or(context);
        
        // Check that source workspace allows evaluation
        self.lifecycle.check_can_evaluate(ws_id)?;
        
        let binding = self.get_binding(ws_id, &reference.name)?;
        binding.value.as_ref()
            .ok_or(IahuError::Evaluation(format!(
                "binding '{}' has no value (possibly unevaluated expression)",
                reference.name
            )))
    }

    /// Evaluate a simple expression in a workspace context.
    /// 
    /// This is a simplified evaluator - in a full implementation,
    /// this would parse and evaluate the expression tree.
    pub fn evaluate(
        &self,
        ws_id: WorkspaceId,
        expr: &str,
    ) -> Result<BindingValue, IahuError> {
        self.lifecycle.check_can_evaluate(ws_id)?;
        
        // Simple: if expr is just a binding name, return its value
        if let Ok(binding) = self.get_binding(ws_id, expr) {
            if let Some(value) = &binding.value {
                return Ok(value.clone());
            }
        }
        
        // Try to parse as number
        if let Ok(f) = expr.parse::<f64>() {
            return Ok(BindingValue::Scalar(Scalar::from_f64_quantized(f, 1_000_000)));
        }
        
        Err(IahuError::Evaluation(format!("cannot evaluate: {}", expr)))
    }

    /// Evaluate with multiple workspace contexts.
    pub fn cross_workspace_eval(
        &self,
        expr: &str,
        context: &[WorkspaceId],
    ) -> Result<BindingValue, IahuError> {
        // Check all contexts allow evaluation
        for &ws_id in context {
            self.lifecycle.check_can_evaluate(ws_id)?;
        }

        // Try each workspace in order
        for &ws_id in context {
            if let Ok(value) = self.evaluate(ws_id, expr) {
                return Ok(value);
            }
        }

        Err(IahuError::Evaluation(format!(
            "cannot evaluate '{}' in any provided context",
            expr
        )))
    }

    /// Check if evaluation is allowed for a workspace.
    pub fn can_evaluate(&self, ws_id: WorkspaceId) -> bool {
        self.lifecycle.check_can_evaluate(ws_id).is_ok()
    }

    /// Check if modification is allowed for a workspace.
    pub fn can_modify(&self, ws_id: WorkspaceId) -> bool {
        self.lifecycle.check_can_modify(ws_id).is_ok()
    }

    // =========================================================================
    // Queries
    // =========================================================================

    /// Get all workspace IDs.
    pub fn all_workspace_ids(&self) -> Vec<WorkspaceId> {
        self.workspaces.keys().copied().collect()
    }

    /// Get workspaces by tag.
    pub fn workspaces_with_tag(&self, tag: &str) -> Vec<WorkspaceId> {
        self.workspaces
            .iter()
            .filter(|(_, ws)| ws.tags.contains(&tag.to_string()))
            .map(|(&id, _)| id)
            .collect()
    }

    /// Get workspace count.
    pub fn workspace_count(&self) -> usize {
        self.workspaces.len()
    }
}

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

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

    #[test]
    fn iahu_create_workspace() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        let ws = iahu.get(id).unwrap();
        assert_eq!(ws.info.name, "test");
        assert_eq!(ws.info.signature, Signature::VGA3);
    }

    #[test]
    fn iahu_get() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        assert!(iahu.get(id).is_some());
        assert!(iahu.get(WorkspaceId::new()).is_none());
    }

    #[test]
    fn iahu_get_by_name() {
        let mut iahu = Iahu::new();
        iahu.create_workspace("myws", Signature::VGA3).unwrap();
        
        assert!(iahu.get_by_name("myws").is_some());
        assert!(iahu.get_by_name("other").is_none());
    }

    #[test]
    fn iahu_activate() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.activate(id).unwrap();
        let ws = iahu.get(id).unwrap();
        assert_eq!(ws.state, WorkspaceState::Active);
    }

    #[test]
    fn iahu_suspend() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.activate(id).unwrap();
        iahu.suspend(id).unwrap();
        
        let ws = iahu.get(id).unwrap();
        assert_eq!(ws.state, WorkspaceState::Suspended);
    }

    #[test]
    fn iahu_dispose() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.activate(id).unwrap();
        iahu.dispose(id).unwrap();
        
        let ws = iahu.get(id).unwrap();
        assert_eq!(ws.state, WorkspaceState::Disposed);
    }

    #[test]
    fn iahu_resume_suspended() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.activate(id).unwrap();
        iahu.suspend(id).unwrap();
        iahu.resume(id).unwrap();
        
        let ws = iahu.get(id).unwrap();
        assert_eq!(ws.state, WorkspaceState::Active);
    }

    #[test]
    fn iahu_active_workspace_ids() {
        let mut iahu = Iahu::new();
        let id1 = iahu.create_workspace("ws1", Signature::VGA3).unwrap();
        let id2 = iahu.create_workspace("ws2", Signature::VGA3).unwrap();
        
        iahu.activate(id1).unwrap();
        iahu.activate(id2).unwrap();
        iahu.suspend(id2).unwrap();
        
        let active = iahu.active_workspace_ids();
        assert_eq!(active.len(), 1);
        assert!(active.contains(&id1));
    }

    #[test]
    fn iahu_metadata() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.set_metadata(id, "author", "rikii").unwrap();
        assert_eq!(iahu.get_metadata(id, "author"), Some("rikii"));
    }

    #[test]
    fn iahu_tags() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        iahu.add_tag(id, "geometry").unwrap();
        iahu.add_tag(id, "3d").unwrap();
        
        let tags = iahu.get_tags(id).unwrap();
        assert!(tags.contains(&"geometry".to_string()));
        assert!(tags.contains(&"3d".to_string()));
    }

    #[test]
    fn iahu_evaluate_local() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        iahu.activate(id).unwrap();
        
        iahu.set_binding(id, Binding::scalar("x", Scalar::from_f64_quantized(42.0, 1000))).unwrap();
        
        let result = iahu.evaluate(id, "x").unwrap();
        if let BindingValue::Scalar(s) = result {
            assert_eq!(s.to_f64(), 42.0);
        } else {
            panic!("Expected scalar");
        }
    }

    #[test]
    fn iahu_evaluate_cross_ref() {
        let mut iahu = Iahu::new();
        let id1 = iahu.create_workspace("ws1", Signature::VGA3).unwrap();
        let id2 = iahu.create_workspace("ws2", Signature::VGA3).unwrap();
        
        iahu.activate(id1).unwrap();
        iahu.activate(id2).unwrap();
        
        iahu.set_binding(id1, Binding::scalar("x", Scalar::from_f64_quantized(10.0, 1000))).unwrap();
        
        let ref_ = BindingRef::remote(id1, "x");
        let value = iahu.resolve_reference(&ref_, id2).unwrap();
        
        if let BindingValue::Scalar(s) = value {
            assert_eq!(s.to_f64(), 10.0);
        } else {
            panic!("Expected scalar");
        }
    }

    #[test]
    fn iahu_cross_workspace_eval() {
        let mut iahu = Iahu::new();
        let id1 = iahu.create_workspace("ws1", Signature::VGA3).unwrap();
        let id2 = iahu.create_workspace("ws2", Signature::VGA3).unwrap();
        
        iahu.activate(id1).unwrap();
        iahu.activate(id2).unwrap();
        
        iahu.set_binding(id2, Binding::scalar("y", Scalar::from_f64_quantized(99.0, 1000))).unwrap();
        
        // Search in id1 first (won't find), then id2 (will find)
        let result = iahu.cross_workspace_eval("y", &[id1, id2]).unwrap();
        
        if let BindingValue::Scalar(s) = result {
            assert_eq!(s.to_f64(), 99.0);
        } else {
            panic!("Expected scalar");
        }
    }

    #[test]
    fn iahu_resolve_reference() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        iahu.activate(id).unwrap();
        
        iahu.set_binding(id, Binding::scalar("pi", Scalar::pi())).unwrap();
        
        let ref_ = BindingRef::local("pi");
        let value = iahu.resolve_reference(&ref_, id).unwrap();
        
        if let BindingValue::Scalar(s) = value {
            assert!((s.to_f64() - std::f64::consts::PI).abs() < 1e-10);
        } else {
            panic!("Expected scalar");
        }
    }

    #[test]
    fn iahu_cannot_evaluate_suspended() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        iahu.activate(id).unwrap();
        iahu.suspend(id).unwrap();
        
        let result = iahu.evaluate(id, "anything");
        assert!(result.is_err());
    }

    #[test]
    fn iahu_cannot_modify_suspended() {
        let mut iahu = Iahu::new();
        let id = iahu.create_workspace("test", Signature::VGA3).unwrap();
        iahu.activate(id).unwrap();
        iahu.suspend(id).unwrap();
        
        let result = iahu.set_binding(id, Binding::scalar("x", Scalar::from_f64_quantized(1.0, 1000)));
        assert!(result.is_err());
    }

    #[test]
    fn binding_ref_parse() {
        let resolver = |name: &str| -> Option<WorkspaceId> {
            if name == "other" { Some(WorkspaceId::new()) } else { None }
        };
        
        let local = BindingRef::parse("x", resolver).unwrap();
        assert!(local.workspace.is_none());
        assert_eq!(local.name, "x");
        
        let remote = BindingRef::parse("other::y", resolver).unwrap();
        assert!(remote.workspace.is_some());
        assert_eq!(remote.name, "y");
    }

    #[test]
    fn duplicate_workspace_name() {
        let mut iahu = Iahu::new();
        iahu.create_workspace("test", Signature::VGA3).unwrap();
        
        let result = iahu.create_workspace("test", Signature::VGA3);
        assert!(matches!(result, Err(IahuError::DuplicateName(_))));
    }
}
