//! Trigger types for AiFastDb
//!
//! Defines the core types for the semantic trigger system.

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

use crate::types::MemoryNode;

/// Trigger execution phase
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TriggerPhase {
    /// Before write operation (can abort)
    PreWrite,
    /// During write operation (fast, synchronous)
    OnWrite,
    /// After write operation (for side effects)
    PostWrite,
}

impl std::fmt::Display for TriggerPhase {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TriggerPhase::PreWrite => write!(f, "pre-write"),
            TriggerPhase::OnWrite => write!(f, "on-write"),
            TriggerPhase::PostWrite => write!(f, "post-write"),
        }
    }
}

/// Context passed to triggers during execution
#[derive(Debug, Clone)]
pub struct TriggerContext {
    /// Current trigger phase
    pub phase: TriggerPhase,
    /// The memory node being processed
    pub node: MemoryNode,
    /// Similar nodes found during pre-processing (if any)
    pub neighbors: Option<Vec<NeighborInfo>>,
    /// Custom metadata for trigger processing
    pub metadata: HashMap<String, serde_json::Value>,
    /// Whether this is an update (vs new insert)
    pub is_update: bool,
    /// Original node (if this is an update)
    pub original_node: Option<MemoryNode>,
}

impl TriggerContext {
    /// Create a new trigger context
    pub fn new(phase: TriggerPhase, node: MemoryNode) -> Self {
        Self {
            phase,
            node,
            neighbors: None,
            metadata: HashMap::new(),
            is_update: false,
            original_node: None,
        }
    }

    /// Set neighbors information
    pub fn with_neighbors(mut self, neighbors: Vec<NeighborInfo>) -> Self {
        self.neighbors = Some(neighbors);
        self
    }

    /// Add metadata
    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata.insert(key.into(), value);
        self
    }

    /// Mark as update operation
    pub fn as_update(mut self, original: MemoryNode) -> Self {
        self.is_update = true;
        self.original_node = Some(original);
        self
    }

    /// Get a metadata value
    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
        self.metadata.get(key)
    }

    /// Check if neighbors are available
    pub fn has_neighbors(&self) -> bool {
        self.neighbors.as_ref().map_or(false, |n| !n.is_empty())
    }

    /// Get the highest similarity neighbor
    pub fn closest_neighbor(&self) -> Option<&NeighborInfo> {
        self.neighbors.as_ref()?.iter().max_by(|a, b| {
            a.similarity.partial_cmp(&b.similarity).unwrap_or(std::cmp::Ordering::Equal)
        })
    }
}

/// Information about a neighboring node
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NeighborInfo {
    /// Node ID
    pub id: String,
    /// Similarity score
    pub similarity: f32,
    /// Node content (optional, may be lazy-loaded)
    pub content: Option<String>,
}

/// Result of trigger execution
#[derive(Debug, Clone)]
pub enum TriggerResult {
    /// Continue with the operation
    Continue,
    /// Abort the operation with a reason
    Abort(String),
    /// Modify the node and continue
    Modify(MemoryNode),
    /// Defer action to post-write phase
    Defer(DeferredAction),
    /// Skip remaining triggers and continue
    SkipRemaining,
}

impl TriggerResult {
    /// Check if the result indicates the operation should continue
    pub fn should_continue(&self) -> bool {
        matches!(self, TriggerResult::Continue | TriggerResult::Modify(_) | TriggerResult::SkipRemaining)
    }

    /// Check if the result indicates the operation should abort
    pub fn should_abort(&self) -> bool {
        matches!(self, TriggerResult::Abort(_))
    }

    /// Get the abort reason if this is an abort result
    pub fn abort_reason(&self) -> Option<&str> {
        match self {
            TriggerResult::Abort(reason) => Some(reason),
            _ => None,
        }
    }

    /// Get the modified node if this is a modify result
    pub fn modified_node(&self) -> Option<&MemoryNode> {
        match self {
            TriggerResult::Modify(node) => Some(node),
            _ => None,
        }
    }
}

/// Deferred action to be executed in post-write phase
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeferredAction {
    /// Notify about potential duplicate
    NotifyDuplicate {
        candidate_ids: Vec<String>,
    },
    /// Notify about potential conflict
    NotifyConflict {
        candidate_ids: Vec<String>,
        conflict_type: String,
    },
    /// Update knowledge graph
    UpdateKnowledgeGraph {
        entity_ids: Vec<String>,
        relation_type: String,
    },
    /// Trigger consolidation
    TriggerConsolidation {
        threshold: f32,
    },
    /// Custom deferred action
    Custom {
        action_type: String,
        payload: serde_json::Value,
    },
}

/// Trigger priority (lower number = higher priority)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TriggerPriority(pub i32);

impl Default for TriggerPriority {
    fn default() -> Self {
        TriggerPriority(100)
    }
}

