use dirigent_inspector::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

/// Full integration test simulating a Dirigent-like setup:
/// - Root node "dirigent"
/// - Connector nodes under "dirigent/connectors"
/// - Process node under a connector
/// - Service nodes under "dirigent/services"
/// - System node under "dirigent/system"
/// - Bidirectional channel communication
/// - Snapshot capture
#[tokio::test]
async fn test_full_inspector_tree() {
    let registry = Arc::new(InspectorRegistry::new());
    let root = registry.root_id().await;
    assert_eq!(root.as_str(), "dirigent");

    // Subscribe to events
    let mut event_rx = registry.subscribe();

    // -- Build the tree structure --

    // Category: connectors
    let connectors_handle = registry
        .register(
            NodeId::new("dirigent/connectors"),
            &root,
            NodeMetadata::new(NodeKind::Custom("category".into()), "Connectors")
                .with_state(NodeState::Running),
            None,
        )
        .await
        .unwrap();

    // Category: services
    let mut services_handle = registry
        .register(
            NodeId::new("dirigent/services"),
            &root,
            NodeMetadata::new(NodeKind::Custom("category".into()), "Services")
                .with_state(NodeState::Running),
            None,
        )
        .await
        .unwrap();

    // Category: system
    let mut system_handle = registry
        .register(
            NodeId::new("dirigent/system"),
            &root,
            NodeMetadata::new(NodeKind::Custom("category".into()), "System")
                .with_state(NodeState::Running),
            None,
        )
        .await
        .unwrap();

    // Connector: ACP Claude
    let acp_handle = connectors_handle
        .register_child(
            NodeId::new("dirigent/connectors/acp-claude"),
            NodeMetadata::new(NodeKind::Connector, "ACP Claude")
                .with_state(NodeState::Running)
                .with_property("transport", serde_json::json!("stdio")),
            None,
        )
        .await
        .unwrap();

    // Process: stdio transport child
    let current_pid = std::process::id();
    let proc_handle = acp_handle
        .register_child(
            NodeId::new("dirigent/connectors/acp-claude/stdio-process"),
            NodeMetadata::new(NodeKind::Process, "stdio-transport")
                .with_state(NodeState::Running)
                .with_property("pid", serde_json::json!(current_pid)),
            None,
        )
        .await
        .unwrap();

    // Connector: OpenCode
    let _opencode_handle = connectors_handle
        .register_child(
            NodeId::new("dirigent/connectors/opencode-1"),
            NodeMetadata::new(NodeKind::Connector, "OpenCode #1")
                .with_state(NodeState::Running)
                .with_property("base_url", serde_json::json!("http://localhost:12225")),
            None,
        )
        .await
        .unwrap();

    // Service: Archivist
    let _archivist_handle = services_handle
        .register_child(
            NodeId::new("dirigent/services/archivist"),
            NodeMetadata::new(NodeKind::Service, "Archivist EventHandler")
                .with_state(NodeState::Idle),
            None,
        )
        .await
        .unwrap();

    // System: Host
    let _host_handle = system_handle
        .register_child(
            NodeId::new("dirigent/system/host"),
            NodeMetadata::new(NodeKind::System, "Host Machine").with_state(NodeState::Running),
            None,
        )
        .await
        .unwrap();

    // -- Verify tree structure --
    // dirigent, connectors, services, system, acp-claude, stdio-process, opencode-1, archivist, host = 9
    assert_eq!(registry.node_count().await, 9);

    // Check children
    let root_children = registry.get_children(&root).await;
    assert_eq!(root_children.len(), 3); // connectors, services, system

    let connector_children = registry
        .get_children(&NodeId::new("dirigent/connectors"))
        .await;
    assert_eq!(connector_children.len(), 2); // acp-claude, opencode-1

    let acp_children = registry
        .get_children(&NodeId::new("dirigent/connectors/acp-claude"))
        .await;
    assert_eq!(acp_children.len(), 1); // stdio-process

    // -- State transitions --
    proc_handle
        .set_state(NodeState::Busy("processing message".into()))
        .await
        .unwrap();

    let proc_meta = registry
        .get_node(&NodeId::new("dirigent/connectors/acp-claude/stdio-process"))
        .await
        .unwrap();
    assert_eq!(
        proc_meta.state,
        NodeState::Busy("processing message".into())
    );

    // -- Property updates --
    let mut props = HashMap::new();
    props.insert("cpu_percent".to_string(), serde_json::json!(23.5));
    props.insert("memory_mb".to_string(), serde_json::json!(256));
    proc_handle.set_properties(props).await.unwrap();

    let proc_meta = registry
        .get_node(&NodeId::new("dirigent/connectors/acp-claude/stdio-process"))
        .await
        .unwrap();
    assert_eq!(proc_meta.properties["cpu_percent"], serde_json::json!(23.5));
    assert_eq!(proc_meta.properties["pid"], serde_json::json!(current_pid)); // original preserved

    // -- Snapshot --
    let snapshot = registry.snapshot().await;
    assert_eq!(snapshot.node_count(), 9);

    // Verify snapshot structure
    let snap_root = snapshot.root().unwrap();
    assert_eq!(snap_root.id.as_str(), "dirigent");
    assert_eq!(snap_root.children.len(), 3);

    let snap_proc = snapshot
        .find(&NodeId::new("dirigent/connectors/acp-claude/stdio-process"))
        .unwrap();
    assert_eq!(
        snap_proc.parent,
        Some(NodeId::new("dirigent/connectors/acp-claude"))
    );
    assert_eq!(
        snap_proc.metadata.state,
        NodeState::Busy("processing message".into())
    );

    // Snapshot serialization roundtrip
    let json = serde_json::to_string(&snapshot).unwrap();
    let deserialized: TreeSnapshot = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.node_count(), 9);

    // -- Events --
    // Drain all events that were emitted during setup
    let mut event_count = 0;
    while let Ok(event) = event_rx.try_recv() {
        event_count += 1;
        // Just verify they're valid events
        match event {
            InspectorEvent::NodeRegistered { .. }
            | InspectorEvent::StateChanged { .. }
            | InspectorEvent::PropertiesUpdated { .. }
            | InspectorEvent::NodeRemoved { .. } => {}
        }
    }
    assert!(event_count > 0, "Should have received events");

    // -- Cleanup: detach category handles so they survive --
    services_handle.detach();
    system_handle.detach();
}

