//! Core data models for KOGRAL.
//!
//! This module defines the fundamental structures:
//! - `Node`: Knowledge base entries (notes, decisions, guidelines, patterns,
//!   etc.)
//! - `Edge`: Relationships between nodes
//! - `Graph`: Collection of nodes and edges with metadata

use std::collections::HashMap;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Type of KOGRAL node
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum NodeType {
    /// General knowledge, observations
    Note,
    /// ADR-style architectural decisions
    Decision,
    /// Code standards, practices
    Guideline,
    /// Reusable solutions
    Pattern,
    /// Daily notes (auto-dated)
    Journal,
    /// Agent task records (from Vapora KG)
    Execution,
}

impl std::fmt::Display for NodeType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Note => write!(f, "note"),
            Self::Decision => write!(f, "decision"),
            Self::Guideline => write!(f, "guideline"),
            Self::Pattern => write!(f, "pattern"),
            Self::Journal => write!(f, "journal"),
            Self::Execution => write!(f, "execution"),
        }
    }
}

/// Status of a KOGRAL node
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum NodeStatus {
    /// Work in progress
    #[default]
    Draft,
    /// Current and active
    Active,
    /// Replaced by newer version
    Superseded,
    /// No longer relevant
    Archived,
}

impl std::fmt::Display for NodeStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Draft => write!(f, "draft"),
            Self::Active => write!(f, "active"),
            Self::Superseded => write!(f, "superseded"),
            Self::Archived => write!(f, "archived"),
        }
    }
}

/// Task status for Logseq TODO blocks
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TaskStatus {
    /// Task to be done
    Todo,
    /// Task in progress
    Doing,
    /// Task completed
    Done,
    /// Task scheduled for later
    Later,
    /// Task to be done now (high priority)
    Now,
    /// Task waiting on something
    Waiting,
    /// Task cancelled
    Cancelled,
}

impl std::fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Todo => write!(f, "TODO"),
            Self::Doing => write!(f, "DOING"),
            Self::Done => write!(f, "DONE"),
            Self::Later => write!(f, "LATER"),
            Self::Now => write!(f, "NOW"),
            Self::Waiting => write!(f, "WAITING"),
            Self::Cancelled => write!(f, "CANCELLED"),
        }
    }
}

/// Properties associated with a content block
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct BlockProperties {
    /// Tags attached to the block (e.g., #card, #important)
    pub tags: Vec<String>,
    /// Task status if this is a TODO block
    pub status: Option<TaskStatus>,
    /// Custom key-value properties (e.g., `property::` value)
    pub custom: HashMap<String, String>,
    /// Block references ((uuid))
    pub block_refs: Vec<String>,
    /// Page references [[page]]
    pub page_refs: Vec<String>,
}

impl BlockProperties {
    /// Create new empty block properties
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Check if the block has a specific tag
    #[must_use]
    pub fn has_tag(&self, tag: &str) -> bool {
        self.tags.iter().any(|t| t == tag)
    }

    /// Add a tag if not already present
    pub fn add_tag(&mut self, tag: String) {
        if !self.tags.contains(&tag) {
            self.tags.push(tag);
        }
    }

    /// Check if the block has a specific custom property
    #[must_use]
    pub fn has_property(&self, key: &str) -> bool {
        self.custom.contains_key(key)
    }

    /// Get a custom property value
    #[must_use]
    pub fn get_property(&self, key: &str) -> Option<&String> {
        self.custom.get(key)
    }

    /// Set a custom property
    pub fn set_property(&mut self, key: String, value: String) {
        self.custom.insert(key, value);
    }
}

/// A content block (Logseq-compatible)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Block {
    /// Unique identifier (UUID)
    pub id: String,
    /// Block content (text)
    pub content: String,
    /// Block properties (tags, status, custom metadata)
    pub properties: BlockProperties,
    /// Nested child blocks
    pub children: Vec<Block>,
    /// Creation timestamp
    pub created: DateTime<Utc>,
    /// Last modification timestamp
    pub modified: DateTime<Utc>,
    /// Parent block ID (if nested)
    pub parent_id: Option<String>,
}

