use crate::error::{InspectorError, Result};
use crate::node::{Inspectable, NodeId, NodeMetadata, NodeState};
use std::collections::HashMap;
use std::sync::Arc;

/// A node within the inspector tree, holding metadata and relationships.
pub struct TreeNode {
    pub id: NodeId,
    pub metadata: NodeMetadata,
    pub parent: Option<NodeId>,
    pub children: Vec<NodeId>,
    pub inspectable: Option<Arc<dyn Inspectable>>,
}

impl TreeNode {
    fn new(
        id: NodeId,
        metadata: NodeMetadata,
        parent: Option<NodeId>,
        inspectable: Option<Arc<dyn Inspectable>>,
    ) -> Self {
        Self {
            id,
            metadata,
            parent,
            children: Vec::new(),
            inspectable,
        }
    }
}

/// The inspector tree: a rooted tree of `TreeNode`s with parent-child relationships.
///
/// All mutations go through this struct. Thread safety is provided by wrapping
/// `NodeTree` in `Arc<RwLock<NodeTree>>` at the registry level.
pub struct NodeTree {
    nodes: HashMap<NodeId, TreeNode>,
    root: NodeId,
}

impl NodeTree {
    /// Create a new tree with a root node.
    ///
    /// The root node is always present and cannot be removed.
    pub fn new(root_id: NodeId, root_metadata: NodeMetadata) -> Self {
        let mut nodes = HashMap::new();
        nodes.insert(
            root_id.clone(),
            TreeNode::new(root_id.clone(), root_metadata, None, None),
        );
        Self {
            nodes,
            root: root_id,
        }
    }

    /// Get the root node ID.
    pub fn root_id(&self) -> &NodeId {
        &self.root
    }

    /// Insert a new node as a child of the given parent.
    pub fn insert(
        &mut self,
        id: NodeId,
        parent: &NodeId,
        metadata: NodeMetadata,
        inspectable: Option<Arc<dyn Inspectable>>,
    ) -> Result<()> {
        if self.nodes.contains_key(&id) {
            return Err(InspectorError::NodeAlreadyExists(id));
        }
        if !self.nodes.contains_key(parent) {
            return Err(InspectorError::ParentNotFound(parent.clone()));
        }

        let node = TreeNode::new(id.clone(), metadata, Some(parent.clone()), inspectable);
        self.nodes.insert(id.clone(), node);

        // Add as child of parent
        if let Some(parent_node) = self.nodes.get_mut(parent) {
            parent_node.children.push(id);
        }

        Ok(())
    }

    /// Remove a node and reparent its children to the node's parent.
    ///
    /// The root node cannot be removed.
    pub fn remove(&mut self, id: &NodeId) -> Result<()> {
        if id == &self.root {
            return Err(InspectorError::CannotRemoveRoot);
        }

        let node = self
            .nodes
            .get(id)
            .ok_or_else(|| InspectorError::NodeNotFound(id.clone()))?;

        let parent_id = node.parent.clone();
        let children: Vec<NodeId> = node.children.clone();

        // Reparent children to the removed node's parent
        if let Some(ref parent_id) = parent_id {
            for child_id in &children {
                if let Some(child) = self.nodes.get_mut(child_id) {
                    child.parent = Some(parent_id.clone());
                }
            }
            // Update parent's children: remove this node, add reparented children
            if let Some(parent) = self.nodes.get_mut(parent_id) {
                parent.children.retain(|c| c != id);
                parent.children.extend(children);
            }
        }

        self.nodes.remove(id);
        Ok(())
    }

    /// Remove a node and all its descendants.
    ///
    /// The root node cannot be removed.
    pub fn remove_subtree(&mut self, id: &NodeId) -> Result<()> {
        if id == &self.root {
            return Err(InspectorError::CannotRemoveRoot);
        }

        if !self.nodes.contains_key(id) {
            return Err(InspectorError::NodeNotFound(id.clone()));
        }

        // Collect all descendant IDs via BFS
        let mut to_remove = Vec::new();
        let mut queue = vec![id.clone()];
        while let Some(current) = queue.pop() {
            if let Some(node) = self.nodes.get(&current) {
                queue.extend(node.children.clone());
            }
            to_remove.push(current);
        }

        // Remove this node from parent's children list
        let parent_id = self.nodes.get(id).and_then(|n| n.parent.clone());
        if let Some(parent_id) = parent_id {
            if let Some(parent) = self.nodes.get_mut(&parent_id) {
                parent.children.retain(|c| c != id);
            }
        }

        // Remove all collected nodes
        for node_id in &to_remove {
            self.nodes.remove(node_id);
        }

        Ok(())
    }