/// Test process monitor with the current process.
#[tokio::test]
async fn test_process_monitor_integration() {
    let registry = Arc::new(InspectorRegistry::new());
    let root = registry.root_id().await;

    let current_pid = std::process::id();
    let node_id = NodeId::new("dirigent/test-process");

    let mut handle = registry
        .register(
            node_id.clone(),
            &root,
            NodeMetadata::new(NodeKind::Process, "Test Process").with_state(NodeState::Running),
            None,
        )
        .await
        .unwrap();
    handle.detach();

    // Create monitor and track current process
    let mut monitor = ProcessMonitor::new();
    monitor.track(current_pid, node_id.clone());

    // Start polling
    let task = monitor.start_polling(Arc::clone(&registry), Duration::from_millis(100));

    // Wait for data to be populated
    tokio::time::sleep(Duration::from_millis(350)).await;

    let meta = registry.get_node(&node_id).await.unwrap();
    assert!(
        meta.properties.contains_key("pid"),
        "Should have PID property"
    );
    assert!(
        meta.properties.contains_key("memory_bytes"),
        "Should have memory property"
    );
    assert_eq!(meta.properties["pid"], serde_json::json!(current_pid));

    task.abort();
}

/// Test bidirectional channel communication with a simulated node loop.
#[tokio::test]
async fn test_channel_integration() {
    let (sender, mut receiver) = inspector_channel(10);

    // Simulate a node loop that handles commands
    let node_loop = tokio::spawn(async move {
        let mut handled = 0;
        while let Some((cmd, resp_tx)) = receiver.recv().await {
            let response = match cmd.kind {
                CommandKind::Introspect => CommandResponse::ok(
                    &cmd.id,
                    serde_json::json!({
                        "queue_depth": receiver.pending_count(),
                        "sessions_active": 3
                    }),
                ),
                CommandKind::Execute(ref name) if name == "restart" => {
                    CommandResponse::ok(&cmd.id, serde_json::json!("restarting..."))
                }
                _ => CommandResponse::err(&cmd.id, "unknown command"),
            };
            let _ = resp_tx.send(response);
            handled += 1;
            if handled >= 2 {
                break;
            }
        }
    });

    // Send introspect command
    let resp = sender
        .send(NodeCommand {
            id: "cmd-1".to_string(),
            kind: CommandKind::Introspect,
            payload: serde_json::Value::Null,
        })
        .await
        .unwrap();
    assert!(resp.success);
    assert_eq!(resp.data["sessions_active"], 3);

    // Send execute command
    let resp = sender
        .send(NodeCommand {
            id: "cmd-2".to_string(),
            kind: CommandKind::Execute("restart".to_string()),
            payload: serde_json::Value::Null,
        })
        .await
        .unwrap();
    assert!(resp.success);

    node_loop.await.unwrap();
}

/// Test that dropping a handle auto-deregisters, and subtree removal works.
#[tokio::test]
async fn test_lifecycle_management() {
    let registry = Arc::new(InspectorRegistry::new());
    let root = registry.root_id().await;

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

    let _child1 = parent
        .register_child(
            NodeId::new("dirigent/parent/child1"),
            NodeMetadata::new(NodeKind::Process, "Child 1"),
            None,
        )
        .await
        .unwrap();

    let _child2 = parent
        .register_child(
            NodeId::new("dirigent/parent/child2"),
            NodeMetadata::new(NodeKind::AsyncTask, "Child 2"),
            None,
        )
        .await
        .unwrap();

    assert_eq!(registry.node_count().await, 4); // root + parent + 2 children

    // Remove entire subtree
    registry
        .deregister_subtree(&NodeId::new("dirigent/parent"))
        .await
        .unwrap();

    assert_eq!(registry.node_count().await, 1); // only root remains
}
