use crate::node::{NodeId, NodeMetadata};
use crate::tree::NodeTree;
use serde::{Deserialize, Serialize};

/// A serializable point-in-time capture of the entire inspector tree.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TreeSnapshot {
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub nodes: Vec<NodeSnapshot>,
}

/// Snapshot of a single node.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeSnapshot {
    pub id: NodeId,
    pub parent: Option<NodeId>,
    pub children: Vec<NodeId>,
    pub metadata: NodeMetadata,
}

impl TreeSnapshot {
    /// Create a snapshot from a `NodeTree`.
    pub(crate) fn from_tree(tree: &NodeTree) -> Self {
        let nodes = tree
            .all_nodes()
            .into_iter()
            .map(|n| NodeSnapshot {
                id: n.id.clone(),
                parent: n.parent.clone(),
                children: n.children.clone(),
                metadata: n.metadata.clone(),
            })
            .collect();

        Self {
            timestamp: chrono::Utc::now(),
            nodes,
        }
    }

    /// Serialize the snapshot to a JSON `Value`.
    pub fn to_json(&self) -> serde_json::Value {
        serde_json::to_value(self).unwrap_or_default()
    }

    /// Serialize the snapshot to pretty-printed JSON string.
    pub fn to_json_pretty(&self) -> String {
        serde_json::to_string_pretty(self).unwrap_or_default()
    }

    /// Number of nodes in the snapshot.
    pub fn node_count(&self) -> usize {
        self.nodes.len()
    }

    /// Find a node by ID.
    pub fn find(&self, id: &NodeId) -> Option<&NodeSnapshot> {
        self.nodes.iter().find(|n| &n.id == id)
    }

    /// Get the root node (the one with no parent).
    pub fn root(&self) -> Option<&NodeSnapshot> {
        self.nodes.iter().find(|n| n.parent.is_none())
    }
}

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

    fn make_tree() -> NodeTree {
        let root = NodeId::new("dirigent");
        let mut tree = NodeTree::new(
            root.clone(),
            NodeMetadata::new(NodeKind::Root, "Dirigent").with_state(NodeState::Running),
        );
        tree.insert(
            NodeId::new("dirigent/svc"),
            &root,
            NodeMetadata::new(NodeKind::Service, "Service A").with_state(NodeState::Running),
            None,
        )
        .unwrap();
        tree.insert(
            NodeId::new("dirigent/svc/task"),
            &NodeId::new("dirigent/svc"),
            NodeMetadata::new(NodeKind::AsyncTask, "Task 1"),
            None,
        )
        .unwrap();
        tree
    }

    #[test]
    fn test_snapshot_from_tree() {
        let tree = make_tree();
        let snap = TreeSnapshot::from_tree(&tree);

        assert_eq!(snap.node_count(), 3);
        assert!(snap.find(&NodeId::new("dirigent")).is_some());
        assert!(snap.find(&NodeId::new("dirigent/svc")).is_some());
        assert!(snap.find(&NodeId::new("dirigent/svc/task")).is_some());
    }

    #[test]
    fn test_snapshot_root() {
        let tree = make_tree();
        let snap = TreeSnapshot::from_tree(&tree);
        let root = snap.root().unwrap();
        assert_eq!(root.id.as_str(), "dirigent");
        assert!(root.parent.is_none());
    }

    #[test]
    fn test_snapshot_serialization_roundtrip() {
        let tree = make_tree();
        let snap = TreeSnapshot::from_tree(&tree);

        let json = serde_json::to_string(&snap).unwrap();
        let deserialized: TreeSnapshot = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.node_count(), snap.node_count());
        assert_eq!(
            deserialized
                .find(&NodeId::new("dirigent/svc"))
                .unwrap()
                .metadata
                .label,
            "Service A"
        );
    }

    #[test]
    fn test_snapshot_to_json_pretty() {
        let tree = make_tree();
        let snap = TreeSnapshot::from_tree(&tree);
        let pretty = snap.to_json_pretty();

        assert!(pretty.contains("dirigent"));
        assert!(pretty.contains("Service A"));
    }

    #[test]
    fn test_snapshot_to_json_value() {
        let tree = make_tree();
        let snap = TreeSnapshot::from_tree(&tree);
        let val = snap.to_json();

        assert!(val.get("timestamp").is_some());
        assert!(val.get("nodes").unwrap().is_array());
    }
}