    /// Get a reference to a node by ID.
    pub fn get(&self, id: &NodeId) -> Option<&TreeNode> {
        self.nodes.get(id)
    }

    /// Get a mutable reference to a node by ID.
    pub fn get_mut(&mut self, id: &NodeId) -> Option<&mut TreeNode> {
        self.nodes.get_mut(id)
    }

    /// Get the direct children of a node.
    pub fn children(&self, id: &NodeId) -> Vec<&TreeNode> {
        self.nodes
            .get(id)
            .map(|node| {
                node.children
                    .iter()
                    .filter_map(|child_id| self.nodes.get(child_id))
                    .collect()
            })
            .unwrap_or_default()
    }

    /// Get all ancestors of a node, from immediate parent to root.
    pub fn ancestors(&self, id: &NodeId) -> Vec<&TreeNode> {
        let mut result = Vec::new();
        let mut current = self.nodes.get(id).and_then(|n| n.parent.as_ref());
        while let Some(parent_id) = current {
            if let Some(parent) = self.nodes.get(parent_id) {
                result.push(parent);
                current = parent.parent.as_ref();
            } else {
                break;
            }
        }
        result
    }

    /// Get all nodes in the tree.
    pub fn all_nodes(&self) -> Vec<&TreeNode> {
        self.nodes.values().collect()
    }

    /// Total number of nodes in the tree.
    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    /// Whether the tree is empty (only root).
    pub fn is_empty(&self) -> bool {
        self.nodes.len() <= 1
    }

    /// Update a node's state, returning the old state.
    pub fn update_state(&mut self, id: &NodeId, state: NodeState) -> Result<NodeState> {
        let node = self
            .nodes
            .get_mut(id)
            .ok_or_else(|| InspectorError::NodeNotFound(id.clone()))?;
        let old = node.metadata.state.clone();
        node.metadata.state = state;
        node.metadata.last_updated = chrono::Utc::now();
        Ok(old)
    }

    /// Update or insert properties on a node. Returns only the keys whose values actually changed.
    ///
    /// A key is considered changed if it is new (not previously present) or if its value
    /// differs from the stored value (compared via `serde_json::Value::PartialEq`).
    /// The `last_updated` timestamp is only bumped when at least one value changed.
    pub fn update_properties(
        &mut self,
        id: &NodeId,
        props: HashMap<String, serde_json::Value>,
    ) -> Result<Vec<String>> {
        let node = self
            .nodes
            .get_mut(id)
            .ok_or_else(|| InspectorError::NodeNotFound(id.clone()))?;

        let mut changed_keys = Vec::new();
        for (key, new_value) in props {
            let is_changed = match node.metadata.properties.get(&key) {
                Some(existing) => existing != &new_value,
                None => true, // new key
            };
            if is_changed {
                changed_keys.push(key.clone());
                node.metadata.properties.insert(key, new_value);
            }
        }

        if !changed_keys.is_empty() {
            node.metadata.last_updated = chrono::Utc::now();
        }

        Ok(changed_keys)
    }

    /// Check if a node exists.
    pub fn contains(&self, id: &NodeId) -> bool {
        self.nodes.contains_key(id)
    }