impl TriggerPriority {
    /// Highest priority (executed first)
    pub const HIGHEST: TriggerPriority = TriggerPriority(0);
    /// High priority
    pub const HIGH: TriggerPriority = TriggerPriority(25);
    /// Normal priority
    pub const NORMAL: TriggerPriority = TriggerPriority(50);
    /// Low priority
    pub const LOW: TriggerPriority = TriggerPriority(75);
    /// Lowest priority (executed last)
    pub const LOWEST: TriggerPriority = TriggerPriority(100);
}

/// Trigger configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriggerConfig {
    /// Whether the trigger is enabled
    pub enabled: bool,
    /// Priority (lower = higher priority)
    pub priority: i32,
    /// Optional condition expression
    pub condition: Option<String>,
    /// Custom configuration parameters
    pub params: HashMap<String, serde_json::Value>,
}

impl Default for TriggerConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            priority: TriggerPriority::NORMAL.0,
            condition: None,
            params: HashMap::new(),
        }
    }
}

impl TriggerConfig {
    /// Create a new enabled trigger config
    pub fn new() -> Self {
        Self::default()
    }

    /// Set priority
    pub fn with_priority(mut self, priority: TriggerPriority) -> Self {
        self.priority = priority.0;
        self
    }

    /// Set condition
    pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
        self.condition = Some(condition.into());
        self
    }

    /// Add parameter
    pub fn with_param(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.params.insert(key.into(), value);
        self
    }

    /// Disable the trigger
    pub fn disabled(mut self) -> Self {
        self.enabled = false;
        self
    }
}

/// Error types for trigger operations
#[derive(Debug, Clone)]
pub enum TriggerError {
    /// Trigger with this ID already exists
    DuplicateId(String),
    /// Trigger not found
    NotFound(String),
    /// Execution error
    ExecutionError(String),
    /// Configuration error
    ConfigError(String),
    /// Condition evaluation error
    ConditionError(String),
}

impl std::fmt::Display for TriggerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TriggerError::DuplicateId(id) => write!(f, "Trigger with ID '{}' already exists", id),
            TriggerError::NotFound(id) => write!(f, "Trigger '{}' not found", id),
            TriggerError::ExecutionError(msg) => write!(f, "Trigger execution error: {}", msg),
            TriggerError::ConfigError(msg) => write!(f, "Trigger configuration error: {}", msg),
            TriggerError::ConditionError(msg) => write!(f, "Condition evaluation error: {}", msg),
        }
    }
}

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

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

    #[test]
    fn test_trigger_phase_display() {
        assert_eq!(TriggerPhase::PreWrite.to_string(), "pre-write");
        assert_eq!(TriggerPhase::OnWrite.to_string(), "on-write");
        assert_eq!(TriggerPhase::PostWrite.to_string(), "post-write");
    }

    #[test]
    fn test_trigger_context_creation() {
        let node = MemoryNode::new("test content");
        let ctx = TriggerContext::new(TriggerPhase::PreWrite, node.clone());
        
        assert_eq!(ctx.phase, TriggerPhase::PreWrite);
        assert!(!ctx.is_update);
        assert!(!ctx.has_neighbors());
    }

    #[test]
    fn test_trigger_context_with_neighbors() {
        let node = MemoryNode::new("test content");
        let neighbors = vec![
            NeighborInfo { id: "n1".to_string(), similarity: 0.9, content: None },
            NeighborInfo { id: "n2".to_string(), similarity: 0.8, content: None },
        ];
        
        let ctx = TriggerContext::new(TriggerPhase::PreWrite, node)
            .with_neighbors(neighbors);
        
        assert!(ctx.has_neighbors());
        assert_eq!(ctx.closest_neighbor().unwrap().id, "n1");
    }

    #[test]
    fn test_trigger_result() {
        let result = TriggerResult::Continue;
        assert!(result.should_continue());
        assert!(!result.should_abort());

        let result = TriggerResult::Abort("test reason".to_string());
        assert!(!result.should_continue());
        assert!(result.should_abort());
        assert_eq!(result.abort_reason(), Some("test reason"));
    }

    #[test]
    fn test_trigger_priority_ordering() {
        assert!(TriggerPriority::HIGHEST < TriggerPriority::HIGH);
        assert!(TriggerPriority::HIGH < TriggerPriority::NORMAL);
        assert!(TriggerPriority::NORMAL < TriggerPriority::LOW);
        assert!(TriggerPriority::LOW < TriggerPriority::LOWEST);
    }

    #[test]
    fn test_trigger_config() {
        let config = TriggerConfig::new()
            .with_priority(TriggerPriority::HIGH)
            .with_param("threshold", serde_json::json!(0.9));
        
        assert!(config.enabled);
        assert_eq!(config.priority, 25);
        assert!(config.params.contains_key("threshold"));
    }
}
