//! Tests for node_selection module

use crate::dev::node_selection::{get_available_node_names, get_enabled_node_names, get_nodes_to_run, NodeToRun};
use crate::types::project::{NodesConfig, ProjectConfig, RobotConfig};

fn create_test_config() -> ProjectConfig {
    ProjectConfig {
        name: "test-project".to_string(),
        version: "0.1.0".to_string(),
        robot: RobotConfig {
            id: "test-robot".to_string(),
            platform: None,
            description: None,
        },
        simulation: None,
        lifecycle: None,
        nodes: NodesConfig(vec![
            "@mecha10/speaker".to_string(),
            "@mecha10/listener".to_string(),
            "@mecha10/motor".to_string(),
            "@local/custom-node".to_string(),
        ]),
        behaviors: None,
        services: Default::default(),
        docker: Default::default(),
        environments: Default::default(),
    }
}

#[test]
fn test_get_nodes_to_run_all() {
    let config = create_test_config();
    let nodes = get_nodes_to_run(&[], &config);

    // All nodes should be returned when no filter applied
    assert_eq!(nodes.len(), 4);
    assert!(nodes.iter().any(|n| n.name == "speaker"));
    assert!(nodes.iter().any(|n| n.name == "listener"));
    assert!(nodes.iter().any(|n| n.name == "motor"));
    assert!(nodes.iter().any(|n| n.name == "custom-node"));
}

#[test]
fn test_get_nodes_to_run_specific() {
    let config = create_test_config();
    let requested = vec!["speaker".to_string(), "motor".to_string()];
    let nodes = get_nodes_to_run(&requested, &config);

    assert_eq!(nodes.len(), 2);
    assert!(nodes.iter().any(|n| n.name == "speaker"));
    assert!(nodes.iter().any(|n| n.name == "motor"));
}

#[test]
fn test_get_nodes_to_run_empty_config() {
    let config = ProjectConfig {
        name: "test".to_string(),
        version: "0.1.0".to_string(),
        robot: RobotConfig {
            id: "test".to_string(),
            platform: None,
            description: None,
        },
        simulation: None,
        lifecycle: None,
        nodes: NodesConfig::new(),
        behaviors: None,
        services: Default::default(),
        docker: Default::default(),
        environments: Default::default(),
    };

    let nodes = get_nodes_to_run(&[], &config);
    assert_eq!(nodes.len(), 0);
}

#[test]
fn test_node_to_run_framework() {
    let node = NodeToRun {
        name: "speaker".to_string(),
        identifier: "@mecha10/speaker".to_string(),
        path: "mecha10-nodes-speaker".to_string(),
        is_framework_node: true,
    };

    // Note: binary_path may return global path if binary is installed
    // In test environment without global binaries, it falls back to local build
    let binary_path = node.binary_path("my-robot");
    assert!(binary_path.ends_with("my-robot") || binary_path.ends_with("speaker"));

    // Args depend on whether using global or local binary
    let args = node.args("my-robot");
    // If local build (starts with "target/"), should have "node" subcommand
    // If global binary, should be empty
    assert!(args.is_empty() || args == vec!["node", "speaker"]);
}

#[test]
fn test_node_to_run_project() {
    let node = NodeToRun {
        name: "custom".to_string(),
        identifier: "@local/custom".to_string(),
        path: "nodes/custom".to_string(),
        is_framework_node: false,
    };

    assert_eq!(node.binary_path("my-robot"), "target/release/custom");
    assert_eq!(node.args("my-robot"), Vec::<String>::new());
}

#[test]
fn test_node_to_run_from_spec_framework() {
    use crate::types::project::NodeSpec;

    let spec = NodeSpec::parse("@mecha10/speaker").unwrap();
    let node = NodeToRun::from_spec(&spec);

    assert_eq!(node.name, "speaker");
    assert_eq!(node.identifier, "@mecha10/speaker");
    assert_eq!(node.path, "mecha10-nodes-speaker");
    assert!(node.is_framework_node);
}

#[test]
fn test_node_to_run_from_spec_project() {
    use crate::types::project::NodeSpec;

    let spec = NodeSpec::parse("@local/custom").unwrap();
    let node = NodeToRun::from_spec(&spec);

    assert_eq!(node.name, "custom");
    assert_eq!(node.identifier, "@local/custom");
    assert_eq!(node.path, "nodes/custom");
    assert!(!node.is_framework_node);
}

#[test]
fn test_get_available_node_names() {
    let config = create_test_config();
    let names = get_available_node_names(&config);

    assert_eq!(names.len(), 4);
    assert!(names.contains(&"speaker".to_string()));
    assert!(names.contains(&"listener".to_string()));
    assert!(names.contains(&"motor".to_string()));
    assert!(names.contains(&"custom-node".to_string()));
}

#[test]
fn test_get_enabled_node_names() {
    let config = create_test_config();
    let names = get_enabled_node_names(&config);

    // In new format, all nodes are "enabled" - lifecycle modes control what runs
    assert_eq!(names.len(), 4);
    assert!(names.contains(&"speaker".to_string()));
    assert!(names.contains(&"listener".to_string()));
    assert!(names.contains(&"motor".to_string()));
    assert!(names.contains(&"custom-node".to_string()));
}

#[test]
fn test_get_available_node_names_empty() {
    let config = ProjectConfig {
        name: "test".to_string(),
        version: "0.1.0".to_string(),
        robot: RobotConfig {
            id: "test".to_string(),
            platform: None,
            description: None,
        },
        simulation: None,
        lifecycle: None,
        nodes: NodesConfig::new(),
        behaviors: None,
        services: Default::default(),
        docker: Default::default(),
        environments: Default::default(),
    };

    let names = get_available_node_names(&config);
    assert_eq!(names.len(), 0);
}

#[test]
fn test_node_spec_parsing() {
    use crate::types::project::{NodeSource, NodeSpec};

    // Framework node
    let spec = NodeSpec::parse("@mecha10/listener").unwrap();
    assert_eq!(spec.name, "listener");
    assert_eq!(spec.source, NodeSource::Framework);
    assert_eq!(spec.package_path(), "mecha10-nodes-listener");

    // Project node (@local/)
    let spec = NodeSpec::parse("@local/my-custom").unwrap();
    assert_eq!(spec.name, "my-custom");
    assert_eq!(spec.source, NodeSource::Project);
    assert_eq!(spec.package_path(), "nodes/my-custom");

    // Registry node
    let spec = NodeSpec::parse("@someorg/cool-node").unwrap();
    assert_eq!(spec.name, "cool-node");
    assert_eq!(spec.source, NodeSource::Registry("someorg".to_string()));
    assert_eq!(spec.package_path(), "node_modules/@someorg/cool-node");
}

#[test]
fn test_nodes_config_operations() {
    let mut nodes = NodesConfig::new();
    assert!(nodes.is_empty());

    nodes.add_node("@mecha10/listener");
    nodes.add_node("@local/custom");
    assert_eq!(nodes.len(), 2);

    assert!(nodes.contains("listener"));
    assert!(nodes.contains("custom"));
    assert!(!nodes.contains("nonexistent"));

    nodes.remove_node("listener");
    assert_eq!(nodes.len(), 1);
    assert!(!nodes.contains("listener"));
    assert!(nodes.contains("custom"));
}