    /// Get all descendant IDs of a node (not including the node itself).
    pub fn descendants(&self, id: &NodeId) -> Vec<NodeId> {
        let mut result = Vec::new();
        let mut queue: Vec<NodeId> = self
            .nodes
            .get(id)
            .map(|n| n.children.clone())
            .unwrap_or_default();

        while let Some(current) = queue.pop() {
            if let Some(node) = self.nodes.get(&current) {
                queue.extend(node.children.clone());
            }
            result.push(current);
        }
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::node::{NodeKind, NodeMetadata};

    fn root_meta() -> NodeMetadata {
        NodeMetadata::new(NodeKind::Root, "Dirigent")
    }

    fn connector_meta(label: &str) -> NodeMetadata {
        NodeMetadata::new(NodeKind::Connector, label).with_state(NodeState::Running)
    }

    fn make_tree() -> NodeTree {
        let root = NodeId::new("dirigent");
        let mut tree = NodeTree::new(root.clone(), root_meta());

        // Add category nodes
        tree.insert(
            root.child("connectors"),
            &root,
            NodeMetadata::new(NodeKind::Root, "Connectors"),
            None,
        )
        .unwrap();
        tree.insert(
            root.child("services"),
            &root,
            NodeMetadata::new(NodeKind::Root, "Services"),
            None,
        )
        .unwrap();

        // Add connectors
        let connectors = root.child("connectors");
        tree.insert(
            connectors.child("opencode-1"),
            &connectors,
            connector_meta("OpenCode #1"),
            None,
        )
        .unwrap();
        tree.insert(
            connectors.child("acp-claude"),
            &connectors,
            connector_meta("ACP Claude"),
            None,
        )
        .unwrap();

        // Add a process child under acp-claude
        let acp = connectors.child("acp-claude");
        tree.insert(
            acp.child("stdio-process"),
            &acp,
            NodeMetadata::new(NodeKind::Process, "stdio-transport")
                .with_state(NodeState::Running)
                .with_property("pid", serde_json::json!(42)),
            None,
        )
        .unwrap();

        tree
    }

    #[test]
    fn test_new_tree_has_root() {
        let root = NodeId::new("dirigent");
        let tree = NodeTree::new(root.clone(), root_meta());

        assert_eq!(tree.len(), 1);
        assert!(tree.get(&root).is_some());
        assert_eq!(tree.root_id(), &root);
    }

    #[test]
    fn test_insert_and_lookup() {
        let tree = make_tree();

        assert_eq!(tree.len(), 6); // root + connectors + services + opencode + acp + stdio
        assert!(tree.contains(&NodeId::new("dirigent/connectors/acp-claude")));
        assert!(tree.contains(&NodeId::new("dirigent/connectors/acp-claude/stdio-process")));
    }

    #[test]
    fn test_insert_duplicate_fails() {
        let mut tree = make_tree();
        let result = tree.insert(
            NodeId::new("dirigent/connectors/opencode-1"),
            &NodeId::new("dirigent/connectors"),
            connector_meta("Duplicate"),
            None,
        );
        assert!(matches!(result, Err(InspectorError::NodeAlreadyExists(_))));
    }

    #[test]
    fn test_insert_missing_parent_fails() {
        let mut tree = make_tree();
        let result = tree.insert(
            NodeId::new("dirigent/nonexistent/child"),
            &NodeId::new("dirigent/nonexistent"),
            connector_meta("Orphan"),
            None,
        );
        assert!(matches!(result, Err(InspectorError::ParentNotFound(_))));
    }

    #[test]
    fn test_children() {
        let tree = make_tree();
        let connectors = NodeId::new("dirigent/connectors");
        let children = tree.children(&connectors);

        assert_eq!(children.len(), 2);
        let child_ids: Vec<&str> = children.iter().map(|c| c.id.as_str()).collect();
        assert!(child_ids.contains(&"dirigent/connectors/opencode-1"));
        assert!(child_ids.contains(&"dirigent/connectors/acp-claude"));
    }

    #[test]
    fn test_ancestors() {
        let tree = make_tree();
        let stdio = NodeId::new("dirigent/connectors/acp-claude/stdio-process");
        let ancestors = tree.ancestors(&stdio);

        assert_eq!(ancestors.len(), 3);
        assert_eq!(ancestors[0].id.as_str(), "dirigent/connectors/acp-claude");
        assert_eq!(ancestors[1].id.as_str(), "dirigent/connectors");
        assert_eq!(ancestors[2].id.as_str(), "dirigent");
    }

    #[test]
    fn test_remove_reparents_children() {
        let mut tree = make_tree();

        // Remove acp-claude; its child (stdio-process) should be reparented to "connectors"
        tree.remove(&NodeId::new("dirigent/connectors/acp-claude"))
            .unwrap();

        assert!(!tree.contains(&NodeId::new("dirigent/connectors/acp-claude")));
        assert!(tree.contains(&NodeId::new("dirigent/connectors/acp-claude/stdio-process")));

        // stdio-process should now be a child of connectors
        let connectors = tree.get(&NodeId::new("dirigent/connectors")).unwrap();
        assert!(connectors
            .children
            .contains(&NodeId::new("dirigent/connectors/acp-claude/stdio-process")));

        // stdio-process parent should be connectors
        let stdio = tree
            .get(&NodeId::new("dirigent/connectors/acp-claude/stdio-process"))
            .unwrap();
        assert_eq!(
            stdio.parent.as_ref().unwrap().as_str(),
            "dirigent/connectors"
        );
    }

    #[test]
    fn test_remove_subtree() {
        let mut tree = make_tree();

        tree.remove_subtree(&NodeId::new("dirigent/connectors/acp-claude"))
            .unwrap();

        assert!(!tree.contains(&NodeId::new("dirigent/connectors/acp-claude")));
        assert!(!tree.contains(&NodeId::new("dirigent/connectors/acp-claude/stdio-process")));
        assert_eq!(tree.len(), 4); // root + connectors + services + opencode

        // connectors should only have opencode-1
        let connectors = tree.get(&NodeId::new("dirigent/connectors")).unwrap();
        assert_eq!(connectors.children.len(), 1);
    }

    #[test]
    fn test_cannot_remove_root() {
        let mut tree = make_tree();
        let result = tree.remove(&NodeId::new("dirigent"));
        assert!(matches!(result, Err(InspectorError::CannotRemoveRoot)));

        let result = tree.remove_subtree(&NodeId::new("dirigent"));
        assert!(matches!(result, Err(InspectorError::CannotRemoveRoot)));
    }

    #[test]
    fn test_update_state() {
        let mut tree = make_tree();
        let id = NodeId::new("dirigent/connectors/opencode-1");

        let old = tree
            .update_state(&id, NodeState::Error("timeout".into()))
            .unwrap();
        assert_eq!(old, NodeState::Running);

        let node = tree.get(&id).unwrap();
        assert_eq!(node.metadata.state, NodeState::Error("timeout".into()));
    }

    #[test]
    fn test_update_properties() {
        let mut tree = make_tree();
        let id = NodeId::new("dirigent/connectors/acp-claude/stdio-process");

        let mut props = HashMap::new();
        props.insert("cpu_percent".to_string(), serde_json::json!(45.2));
        props.insert("memory_mb".to_string(), serde_json::json!(128));

        let keys = tree.update_properties(&id, props).unwrap();
        assert_eq!(keys.len(), 2);

        let node = tree.get(&id).unwrap();
        assert_eq!(node.metadata.properties["pid"], serde_json::json!(42)); // original preserved
        assert_eq!(
            node.metadata.properties["cpu_percent"],
            serde_json::json!(45.2)
        );
    }

    #[test]
    fn test_update_properties_no_change() {
        let mut tree = make_tree();
        let id = NodeId::new("dirigent/connectors/acp-claude/stdio-process");

        // First update: new key, should be reported as changed
        let mut props = HashMap::new();
        props.insert("cpu_percent".to_string(), serde_json::json!(45.2));
        let keys = tree.update_properties(&id, props).unwrap();
        assert_eq!(keys.len(), 1);

        // Second update: same value, should NOT be reported as changed
        let mut props = HashMap::new();
        props.insert("cpu_percent".to_string(), serde_json::json!(45.2));
        let keys = tree.update_properties(&id, props).unwrap();
        assert_eq!(keys.len(), 0, "Same value should not be reported as changed");

        // Third update: different value, should be reported
        let mut props = HashMap::new();
        props.insert("cpu_percent".to_string(), serde_json::json!(50.0));
        let keys = tree.update_properties(&id, props).unwrap();
        assert_eq!(keys.len(), 1);
    }

    #[test]
    fn test_descendants() {
        let tree = make_tree();
        let root = NodeId::new("dirigent");
        let descendants = tree.descendants(&root);

        assert_eq!(descendants.len(), 5); // all except root itself
    }

    #[test]
    fn test_is_empty() {
        let root = NodeId::new("dirigent");
        let tree = NodeTree::new(root, root_meta());
        assert!(tree.is_empty());

        let tree = make_tree();
        assert!(!tree.is_empty());
    }
}