impl Block {
    /// Create a new block with the given content
    #[must_use]
    pub fn new(content: String) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4().to_string(),
            content,
            properties: BlockProperties::default(),
            children: Vec::new(),
            created: now,
            modified: now,
            parent_id: None,
        }
    }

    /// Create a new block with UUID and content
    #[must_use]
    pub fn with_id(id: String, content: String) -> Self {
        let now = Utc::now();
        Self {
            id,
            content,
            properties: BlockProperties::default(),
            children: Vec::new(),
            created: now,
            modified: now,
            parent_id: None,
        }
    }

    /// Update the modified timestamp to now
    pub fn touch(&mut self) {
        self.modified = Utc::now();
    }

    /// Check if this is a task block (has TODO status)
    #[must_use]
    pub fn is_task(&self) -> bool {
        self.properties.status.is_some()
    }

    /// Check if this block has a specific tag
    #[must_use]
    pub fn has_tag(&self, tag: &str) -> bool {
        self.properties.has_tag(tag)
    }

    /// Add a child block
    pub fn add_child(&mut self, mut child: Block) {
        child.parent_id = Some(self.id.clone());
        self.children.push(child);
        self.touch();
    }

    /// Get all blocks recursively (depth-first traversal)
    #[must_use]
    pub fn all_blocks(&self) -> Vec<&Block> {
        let mut blocks = vec![self];
        for child in &self.children {
            blocks.extend(child.all_blocks());
        }
        blocks
    }

    /// Find blocks by tag (recursively)
    #[must_use]
    pub fn find_by_tag(&self, tag: &str) -> Vec<&Block> {
        self.all_blocks()
            .into_iter()
            .filter(|b| b.has_tag(tag))
            .collect()
    }

    /// Find blocks by task status (recursively)
    #[must_use]
    pub fn find_by_status(&self, status: TaskStatus) -> Vec<&Block> {
        self.all_blocks()
            .into_iter()
            .filter(|b| b.properties.status == Some(status))
            .collect()
    }

    /// Find blocks with a specific custom property (recursively)
    #[must_use]
    pub fn find_by_property(&self, key: &str, value: &str) -> Vec<&Block> {
        self.all_blocks()
            .into_iter()
            .filter(|b| b.properties.get_property(key) == Some(&value.to_string()))
            .collect()
    }
}

/// Type of relationship between nodes
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EdgeType {
    /// General conceptual link
    RelatesTo,
    /// Must exist/be read first
    DependsOn,
    /// Concrete implementation of
    Implements,
    /// Inherits and overrides
    Extends,
    /// Replaces older version
    Supersedes,
    /// Documents/clarifies
    Explains,
}

impl std::fmt::Display for EdgeType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::RelatesTo => write!(f, "relates_to"),
            Self::DependsOn => write!(f, "depends_on"),
            Self::Implements => write!(f, "implements"),
            Self::Extends => write!(f, "extends"),
            Self::Supersedes => write!(f, "supersedes"),
            Self::Explains => write!(f, "explains"),
        }
    }
}

/// A node in the KOGRAL graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Node {
    /// Unique identifier
    pub id: String,
    /// Type of node
    pub node_type: NodeType,
    /// Human-readable title
    pub title: String,
    /// Creation timestamp
    pub created: DateTime<Utc>,
    /// Last modification timestamp
    pub modified: DateTime<Utc>,
    /// Content (markdown body)
    pub content: String,
    /// Tags for categorization
    pub tags: Vec<String>,
    /// Current status
    pub status: NodeStatus,
    /// Related node IDs
    pub relates_to: Vec<String>,
    /// Dependencies (node IDs this depends on)
    pub depends_on: Vec<String>,
    /// Implementations (node IDs this implements)
    pub implements: Vec<String>,
    /// Extensions (node IDs this extends)
    pub extends: Vec<String>,
    /// Project identifier (for cross-project links)
    pub project: Option<String>,
    /// Additional metadata (type-specific fields)
    pub metadata: HashMap<String, serde_json::Value>,
    /// Vector embedding (for semantic search)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub embedding: Option<Vec<f32>>,
    /// Parsed content blocks (Logseq-compatible, optional)
    /// This is a cached structure parsed from `content` markdown.
    /// When None, blocks haven't been parsed yet (lazy parsing).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub blocks: Option<Vec<Block>>,
}

