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

/// Handle to a registered node in the inspector tree.
///
/// Returned when a node is registered. The producer (connector, service, etc.)
/// uses this handle to update their node's state and properties without needing
/// direct access to the registry.
///
/// When the handle is dropped, the node is automatically deregistered (best-effort).
/// To keep the node alive without the handle, call `detach()` before dropping.
pub struct NodeHandle {
    id: NodeId,
    registry: Arc<InspectorRegistry>,
    detached: bool,
}

impl NodeHandle {
    pub(crate) fn new(id: NodeId, registry: Arc<InspectorRegistry>) -> Self {
        Self {
            id,
            registry,
            detached: false,
        }
    }

    /// Get this node's ID.
    pub fn id(&self) -> &NodeId {
        &self.id
    }

    /// Update this node's lifecycle state.
    pub async fn set_state(&self, state: NodeState) -> Result<()> {
        self.registry.update_state(&self.id, state).await
    }

    /// Set a single property on this node.
    pub async fn set_property(&self, key: &str, value: serde_json::Value) -> Result<()> {
        let mut props = HashMap::new();
        props.insert(key.to_string(), value);
        self.registry.update_properties(&self.id, props).await
    }

    /// Set multiple properties on this node.
    pub async fn set_properties(&self, props: HashMap<String, serde_json::Value>) -> Result<()> {
        self.registry.update_properties(&self.id, props).await
    }

    /// Register a child node under this node.
    ///
    /// Returns a new `NodeHandle` for the child.
    pub async fn register_child(
        &self,
        child_id: NodeId,
        metadata: NodeMetadata,
        inspectable: Option<Arc<dyn Inspectable>>,
    ) -> Result<NodeHandle> {
        self.registry
            .register(child_id, &self.id, metadata, inspectable)
            .await
    }

    /// Explicitly deregister this node and consume the handle.
    pub async fn deregister(mut self) -> Result<()> {
        self.detached = true; // prevent Drop from double-deregistering
        self.registry.deregister(&self.id).await
    }

    /// Detach this handle so the node survives when the handle is dropped.
    ///
    /// After calling this, dropping the handle will NOT deregister the node.
    /// The node can still be removed via `InspectorRegistry::deregister()`.
    pub fn detach(&mut self) {
        self.detached = true;
    }

    /// Get a reference to the registry this handle is connected to.
    pub fn registry(&self) -> &Arc<InspectorRegistry> {
        &self.registry
    }
}

impl Drop for NodeHandle {
    fn drop(&mut self) {
        if !self.detached {
            let id = self.id.clone();
            let registry = Arc::clone(&self.registry);
            // Best-effort async deregister from a sync Drop context
            tokio::spawn(async move {
                let _ = registry.deregister(&id).await;
            });
        }
    }
}

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

    #[tokio::test]
    async fn test_handle_set_state() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        let handle = registry
            .register(
                NodeId::new("dirigent/test"),
                &root,
                NodeMetadata::new(NodeKind::Service, "Test"),
                None,
            )
            .await
            .unwrap();

        handle.set_state(NodeState::Running).await.unwrap();

        let meta = registry
            .get_node(&NodeId::new("dirigent/test"))
            .await
            .unwrap();
        assert_eq!(meta.state, NodeState::Running);
    }

    #[tokio::test]
    async fn test_handle_set_property() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        let handle = registry
            .register(
                NodeId::new("dirigent/proc"),
                &root,
                NodeMetadata::new(NodeKind::Process, "Proc"),
                None,
            )
            .await
            .unwrap();

        handle
            .set_property("pid", serde_json::json!(9999))
            .await
            .unwrap();

        let meta = registry
            .get_node(&NodeId::new("dirigent/proc"))
            .await
            .unwrap();
        assert_eq!(meta.properties["pid"], serde_json::json!(9999));
    }

    #[tokio::test]
    async fn test_handle_register_child() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        let parent_handle = registry
            .register(
                NodeId::new("dirigent/parent"),
                &root,
                NodeMetadata::new(NodeKind::Connector, "Parent"),
                None,
            )
            .await
            .unwrap();

        let child_handle = parent_handle
            .register_child(
                NodeId::new("dirigent/parent/child"),
                NodeMetadata::new(NodeKind::Process, "Child"),
                None,
            )
            .await
            .unwrap();

        assert_eq!(child_handle.id().as_str(), "dirigent/parent/child");
        assert!(
            registry
                .contains(&NodeId::new("dirigent/parent/child"))
                .await
        );
    }

    #[tokio::test]
    async fn test_handle_deregister() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        let handle = registry
            .register(
                NodeId::new("dirigent/temp"),
                &root,
                NodeMetadata::new(NodeKind::AsyncTask, "Temp"),
                None,
            )
            .await
            .unwrap();

        assert!(registry.contains(&NodeId::new("dirigent/temp")).await);

        handle.deregister().await.unwrap();

        assert!(!registry.contains(&NodeId::new("dirigent/temp")).await);
    }

    #[tokio::test]
    async fn test_handle_drop_deregisters() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        {
            let _handle = registry
                .register(
                    NodeId::new("dirigent/ephemeral"),
                    &root,
                    NodeMetadata::new(NodeKind::AsyncTask, "Ephemeral"),
                    None,
                )
                .await
                .unwrap();
            // handle dropped here
        }

        // Give the spawned deregister task a moment to run
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;

        assert!(
            !registry.contains(&NodeId::new("dirigent/ephemeral")).await,
            "Node should be deregistered after handle drop"
        );
    }

    #[tokio::test]
    async fn test_handle_detach_survives_drop() {
        let registry = Arc::new(InspectorRegistry::new());
        let root = registry.root_id().await;

        {
            let mut handle = registry
                .register(
                    NodeId::new("dirigent/persistent"),
                    &root,
                    NodeMetadata::new(NodeKind::Service, "Persistent"),
                    None,
                )
                .await
                .unwrap();
            handle.detach();
            // handle dropped here, but detached
        }

        tokio::time::sleep(std::time::Duration::from_millis(50)).await;

        assert!(
            registry.contains(&NodeId::new("dirigent/persistent")).await,
            "Detached node should survive handle drop"
        );
    }
}
