//! Unit tests for CLI lifecycle adapter

use mecha10_cli::dev::lifecycle_adapter::CliLifecycleManager;
use mecha10_cli::types::project::{LifecycleConfig, ModeConfig, NodesConfig, ProjectConfig, RobotConfig};
use std::collections::HashMap;

fn create_test_lifecycle_config() -> LifecycleConfig {
    let mut modes = HashMap::new();

    modes.insert(
        "dev".to_string(),
        ModeConfig {
            description: Some("Development mode".to_string()),
            nodes: vec!["listener".to_string(), "speaker".to_string()],
        },
    );

    modes.insert(
        "simulation".to_string(),
        ModeConfig {
            description: Some("Simulation mode".to_string()),
            nodes: vec![
                "listener".to_string(),
                "speaker".to_string(),
                "simulation-bridge".to_string(),
            ],
        },
    );

    modes.insert(
        "full".to_string(),
        ModeConfig {
            description: Some("Full mode with hardware".to_string()),
            nodes: vec![
                "listener".to_string(),
                "speaker".to_string(),
                "imu".to_string(),
                "motor".to_string(),
            ],
        },
    );

    LifecycleConfig {
        modes,
        default_mode: "dev".to_string(),
    }
}

fn create_project_config(lifecycle: Option<LifecycleConfig>) -> ProjectConfig {
    ProjectConfig {
        name: "test".to_string(),
        version: "0.1.0".to_string(),
        robot: RobotConfig {
            id: "test".to_string(),
            platform: None,
            description: None,
        },
        lifecycle,
        simulation: None,
        nodes: NodesConfig::new(),
        behaviors: None,
        services: Default::default(),
        docker: Default::default(),
        environments: Default::default(),
    }
}

#[test]
fn test_from_project_config_with_lifecycle() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let manager = CliLifecycleManager::from_project_config(&project_config);

    assert!(manager.is_some());
    let manager = manager.unwrap();
    assert_eq!(manager.current_mode(), "dev");
}

#[test]
fn test_from_project_config_without_lifecycle() {
    let project_config = create_project_config(None);

    let manager = CliLifecycleManager::from_project_config(&project_config);

    assert!(manager.is_none());
}

#[test]
fn test_nodes_for_current_mode() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    let nodes = manager.nodes_for_current_mode();
    assert_eq!(nodes.len(), 2);
    assert!(nodes.contains(&"listener".to_string()));
    assert!(nodes.contains(&"speaker".to_string()));
}

#[test]
fn test_mark_nodes_running() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    manager.mark_nodes_running(&["listener".to_string(), "speaker".to_string()]);

    // After marking nodes as running, calculate diff should show no start needed
    let diff = manager.calculate_mode_diff("dev").unwrap();
    assert_eq!(diff.start.len(), 0);
    assert_eq!(diff.stop.len(), 0);
}

#[test]
fn test_calculate_mode_diff_start_nodes() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    // Mark some nodes as running
    manager.mark_nodes_running(&["listener".to_string(), "speaker".to_string()]);

    // Calculate diff to simulation mode (needs simulation-bridge)
    let diff = manager.calculate_mode_diff("simulation").unwrap();

    assert_eq!(diff.start.len(), 1);
    assert!(diff.start.contains(&"simulation-bridge".to_string()));
    assert_eq!(diff.stop.len(), 0);
}

#[test]
fn test_calculate_mode_diff_stop_nodes() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    // Mark simulation nodes as running
    manager.mark_nodes_running(&[
        "listener".to_string(),
        "speaker".to_string(),
        "simulation-bridge".to_string(),
    ]);

    // Calculate diff to dev mode (should stop simulation-bridge)
    let diff = manager.calculate_mode_diff("dev").unwrap();

    assert_eq!(diff.start.len(), 0);
    assert_eq!(diff.stop.len(), 1);
    assert!(diff.stop.contains(&"simulation-bridge".to_string()));
}

#[test]
fn test_calculate_mode_diff_stops_nodes_not_in_target() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    // Mark simulation nodes as running
    manager.mark_nodes_running(&[
        "listener".to_string(),
        "speaker".to_string(),
        "simulation-bridge".to_string(),
    ]);

    // Calculate diff to full mode (should stop simulation-bridge since it's not in full mode)
    let diff = manager.calculate_mode_diff("full").unwrap();

    assert!(diff.start.contains(&"imu".to_string()));
    assert!(diff.start.contains(&"motor".to_string()));
    assert!(diff.stop.contains(&"simulation-bridge".to_string()));
}

#[test]
fn test_change_mode() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    assert_eq!(manager.current_mode(), "dev");

    let diff = manager.change_mode("simulation").unwrap();

    assert_eq!(manager.current_mode(), "simulation");
    assert_eq!(diff.start.len(), 3); // All nodes need to start
    assert_eq!(diff.stop.len(), 0); // Nothing running yet
}

#[test]
fn test_change_mode_invalid() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    let result = manager.change_mode("invalid-mode");

    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("not found"));
}

#[test]
fn test_available_modes() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    let modes = manager.available_modes();
    assert_eq!(modes.len(), 3);
    assert!(modes.contains(&"dev"));
    assert!(modes.contains(&"simulation"));
    assert!(modes.contains(&"full"));
}

#[test]
fn test_validate_valid_config() {
    let config = create_test_lifecycle_config();
    let available_nodes = vec![
        "listener".to_string(),
        "speaker".to_string(),
        "simulation-bridge".to_string(),
        "imu".to_string(),
        "motor".to_string(),
    ];

    let result = CliLifecycleManager::validate(&config, &available_nodes);
    assert!(result.is_ok());
}

#[test]
fn test_validate_missing_node() {
    let config = create_test_lifecycle_config();
    let available_nodes = vec!["listener".to_string(), "speaker".to_string()];

    let result = CliLifecycleManager::validate(&config, &available_nodes);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("unknown node"));
}

#[test]
fn test_validate_missing_default_mode() {
    let mut config = create_test_lifecycle_config();
    config.default_mode = "nonexistent".to_string();

    let available_nodes = vec![
        "listener".to_string(),
        "speaker".to_string(),
        "simulation-bridge".to_string(),
    ];

    let result = CliLifecycleManager::validate(&config, &available_nodes);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("not found in modes"));
}

#[test]
fn test_mark_nodes_stopped() {
    let config = create_test_lifecycle_config();
    let project_config = create_project_config(Some(config));

    let mut manager = CliLifecycleManager::from_project_config(&project_config).unwrap();

    // Mark nodes as running
    manager.mark_nodes_running(&["listener".to_string(), "speaker".to_string()]);

    // Mark one as stopped
    manager.mark_nodes_stopped(&["listener".to_string()]);

    // Calculate diff should now show listener needs to start again
    let diff = manager.calculate_mode_diff("dev").unwrap();
    assert_eq!(diff.start.len(), 1);
    assert!(diff.start.contains(&"listener".to_string()));
}