impl Node {
    /// Create a new node with the given type and title
    #[must_use]
    pub fn new(node_type: NodeType, title: String) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4().to_string(),
            node_type,
            title,
            created: now,
            modified: now,
            content: String::new(),
            tags: Vec::new(),
            status: NodeStatus::Draft,
            relates_to: Vec::new(),
            depends_on: Vec::new(),
            implements: Vec::new(),
            extends: Vec::new(),
            project: None,
            metadata: HashMap::new(),
            embedding: None,
            blocks: None,
        }
    }

    /// Update the modified timestamp to now
    pub fn touch(&mut self) {
        self.modified = Utc::now();
    }

    /// Add a tag if not already present
    pub fn add_tag(&mut self, tag: String) {
        if !self.tags.contains(&tag) {
            self.tags.push(tag);
            self.touch();
        }
    }

    /// Add a relationship if not already present
    pub fn add_relation(&mut self, edge_type: EdgeType, target_id: String) {
        let list = match edge_type {
            EdgeType::RelatesTo => &mut self.relates_to,
            EdgeType::DependsOn => &mut self.depends_on,
            EdgeType::Implements => &mut self.implements,
            EdgeType::Extends => &mut self.extends,
            EdgeType::Supersedes | EdgeType::Explains => {
                // These are stored as separate edges, not in node fields
                return;
            }
        };

        if !list.contains(&target_id) {
            list.push(target_id);
            self.touch();
        }
    }

    /// Get all related node IDs (union of all relationship types)
    #[must_use]
    pub fn all_relations(&self) -> Vec<String> {
        let mut relations = Vec::new();
        relations.extend(self.relates_to.clone());
        relations.extend(self.depends_on.clone());
        relations.extend(self.implements.clone());
        relations.extend(self.extends.clone());
        relations.sort();
        relations.dedup();
        relations
    }

    /// Get blocks (lazy parsing from content if not already parsed)
    ///
    /// Parses `content` into structured blocks on first access, caches result.
    /// Returns empty Vec if parsing fails.
    ///
    /// # Panics
    ///
    /// Does not panic - the internal `unwrap()` is safe because blocks are
    /// initialized before being accessed.
    pub fn get_blocks(&mut self) -> Vec<&Block> {
        use crate::block_parser::BlockParser;

        if self.blocks.is_none() {
            // Parse content into blocks (lazy)
            if let Ok(parsed) = BlockParser::parse(&self.content) {
                self.blocks = Some(parsed);
            } else {
                self.blocks = Some(Vec::new());
            }
        }

        self.blocks.as_ref().unwrap().iter().collect()
    }

    /// Find blocks with a specific tag (parses if needed)
    pub fn find_blocks_by_tag(&mut self, tag: &str) -> Vec<&Block> {
        self.get_blocks(); // Ensure blocks are parsed

        if let Some(blocks) = &self.blocks {
            blocks.iter().flat_map(|b| b.find_by_tag(tag)).collect()
        } else {
            Vec::new()
        }
    }

    /// Find all TODO blocks (any task status)
    pub fn find_all_todos(&mut self) -> Vec<&Block> {
        self.get_blocks(); // Ensure blocks are parsed

        if let Some(blocks) = &self.blocks {
            blocks
                .iter()
                .flat_map(|b| b.all_blocks())
                .filter(|b| b.is_task())
                .collect()
        } else {
            Vec::new()
        }
    }

    /// Find blocks with a specific task status
    pub fn find_blocks_by_status(&mut self, status: TaskStatus) -> Vec<&Block> {
        self.get_blocks(); // Ensure blocks are parsed

        if let Some(blocks) = &self.blocks {
            blocks
                .iter()
                .flat_map(|b| b.find_by_status(status))
                .collect()
        } else {
            Vec::new()
        }
    }

    /// Find blocks with a specific custom property
    pub fn find_blocks_by_property(&mut self, key: &str, value: &str) -> Vec<&Block> {
        self.get_blocks(); // Ensure blocks are parsed

        if let Some(blocks) = &self.blocks {
            blocks
                .iter()
                .flat_map(|b| b.find_by_property(key, value))
                .collect()
        } else {
            Vec::new()
        }
    }

    /// Force re-parsing of blocks from content
    ///
    /// Useful when content has been modified and blocks need to be updated
    pub fn reparse_blocks(&mut self) {
        use crate::block_parser::BlockParser;

        if let Ok(parsed) = BlockParser::parse(&self.content) {
            self.blocks = Some(parsed);
        } else {
            self.blocks = Some(Vec::new());
        }
    }
}

/// A directed edge between two nodes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edge {
    /// Source node ID
    pub from: String,
    /// Target node ID
    pub to: String,
    /// Relationship type
    pub edge_type: EdgeType,
    /// Relationship strength (0.0 to 1.0)
    #[serde(default = "default_edge_strength")]
    pub strength: f32,
    /// Creation timestamp
    pub created: DateTime<Utc>,
    /// Additional metadata
    #[serde(default)]
    pub metadata: HashMap<String, serde_json::Value>,
}

fn default_edge_strength() -> f32 {
    1.0
}

impl Edge {
    /// Create a new edge between nodes
    #[must_use]
    pub fn new(from: String, to: String, edge_type: EdgeType) -> Self {
        Self {
            from,
            to,
            edge_type,
            strength: 1.0,
            created: Utc::now(),
            metadata: HashMap::new(),
        }
    }

    /// Create an edge with a specific strength
    #[must_use]
    pub fn with_strength(from: String, to: String, edge_type: EdgeType, strength: f32) -> Self {
        Self {
            from,
            to,
            edge_type,
            strength: strength.clamp(0.0, 1.0),
            created: Utc::now(),
            metadata: HashMap::new(),
        }
    }

    /// Get the reverse edge type (if applicable)
    #[must_use]
    pub fn reverse_type(&self) -> Option<EdgeType> {
        match self.edge_type {
            EdgeType::DependsOn => Some(EdgeType::DependsOn), // Symmetric in reverse
            EdgeType::Extends => Some(EdgeType::Extends),
            EdgeType::Supersedes => Some(EdgeType::Supersedes),
            _ => None,
        }
    }
}

/// A KOGRAL graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Graph {
    /// Graph name/identifier
    pub name: String,
    /// Graph version
    pub version: String,
    /// Human-readable description
    pub description: String,
    /// Nodes indexed by ID
    pub nodes: HashMap<String, Node>,
    /// Edges (stored separately from node relationships for flexibility)
    pub edges: Vec<Edge>,
    /// Graph creation timestamp
    pub created: DateTime<Utc>,
    /// Last modification timestamp
    pub modified: DateTime<Utc>,
    /// Graph metadata
    pub metadata: HashMap<String, serde_json::Value>,
}

impl Graph {
    /// Create a new empty graph
    #[must_use]
    pub fn new(name: String) -> Self {
        let now = Utc::now();
        Self {
            name,
            version: "1.0.0".to_string(),
            description: String::new(),
            nodes: HashMap::new(),
            edges: Vec::new(),
            created: now,
            modified: now,
            metadata: HashMap::new(),
        }
    }

    /// Add a node to the graph
    ///
    /// # Errors
    ///
    /// Returns an error if a node with the same ID already exists in the graph
    pub fn add_node(&mut self, node: Node) -> Result<(), String> {
        if self.nodes.contains_key(&node.id) {
            return Err(format!("Node with ID {} already exists", node.id));
        }
        self.nodes.insert(node.id.clone(), node);
        self.modified = Utc::now();
        Ok(())
    }

    /// Get a node by ID
    #[must_use]
    pub fn get_node(&self, id: &str) -> Option<&Node> {
        self.nodes.get(id)
    }

    /// Get a mutable reference to a node
    pub fn get_node_mut(&mut self, id: &str) -> Option<&mut Node> {
        if self.nodes.contains_key(id) {
            self.modified = Utc::now();
        }
        self.nodes.get_mut(id)
    }

    /// Remove a node and all its edges
    pub fn remove_node(&mut self, id: &str) -> Option<Node> {
        self.edges.retain(|e| e.from != id && e.to != id);
        self.modified = Utc::now();
        self.nodes.remove(id)
    }

    /// Add an edge between nodes
    ///
    /// # Errors
    ///
    /// Returns an error if source or target node does not exist in the graph
    pub fn add_edge(&mut self, edge: Edge) -> Result<(), String> {
        if !self.nodes.contains_key(&edge.from) {
            return Err(format!("Source node {} not found", edge.from));
        }
        if !self.nodes.contains_key(&edge.to) {
            return Err(format!("Target node {} not found", edge.to));
        }
        self.edges.push(edge);
        self.modified = Utc::now();
        Ok(())
    }

    /// Get all edges from a specific node
    #[must_use]
    pub fn edges_from(&self, node_id: &str) -> Vec<&Edge> {
        self.edges.iter().filter(|e| e.from == node_id).collect()
    }

    /// Get all edges to a specific node
    #[must_use]
    pub fn edges_to(&self, node_id: &str) -> Vec<&Edge> {
        self.edges.iter().filter(|e| e.to == node_id).collect()
    }

    /// Get all edges involving a specific node (in or out)
    #[must_use]
    pub fn edges_involving(&self, node_id: &str) -> Vec<&Edge> {
        self.edges
            .iter()
            .filter(|e| e.from == node_id || e.to == node_id)
            .collect()
    }

    /// Get nodes by type
    #[must_use]
    pub fn nodes_by_type(&self, node_type: NodeType) -> Vec<&Node> {
        self.nodes
            .values()
            .filter(|n| n.node_type == node_type)
            .collect()
    }

    /// Get nodes by tag
    #[must_use]
    pub fn nodes_by_tag(&self, tag: &str) -> Vec<&Node> {
        self.nodes
            .values()
            .filter(|n| n.tags.contains(&tag.to_string()))
            .collect()
    }

    /// Get nodes by status
    #[must_use]
    pub fn nodes_by_status(&self, status: NodeStatus) -> Vec<&Node> {
        self.nodes.values().filter(|n| n.status == status).collect()
    }

    /// Get total node count
    #[must_use]
    pub fn node_count(&self) -> usize {
        self.nodes.len()
    }

    /// Get total edge count
    #[must_use]
    pub fn edge_count(&self) -> usize {
        self.edges.len()
    }

    /// Find blocks with a specific tag across all nodes
    ///
    /// Returns tuples of (`node_id`, blocks) for each node that has matching
    /// blocks
    pub fn find_blocks_by_tag(&mut self, tag: &str) -> Vec<(String, Vec<&Block>)> {
        let mut results = Vec::new();

        for (node_id, node) in &mut self.nodes {
            let blocks = node.find_blocks_by_tag(tag);
            if !blocks.is_empty() {
                results.push((node_id.clone(), blocks));
            }
        }

        results
    }

    /// Find all TODO blocks across all nodes
    ///
    /// Returns tuples of (`node_id`, `todo_blocks`)
    pub fn find_all_todos(&mut self) -> Vec<(String, Vec<&Block>)> {
        let mut results = Vec::new();

        for (node_id, node) in &mut self.nodes {
            let todos = node.find_all_todos();
            if !todos.is_empty() {
                results.push((node_id.clone(), todos));
            }
        }

        results
    }

    /// Find blocks with a specific task status across all nodes
    pub fn find_blocks_by_status(&mut self, status: TaskStatus) -> Vec<(String, Vec<&Block>)> {
        let mut results = Vec::new();

        for (node_id, node) in &mut self.nodes {
            let blocks = node.find_blocks_by_status(status);
            if !blocks.is_empty() {
                results.push((node_id.clone(), blocks));
            }
        }

        results
    }

    /// Find blocks with a specific custom property across all nodes
    pub fn find_blocks_by_property(
        &mut self,
        key: &str,
        value: &str,
    ) -> Vec<(String, Vec<&Block>)> {
        let mut results = Vec::new();

        for (node_id, node) in &mut self.nodes {
            let blocks = node.find_blocks_by_property(key, value);
            if !blocks.is_empty() {
                results.push((node_id.clone(), blocks));
            }
        }

        results
    }
}

impl Default for Graph {
    fn default() -> Self {
        Self::new("default".to_string())
    }
}

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

    #[test]
    fn test_node_creation() {
        let node = Node::new(NodeType::Note, "Test Note".to_string());
        assert_eq!(node.node_type, NodeType::Note);
        assert_eq!(node.title, "Test Note");
        assert_eq!(node.status, NodeStatus::Draft);
        assert!(node.tags.is_empty());
        assert!(node.content.is_empty());
    }

    #[test]
    fn test_node_add_tag() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.add_tag("rust".to_string());
        assert_eq!(node.tags, vec!["rust"]);

        // Adding same tag again should not duplicate
        node.add_tag("rust".to_string());
        assert_eq!(node.tags, vec!["rust"]);
    }

    #[test]
    fn test_node_add_relation() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        let target_id = "target-123".to_string();

        node.add_relation(EdgeType::RelatesTo, target_id.clone());
        assert_eq!(node.relates_to, vec![target_id.clone()]);

        // Adding same relation again should not duplicate
        node.add_relation(EdgeType::RelatesTo, target_id.clone());
        assert_eq!(node.relates_to, vec![target_id]);
    }

    #[test]
    fn test_node_all_relations() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.relates_to = vec!["a".to_string(), "b".to_string()];
        node.depends_on = vec!["c".to_string()];
        node.implements = vec!["d".to_string()];

        let all = node.all_relations();
        assert_eq!(all.len(), 4);
        assert!(all.contains(&"a".to_string()));
        assert!(all.contains(&"b".to_string()));
        assert!(all.contains(&"c".to_string()));
        assert!(all.contains(&"d".to_string()));
    }

    #[test]
    fn test_edge_creation() {
        let edge = Edge::new(
            "node1".to_string(),
            "node2".to_string(),
            EdgeType::DependsOn,
        );
        assert_eq!(edge.from, "node1");
        assert_eq!(edge.to, "node2");
        assert_eq!(edge.edge_type, EdgeType::DependsOn);
        assert_eq!(edge.strength, 1.0);
    }

    #[test]
    fn test_edge_with_strength() {
        let edge = Edge::with_strength(
            "node1".to_string(),
            "node2".to_string(),
            EdgeType::RelatesTo,
            0.5,
        );
        assert_eq!(edge.strength, 0.5);

        // Test clamping
        let edge2 = Edge::with_strength(
            "node1".to_string(),
            "node2".to_string(),
            EdgeType::RelatesTo,
            1.5,
        );
        assert_eq!(edge2.strength, 1.0);

        let edge3 = Edge::with_strength(
            "node1".to_string(),
            "node2".to_string(),
            EdgeType::RelatesTo,
            -0.5,
        );
        assert_eq!(edge3.strength, 0.0);
    }

    #[test]
    fn test_graph_creation() {
        let graph = Graph::new("test-graph".to_string());
        assert_eq!(graph.name, "test-graph");
        assert_eq!(graph.version, "1.0.0");
        assert_eq!(graph.node_count(), 0);
        assert_eq!(graph.edge_count(), 0);
    }

    #[test]
    fn test_graph_add_node() {
        let mut graph = Graph::new("test".to_string());
        let node = Node::new(NodeType::Note, "Test Note".to_string());
        let node_id = node.id.clone();

        assert!(graph.add_node(node).is_ok());
        assert_eq!(graph.node_count(), 1);
        assert!(graph.get_node(&node_id).is_some());

        // Adding same node again should fail
        let duplicate = Node {
            id: node_id,
            ..Node::new(NodeType::Note, "Duplicate".to_string())
        };
        assert!(graph.add_node(duplicate).is_err());
    }

    #[test]
    fn test_graph_add_edge() {
        let mut graph = Graph::new("test".to_string());
        let node1 = Node::new(NodeType::Note, "Node 1".to_string());
        let node2 = Node::new(NodeType::Note, "Node 2".to_string());
        let id1 = node1.id.clone();
        let id2 = node2.id.clone();

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();

        let edge = Edge::new(id1.clone(), id2.clone(), EdgeType::RelatesTo);
        assert!(graph.add_edge(edge).is_ok());
        assert_eq!(graph.edge_count(), 1);

        // Adding edge with non-existent node should fail
        let bad_edge = Edge::new("nonexistent".to_string(), id2, EdgeType::RelatesTo);
        assert!(graph.add_edge(bad_edge).is_err());
    }

    #[test]
    fn test_graph_remove_node() {
        let mut graph = Graph::new("test".to_string());
        let node = Node::new(NodeType::Note, "Test".to_string());
        let id = node.id.clone();

        graph.add_node(node).unwrap();
        assert_eq!(graph.node_count(), 1);

        let removed = graph.remove_node(&id);
        assert!(removed.is_some());
        assert_eq!(graph.node_count(), 0);
    }

    #[test]
    fn test_graph_edges_from_to() {
        let mut graph = Graph::new("test".to_string());
        let node1 = Node::new(NodeType::Note, "Node 1".to_string());
        let node2 = Node::new(NodeType::Note, "Node 2".to_string());
        let node3 = Node::new(NodeType::Note, "Node 3".to_string());
        let id1 = node1.id.clone();
        let id2 = node2.id.clone();
        let id3 = node3.id.clone();

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();
        graph.add_node(node3).unwrap();

        graph
            .add_edge(Edge::new(id1.clone(), id2.clone(), EdgeType::RelatesTo))
            .unwrap();
        graph
            .add_edge(Edge::new(id1.clone(), id3.clone(), EdgeType::DependsOn))
            .unwrap();
        graph
            .add_edge(Edge::new(id2.clone(), id3.clone(), EdgeType::Implements))
            .unwrap();

        let from_id1 = graph.edges_from(&id1);
        assert_eq!(from_id1.len(), 2);

        let to_id3 = graph.edges_to(&id3);
        assert_eq!(to_id3.len(), 2);

        let involving_id2 = graph.edges_involving(&id2);
        assert_eq!(involving_id2.len(), 2);
    }

    #[test]
    fn test_graph_nodes_by_type() {
        let mut graph = Graph::new("test".to_string());
        graph
            .add_node(Node::new(NodeType::Note, "Note 1".to_string()))
            .unwrap();
        graph
            .add_node(Node::new(NodeType::Note, "Note 2".to_string()))
            .unwrap();
        graph
            .add_node(Node::new(NodeType::Decision, "Decision 1".to_string()))
            .unwrap();

        let notes = graph.nodes_by_type(NodeType::Note);
        assert_eq!(notes.len(), 2);

        let decisions = graph.nodes_by_type(NodeType::Decision);
        assert_eq!(decisions.len(), 1);
    }

    #[test]
    fn test_graph_nodes_by_tag() {
        let mut graph = Graph::new("test".to_string());
        let mut node1 = Node::new(NodeType::Note, "Node 1".to_string());
        node1.add_tag("rust".to_string());
        let mut node2 = Node::new(NodeType::Note, "Node 2".to_string());
        node2.add_tag("rust".to_string());
        node2.add_tag("testing".to_string());
        let node3 = Node::new(NodeType::Note, "Node 3".to_string());

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();
        graph.add_node(node3).unwrap();

        let rust_nodes = graph.nodes_by_tag("rust");
        assert_eq!(rust_nodes.len(), 2);

        let testing_nodes = graph.nodes_by_tag("testing");
        assert_eq!(testing_nodes.len(), 1);
    }

    #[test]
    fn test_graph_nodes_by_status() {
        let mut graph = Graph::new("test".to_string());
        let mut node1 = Node::new(NodeType::Note, "Node 1".to_string());
        node1.status = NodeStatus::Active;
        let node2 = Node::new(NodeType::Note, "Node 2".to_string()); // Draft by default

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();

        let active = graph.nodes_by_status(NodeStatus::Active);
        assert_eq!(active.len(), 1);

        let draft = graph.nodes_by_status(NodeStatus::Draft);
        assert_eq!(draft.len(), 1);
    }

    #[test]
    fn test_node_type_display() {
        assert_eq!(NodeType::Note.to_string(), "note");
        assert_eq!(NodeType::Decision.to_string(), "decision");
        assert_eq!(NodeType::Guideline.to_string(), "guideline");
    }

    #[test]
    fn test_node_status_display() {
        assert_eq!(NodeStatus::Draft.to_string(), "draft");
        assert_eq!(NodeStatus::Active.to_string(), "active");
        assert_eq!(NodeStatus::Superseded.to_string(), "superseded");
        assert_eq!(NodeStatus::Archived.to_string(), "archived");
    }

    #[test]
    fn test_edge_type_display() {
        assert_eq!(EdgeType::RelatesTo.to_string(), "relates_to");
        assert_eq!(EdgeType::DependsOn.to_string(), "depends_on");
        assert_eq!(EdgeType::Implements.to_string(), "implements");
    }

    #[test]
    fn test_node_get_blocks() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.content = r#"- First block
- TODO Second block #tag
  - Nested block"#
            .to_string();

        let blocks = node.get_blocks();
        assert_eq!(blocks.len(), 2);
        assert_eq!(blocks[0].content, "First block");
        assert_eq!(blocks[1].properties.status, Some(TaskStatus::Todo));
    }

    #[test]
    fn test_node_find_blocks_by_tag() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.content = r#"- Block 1 #important
- Block 2
- Block 3 #important #urgent"#
            .to_string();

        let blocks = node.find_blocks_by_tag("important");
        assert_eq!(blocks.len(), 2);
    }

    #[test]
    fn test_node_find_all_todos() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.content = r#"- Regular block
- TODO Task 1
- DONE Task 2
- TODO Task 3"#
            .to_string();

        let todos = node.find_all_todos();
        assert_eq!(todos.len(), 3);
    }

    #[test]
    fn test_node_find_blocks_by_status() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());
        node.content = r#"- TODO Task 1
- DONE Task 2
- TODO Task 3"#
            .to_string();

        let todo_blocks = node.find_blocks_by_status(TaskStatus::Todo);
        assert_eq!(todo_blocks.len(), 2);

        let done_blocks = node.find_blocks_by_status(TaskStatus::Done);
        assert_eq!(done_blocks.len(), 1);
    }

    #[test]
    fn test_graph_find_blocks_by_tag() {
        let mut graph = Graph::new("test".to_string());

        let mut node1 = Node::new(NodeType::Note, "Node 1".to_string());
        node1.content = "- Block #card\n- Block #important".to_string();
        let id1 = node1.id.clone();

        let mut node2 = Node::new(NodeType::Note, "Node 2".to_string());
        node2.content = "- Another #card".to_string();
        let id2 = node2.id.clone();

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();

        let results = graph.find_blocks_by_tag("card");
        assert_eq!(results.len(), 2);
        assert!(results.iter().any(|(id, _)| id == &id1));
        assert!(results.iter().any(|(id, _)| id == &id2));
    }

    #[test]
    fn test_graph_find_all_todos() {
        let mut graph = Graph::new("test".to_string());

        let mut node1 = Node::new(NodeType::Note, "Node 1".to_string());
        node1.content = "- TODO Task in node 1".to_string();

        let mut node2 = Node::new(NodeType::Note, "Node 2".to_string());
        node2.content = "- TODO Task in node 2\n- DONE Completed".to_string();

        graph.add_node(node1).unwrap();
        graph.add_node(node2).unwrap();

        let todos = graph.find_all_todos();
        assert_eq!(todos.len(), 2);
    }
}
