//! Dev mode operations (simulator, teleop, etc.)
//!
//! Helper functions for development mode operations.
//! Extracted from the dev handler to reduce complexity.

use crate::context::CliContext;
use crate::dev::node_selection::NodeToRun;
use crate::paths;
use crate::services::dev::SimulationPaths;
use crate::services::CredentialsService;
use crate::services::DockerService;
use crate::services::ModelService;
use crate::types::ProjectConfig;
use crate::ui::{LogBuffer, TeleopUi};
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

/// Configuration for launching the simulator
pub struct SimulatorLaunchConfig<'a> {
    pub godot_path: &'a Option<String>,
    pub sim_paths: &'a Option<SimulationPaths>,
    #[allow(dead_code)] // May be used in future for process naming
    pub project_name: &'a str,
    pub redis_url: &'a str,
}

/// Start Docker services if configured
pub async fn start_docker_services(ctx: &mut CliContext, config: &ProjectConfig, project_name: &str) -> Result<()> {
    println!("🐳 Starting project services...");

    let proj = ctx.project()?;
    let compose_path = proj.root().join(&config.docker.compose_file);

    if compose_path.exists() {
        let docker = DockerService::with_compose_file(compose_path.to_string_lossy().to_string());

        match docker.start_project_services(&config.docker.robot, project_name).await {
            Ok(true) => {
                println!("✅ Services started");
                println!();
            }
            Ok(false) => {
                println!("  Services already running");
                println!();
            }
            Err(e) => {
                println!("⚠️  Warning: Failed to start services: {}", e);
                println!("   You can manually start them with: mecha10 services start");
                println!();
            }
        }
    }

    Ok(())
}

/// Setup Redis connection and flush for clean dev session
pub async fn setup_redis_connection(ctx: &mut CliContext) -> Result<()> {
    println!("Checking Redis connection...");
    let redis_url = ctx.redis_url().to_string();
    let redis = ctx.redis()?;

    match redis.get_connection().await {
        Ok(mut conn) => {
            println!("✅ Redis connected: {}", redis_url);

            // Use DevService to flush Redis
            println!("🧹 Flushing Redis for clean dev session...");
            let dev_service = ctx.dev();
            dev_service.flush_redis(&mut conn).await?;
            println!("✅ Redis flushed");
        }
        Err(e) => {
            println!("❌ Redis connection failed: {}", e);
            println!();
            println!("Please ensure Redis is running:");
            println!("  mecha10 services start");
            println!("  or");
            println!("  docker compose up -d redis");
            println!();
            return Err(anyhow::anyhow!("Redis connection required for dev mode"));
        }
    }
    println!();

    Ok(())
}

/// Start headless simulation container
///
/// Launches the Docker-based headless simulation with Xvfb for camera rendering.
/// This is called automatically when simulation-bridge is started with headless: true.
///
/// # Arguments
///
/// * `ctx` - CLI context
/// * `sim_paths` - Resolved simulation paths (model, environment, networking config)
/// * `config` - Project configuration
pub async fn start_headless_simulation(
    ctx: &mut CliContext,
    sim_paths: &SimulationPaths,
    _config: &ProjectConfig,
) -> Result<()> {
    // Get project root path
    let project_root = ctx.project()?.root().to_path_buf();
    let compose_path = project_root.join(paths::docker::COMPOSE_FILE);

    if !compose_path.exists() {
        return Err(anyhow::anyhow!(
            "{} not found. Headless simulation requires Docker Compose.",
            paths::docker::COMPOSE_FILE
        ));
    }

    let docker_service = DockerService::with_compose_file(compose_path.to_string_lossy().to_string());

    // Check if Docker is available
    if let Err(e) = docker_service.check_installation() {
        return Err(anyhow::anyhow!(
            "Docker not available: {}. Install Docker or set headless: false in simulation config.",
            e
        ));
    }

    // Resolve simulation assets path for Docker mount
    // Priority: MECHA10_FRAMEWORK_PATH > cached assets from GitHub
    let simulation_mount_path = if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
        // Monorepo dev mode - use framework path
        std::path::PathBuf::from(framework_path)
    } else {
        // Production mode - ensure assets are downloaded and use cached path
        let assets_service = crate::services::SimulationAssetsService::new();
        match assets_service.ensure_assets().await {
            Ok(assets_path) => assets_path,
            Err(e) => {
                return Err(anyhow::anyhow!(
                    "Failed to get simulation assets: {}. Set MECHA10_FRAMEWORK_PATH for local development or check internet connection.",
                    e
                ));
            }
        }
    };

    // Set environment variable for docker-compose
    std::env::set_var("MECHA10_SIMULATION_PATH", &simulation_mount_path);
    println!("   Simulation assets: {}", simulation_mount_path.display());

    // Build Godot args with CONTAINER paths (not host paths)
    // Container mounts:
    //   - Simulation assets: /mecha10 (either framework or standalone assets)
    //   - Project configs: /project/configs
    //   - Project simulation: /project/simulation
    //
    // Path differs based on mode:
    //   - Framework dev: /mecha10/packages/simulation/godot-project (full monorepo)
    //   - Standalone: /mecha10/godot-project (extracted assets tarball)
    let container_godot_path = if std::env::var("MECHA10_FRAMEWORK_PATH").is_ok() {
        std::path::PathBuf::from(paths::container::FRAMEWORK_GODOT_PROJECT)
    } else {
        std::path::PathBuf::from(paths::container::FRAMEWORK_GODOT_PROJECT_LEGACY)
    };

    // Translate config paths from host to container
    let container_model_config = sim_paths.model_config_path.as_ref().map(|p| {
        let relative = p.strip_prefix(&project_root).unwrap_or(p);
        std::path::PathBuf::from(paths::container::PROJECT_ROOT).join(relative)
    });

    let container_env_config = sim_paths.environment_config_path.as_ref().map(|p| {
        let relative = p.strip_prefix(&project_root).unwrap_or(p);
        std::path::PathBuf::from(paths::container::PROJECT_ROOT).join(relative)
    });

    let dev_service = ctx.dev();
    let args = dev_service.build_godot_args(
        &container_godot_path,
        &sim_paths.model_path,
        &sim_paths.environment_path,
        container_model_config.as_ref(),
        container_env_config.as_ref(),
        false, // Don't add --headless flag; Docker uses Xvfb instead
        Some(&sim_paths.networking),
    );

    // Launch simulation via docker compose run
    match docker_service
        .compose_run_service("simulation", true, true, &args)
        .await
    {
        Ok(_) => {
            println!("✅ Simulation container started");
            println!("   View logs: docker compose logs -f simulation");
            println!();
            Ok(())
        }
        Err(e) => Err(anyhow::anyhow!(
            "Failed to start simulation container: {}. Check: docker compose logs simulation",
            e
        )),
    }
}

/// Minimal config structure to extract model requirements from node configs
#[derive(Deserialize, Default)]
struct NodeModelConfig {
    model: Option<ModelInfo>,
}

#[derive(Deserialize)]
struct ModelInfo {
    name: Option<String>,
}

/// Environment-aware node config wrapper
#[derive(Deserialize)]
struct EnvironmentNodeConfig {
    dev: Option<NodeModelConfig>,
    production: Option<NodeModelConfig>,
}

/// Parse node config from environment-aware format
fn parse_node_config(content: &str) -> Option<NodeModelConfig> {
    // Get environment from env var or default to dev
    let profile = std::env::var("MECHA10_ENVIRONMENT").unwrap_or_else(|_| "dev".to_string());

    // Try to parse as environment-aware config first
    if let Ok(env_config) = serde_json::from_str::<EnvironmentNodeConfig>(content) {
        let config = match profile.as_str() {
            "production" | "prod" => env_config.production,
            _ => env_config.dev,
        };
        return config;
    }

    // Fall back to direct parsing for backwards compatibility
    serde_json::from_str(content).ok()
}

/// Ensure all required AI models are available for nodes
///
/// Reads node configs and downloads missing models before spawning nodes.
/// This is called in standalone mode where node-runner is not available.
async fn ensure_node_models_available(node_names: &[String]) -> Result<()> {
    let model_service = match ModelService::with_models_dir("models".into()) {
        Ok(service) => service,
        Err(e) => {
            eprintln!("⚠️  Warning: Could not initialize model service: {}", e);
            return Ok(());
        }
    };

    for node_name in node_names {
        // Strip scope prefix if present (@mecha10/node-name -> node-name)
        let short_name = node_name
            .strip_prefix("@mecha10/")
            .or_else(|| node_name.strip_prefix("@local/"))
            .unwrap_or(node_name);

        // Try to load node config to find model requirements
        // Check config locations: @mecha10 for framework, plain name for project nodes
        let config_paths = vec![
            format!("configs/nodes/@mecha10/{}/config.json", short_name),
            format!("configs/nodes/{}/config.json", short_name), // Plain name for project nodes
            format!("configs/nodes/@local/{}/config.json", short_name), // Legacy format
        ];

        for config_path in &config_paths {
            let path = PathBuf::from(config_path);
            if !path.exists() {
                continue;
            }

            // Read and parse config
            let content = match std::fs::read_to_string(&path) {
                Ok(c) => c,
                Err(_) => continue,
            };

            // Parse environment-aware config
            let config = match parse_node_config(&content) {
                Some(c) => c,
                None => continue,
            };

            // Check if model is required
            if let Some(model_info) = config.model {
                if let Some(model_name) = model_info.name {
                    let model_path = PathBuf::from(format!("models/{}/model.onnx", model_name));
                    if !model_path.exists() {
                        println!("📦 Downloading model '{}' for node '{}'...", model_name, node_name);
                        match model_service.pull(&model_name, None).await {
                            Ok(_) => println!("✅ Model '{}' ready", model_name),
                            Err(e) => {
                                eprintln!("⚠️  Warning: Failed to download model '{}': {}", model_name, e);
                                eprintln!("   You can manually download with: mecha10 models pull {}", model_name);
                            }
                        }
                    }
                }
            }
            break; // Found and processed config, stop looking
        }
    }

    Ok(())
}

/// Spawn nodes by name
///
/// This is used when lifecycle management is enabled and we have node names
/// instead of NodeToRun objects.
///
/// Uses mecha10-node-runner if available (framework dev mode), otherwise
/// spawns nodes directly via the project binary (standalone mode).
///
/// # Arguments
///
/// * `ctx` - CLI context
/// * `node_names` - Names of nodes to spawn
/// * `project_name` - Project name (used for direct spawning in standalone mode)
/// * `project_config` - Project configuration (for environment variables)
/// * `log_buffer` - Optional log buffer for logging node start events
pub fn spawn_nodes_by_name(
    ctx: &mut CliContext,
    node_names: &[String],
    project_name: &str,
    project_config: &ProjectConfig,
    log_buffer: Option<&LogBuffer>,
) -> Result<HashMap<String, u32>> {
    use crate::services::ProcessService;

    println!("Starting nodes...");
    println!();

    // Use shared process service so CommandListener can control these nodes
    let shared_process = ctx.shared_process();
    let mut process_service = shared_process.lock().unwrap();

    // Debug: Log to file to verify we're using shared service
    let _ = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open("/tmp/command-listener.log")
        .and_then(|mut f| {
            use std::io::Write;
            writeln!(
                f,
                "[spawn_nodes_by_name] Using shared ProcessService, ptr={:p}",
                &*process_service
            )
        });

    // Check if node-runner is available (framework dev mode)
    let use_node_runner = ProcessService::is_node_runner_available();

    // Ensure models are downloaded before spawning nodes (both modes)
    // Use block_in_place to allow blocking within the async runtime
    if let Err(e) = tokio::task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(ensure_node_models_available(node_names))
    }) {
        eprintln!("⚠️  Warning: Failed to check/download models: {}", e);
    }

    if !use_node_runner {
        println!("  (standalone mode - spawning nodes directly)");
        println!();
    }

    // Build project environment variables for node config substitution
    // These are needed by both node-runner and direct spawn for ${VAR} substitution
    let project_env = {
        let mut env = HashMap::new();
        // Control plane URL from mecha10.json environments
        env.insert(
            "CONTROL_PLANE_URL".to_string(),
            project_config.environments.control_plane_url(),
        );
        // Robot info
        env.insert("ROBOT_ID".to_string(), project_config.robot.id.clone());
        // API key from global credentials (user must be logged in)
        let credentials_service = CredentialsService::new();
        if let Ok(Some(api_key)) = credentials_service.get_api_key() {
            env.insert("ROBOT_API_KEY".to_string(), api_key.clone());
            tracing::debug!("🔑 Loaded API key from credentials: {}", &api_key[..20]);
        } else {
            tracing::warn!("⚠️  No API key found in credentials - did you run 'mecha10 auth login'?");
        }
        // WebRTC relay URL derived from control plane
        env.insert("WEBRTC_RELAY_URL".to_string(), project_config.environments.relay_url());
        // Redis URL (local default, env var override)
        env.insert("REDIS_URL".to_string(), project_config.environments.redis_url());

        // Debug: Log all environment variables being injected
        tracing::debug!("📋 Environment variables to inject:");
        tracing::debug!(
            "  CONTROL_PLANE_URL: {}",
            env.get("CONTROL_PLANE_URL").unwrap_or(&"<missing>".to_string())
        );
        tracing::debug!(
            "  ROBOT_ID: {}",
            env.get("ROBOT_ID").unwrap_or(&"<missing>".to_string())
        );
        tracing::debug!(
            "  ROBOT_API_KEY: {}",
            if env.contains_key("ROBOT_API_KEY") {
                "<present>"
            } else {
                "<missing>"
            }
        );
        tracing::debug!(
            "  WEBRTC_RELAY_URL: {}",
            env.get("WEBRTC_RELAY_URL").unwrap_or(&"<missing>".to_string())
        );
        tracing::debug!(
            "  REDIS_URL: {}",
            env.get("REDIS_URL").unwrap_or(&"<missing>".to_string())
        );

        Some(env)
    };

    // Spawn each node
    let mut pids = HashMap::new();
    for node_name in node_names {
        // Local project nodes: @local/* (legacy) or plain names (no @ prefix)
        // Framework nodes: @mecha10/*
        let is_local_node = node_name.starts_with("@local/") || !node_name.starts_with('@');
        let should_use_node_runner = use_node_runner && is_local_node;

        tracing::debug!(
            "🚀 Spawning node: {} (use_node_runner: {}, is_local: {})",
            node_name,
            should_use_node_runner,
            is_local_node
        );
        let result = if should_use_node_runner {
            process_service.spawn_node_runner(node_name, project_env.clone())
        } else {
            process_service.spawn_node_direct(node_name, project_name, project_env.clone())
        };

        match result {
            Ok(pid) => {
                pids.insert(node_name.clone(), pid);
                // Debug: Log spawned node
                let _ = std::fs::OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open("/tmp/command-listener.log")
                    .and_then(|mut f| {
                        use std::io::Write;
                        writeln!(f, "[spawn_nodes_by_name] Spawned '{}' with PID {}", node_name, pid)
                    });
                if let Some(buffer) = log_buffer {
                    buffer.push(format!("✅ STARTED: {} (PID: {})", node_name, pid));
                }
            }
            Err(e) => {
                eprintln!("Failed to spawn {}: {}", node_name, e);
                if let Some(buffer) = log_buffer {
                    buffer.push(format!("ERROR: Failed to start {}: {}", node_name, e));
                }
            }
        }
    }

    for (node, pid) in &pids {
        println!("  ✅ {} (PID: {})", node, pid);
    }
    println!();

    Ok(pids)
}

/// Build all nodes
///
/// Smart build logic:
/// - Framework dev mode: Build only packages needed by this project + node-runner
/// - Standalone mode: No build needed - nodes are bundled in the CLI binary
///
/// This also pre-builds local project nodes (from nodes/ directory) so that
/// the node-runner doesn't need to build them at spawn time.
pub fn build_nodes(ctx: &mut CliContext) -> Result<()> {
    use crate::services::ProcessService;

    // Check if we're in framework dev mode
    if ProcessService::is_framework_dev_mode() {
        println!("🔧 Framework dev mode: Building required packages from source...");
        let process_service = ctx.process();

        // Build node-runner first from framework (required for Phase 2+ of node lifecycle)
        // This must be built from the framework monorepo, not the generated project
        println!("Building mecha10-node-runner from framework...");
        process_service.build_from_framework("mecha10-node-runner", true)?;

        // Use smart selective build instead of build_all
        // This only builds packages needed by this specific project
        process_service.build_project_packages(true)?;

        println!("✅ Build complete");
        println!();
    } else {
        // Standalone mode: No build needed!
        // Standard nodes are bundled in the CLI binary and run via `mecha10 node <name>`
        println!("⚡ Using bundled nodes (no build required)");
        println!();
    }

    // Pre-build local project nodes so node-runner doesn't need to build at spawn time
    // This prevents the TUI from showing "STARTED" while nodes are still building
    build_local_project_nodes()?;

    Ok(())
}

/// Pre-build all local project nodes from the nodes/ directory
///
/// This ensures that when node-runner spawns a node, the binary already exists
/// and no compilation is needed. This prevents the "started" status from appearing
/// in the TUI while the node is still building.
fn build_local_project_nodes() -> Result<()> {
    use crate::types::NodeSource;
    use std::process::Command;

    // Load project config
    let config_path = std::path::Path::new(paths::PROJECT_CONFIG);
    if !config_path.exists() {
        return Ok(()); // No project config, nothing to build
    }

    let config_content = std::fs::read_to_string(config_path)?;
    let config: ProjectConfig = serde_json::from_str(&config_content)?;

    // Get local project nodes
    let local_nodes: Vec<_> = config
        .nodes
        .get_node_specs()
        .into_iter()
        .filter(|spec| spec.source == NodeSource::Project)
        .collect();

    if local_nodes.is_empty() {
        return Ok(());
    }

    println!("🔨 Pre-building local project nodes...");

    for node_spec in &local_nodes {
        let node_name = &node_spec.name;

        // Check if node directory exists
        let node_dir = std::path::Path::new("nodes").join(node_name);
        if !node_dir.exists() {
            tracing::warn!("Local node directory not found: nodes/{}", node_name);
            continue;
        }

        println!("  Building {}...", node_name);

        let output = Command::new("cargo")
            .args(["build", "-p", node_name])
            .output()
            .map_err(|e| anyhow::anyhow!("Failed to run cargo build for {}: {}", node_name, e))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!(
                "Failed to build local node '{}': {}",
                node_name,
                stderr
            ));
        }
    }

    println!("✅ Local nodes built");
    println!();

    Ok(())
}

/// Run watch mode - monitors processes and rebuilds on file changes
pub async fn run_watch_mode(ctx: &mut CliContext, _pids: HashMap<String, u32>) -> Result<()> {
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;
    use std::time::Duration;

    println!("Watch mode enabled (file changes will trigger rebuild)");
    println!();
    println!("Press Ctrl+C to stop all nodes");
    println!();

    // Set up signal handler
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();

    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
    })?;

    let process_service = ctx.process();

    // Watch loop (simplified - full implementation would use file watching)
    while running.load(Ordering::SeqCst) {
        tokio::time::sleep(Duration::from_secs(1)).await;

        // Check process status
        let status = process_service.get_status();
        for (node, node_status) in &status {
            if !node_status.contains("running") {
                println!("⚠️  {} stopped unexpectedly", node);
            }
        }
    }

    println!();
    println!("Stopping all nodes...");
    process_service.cleanup();
    println!("✅ All nodes stopped");

    Ok(())
}

/// Cleanup dev mode - stop all processes in dependency order
pub fn cleanup(ctx: &mut CliContext) -> Result<()> {
    use std::time::Duration;
    use tracing::warn;

    println!();
    println!("Stopping all nodes in dependency order...");

    // Use shared process service for consistency with spawn
    let shared_process = ctx.shared_process();
    let mut process_service = shared_process.lock().unwrap();
    let shutdown_order = process_service.get_shutdown_order();

    if shutdown_order.is_empty() {
        println!("✅ No nodes to stop");
        return Ok(());
    }

    // Stop nodes in dependency order
    for node in shutdown_order {
        println!("  Stopping {}...", node);

        // Try graceful shutdown with 10 second timeout
        if let Err(e) = process_service.stop_with_timeout(&node, Duration::from_secs(10)) {
            warn!("Failed to gracefully stop {}: {}", node, e);
            // Try force kill as last resort
            if let Err(kill_err) = process_service.force_kill(&node) {
                warn!("Failed to force kill {}: {}", node, kill_err);
            } else {
                println!("  ✅ {} stopped (force killed)", node);
            }
        } else {
            println!("  ✅ {} stopped", node);
        }
    }

    println!("✅ All nodes stopped");

    Ok(())
}

/// Handle lifecycle mode change
///
/// Calculates node diff, stops old nodes, starts new nodes, updates lifecycle state
#[allow(clippy::too_many_arguments)]
async fn handle_mode_change(
    ctx: &mut CliContext,
    lifecycle: &mut crate::dev::lifecycle_adapter::CliLifecycleManager,
    target_mode: &str,
    pids: &mut HashMap<String, u32>,
    project_name: &str,
    project_config: &ProjectConfig,
    nodes_to_run: &[NodeToRun],
    log_buffer: Option<&LogBuffer>,
) -> Result<()> {
    println!();
    println!("⚡ Switching to {} mode...", target_mode);

    // Log mode change
    if let Some(buffer) = log_buffer {
        buffer.push(format!("MODE: Switching to {} mode", target_mode));
    }

    // Calculate what needs to change
    let diff = lifecycle.change_mode(target_mode)?;

    // Stop nodes that need to stop
    if !diff.stop.is_empty() {
        println!("🛑 Stopping: {}", diff.stop.join(", "));
        // Use shared process service (same one used by spawn_nodes_by_name)
        let shared_process = ctx.shared_process();
        let mut process_service = shared_process.lock().unwrap();
        for node_name in &diff.stop {
            if pids.remove(node_name).is_some() {
                if let Err(e) = process_service.stop(node_name) {
                    eprintln!("  Warning: Failed to stop {}: {}", node_name, e);
                    if let Some(buffer) = log_buffer {
                        buffer.push(format!("ERROR: Failed to stop {}: {}", node_name, e));
                    }
                } else if let Some(buffer) = log_buffer {
                    buffer.push(format!("🛑 STOPPED: {}", node_name));
                }
            }
        }
        lifecycle.mark_nodes_stopped(&diff.stop);
    }

    // Start nodes that need to start
    if !diff.start.is_empty() {
        println!("✅ Starting: {}", diff.start.join(", "));
        let new_pids = spawn_nodes_by_name(ctx, &diff.start, project_name, project_config, log_buffer)?;
        // Mark only successfully spawned nodes as running
        let actually_started: Vec<String> = new_pids.keys().cloned().collect();
        pids.extend(new_pids);
        lifecycle.mark_nodes_running(&actually_started);
    }

    println!("✓ Mode changed to: {}", target_mode);
    println!();

    // Log mode change completion
    if let Some(buffer) = log_buffer {
        buffer.push(format!("MODE: Successfully changed to {} mode", target_mode));
    }

    // Display current mode status
    let running: Vec<String> = pids.keys().cloned().collect();
    crate::ui::dev_banners::print_mode_status(target_mode, &running, nodes_to_run);

    Ok(())
}

/// Configuration for interactive mode
pub struct InteractiveModeConfig<'a> {
    pub project_config: &'a ProjectConfig,
    pub nodes_to_run: &'a [NodeToRun],
    pub godot_path: &'a Option<String>,
    pub sim_paths: &'a Option<SimulationPaths>,
    pub project_name: &'a str,
}

/// Mutable state for interactive mode
pub struct InteractiveModeState {
    pub pids: HashMap<String, u32>,
    pub lifecycle: Option<crate::dev::lifecycle_adapter::CliLifecycleManager>,
    pub log_buffer: LogBuffer,
}

/// Run interactive mode with keyboard controls
pub async fn run_interactive_mode(
    ctx: &mut CliContext,
    config: InteractiveModeConfig<'_>,
    state: &mut InteractiveModeState,
) -> Result<()> {
    use crate::ui::DevModeTui;
    use crossterm::event::{self, Event, KeyCode, KeyModifiers};
    use crossterm::execute;
    use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
    use ratatui::{backend::CrosstermBackend, Terminal};
    use std::io::stdout;
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;
    use std::time::Duration;

    // Set up signal handler
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();

    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
    })?;

    let redis_url = ctx.redis_url().to_string();

    // Setup terminal for TUI
    enable_raw_mode()?;
    let mut stdout_handle = stdout();
    execute!(stdout_handle, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout_handle);
    let mut terminal = Terminal::new(backend)?;

    // Clear the terminal to ensure proper initial rendering
    terminal.clear()?;

    // Create dev mode TUI
    let current_mode = state
        .lifecycle
        .as_ref()
        .map(|l| l.current_mode().to_string())
        .unwrap_or_else(|| "startup".to_string());
    let running_nodes: Vec<String> = state.pids.keys().cloned().collect();
    let dashboard_url = config.project_config.environments.dashboard_url();
    let mut dev_tui = DevModeTui::new(state.log_buffer.clone(), current_mode, running_nodes, dashboard_url);

    // Main event loop with TUI
    let result = loop {
        // Check interrupt flag
        if !running.load(Ordering::SeqCst) {
            break Ok(());
        }

        // Draw TUI
        if let Err(e) = terminal.draw(|f| dev_tui.draw(f)) {
            break Err(e.into());
        }

        // Poll for events
        if event::poll(Duration::from_millis(100))? {
            if let Event::Key(key_event) = event::read()? {
                // Check for quit first
                if matches!(key_event.code, KeyCode::Char('c') | KeyCode::Char('x'))
                    && key_event.modifiers.contains(KeyModifiers::CONTROL)
                {
                    running.store(false, Ordering::SeqCst);
                    break Ok(());
                }

                // Handle key and get the character
                if let Some(c) = dev_tui.handle_key(key_event) {
                    match c {
                        's' => {
                            // Exit TUI temporarily
                            disable_raw_mode()?;
                            execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
                            terminal.show_cursor()?;

                            // Clear screen and show header for simulation mode
                            print!("\x1B[2J\x1B[1;1H"); // Clear screen and move cursor to top
                            std::io::stdout().flush()?;
                            println!("╔════════════════════════════════════════════════════════════╗");
                            println!("║              SWITCHING TO SIMULATION MODE                  ║");
                            println!("╚════════════════════════════════════════════════════════════╝");
                            println!();

                            // Switch to simulation mode and start Godot
                            if let Some(ref mut lifecycle_mgr) = state.lifecycle {
                                // Try to switch to simulation mode
                                if lifecycle_mgr.available_modes().contains(&"simulation") {
                                    handle_mode_change(
                                        ctx,
                                        lifecycle_mgr,
                                        "simulation",
                                        &mut state.pids,
                                        config.project_name,
                                        config.project_config,
                                        config.nodes_to_run,
                                        Some(&state.log_buffer),
                                    )
                                    .await?;

                                    // Start Godot simulator for simulation mode
                                    let launch_config = SimulatorLaunchConfig {
                                        godot_path: config.godot_path,
                                        sim_paths: config.sim_paths,
                                        project_name: config.project_name,
                                        redis_url: &redis_url,
                                    };
                                    start_simulator(
                                        ctx,
                                        config.project_config,
                                        config.nodes_to_run,
                                        &mut state.pids,
                                        &launch_config,
                                        &running,
                                        &state.log_buffer,
                                    )
                                    .await?;
                                } else {
                                    println!("⚠️  No 'simulation' mode configured in lifecycle");
                                    state
                                        .log_buffer
                                        .push("MODE: No 'simulation' mode in lifecycle config".to_string());
                                }
                            } else {
                                println!("❌ Lifecycle configuration is required. Please add a 'lifecycle' section to mecha10.json");
                                state.log_buffer.push("MODE: No lifecycle config found".to_string());
                            }

                            // Re-enter TUI
                            enable_raw_mode()?;
                            execute!(terminal.backend_mut(), EnterAlternateScreen)?;
                            terminal.clear()?;

                            // Update TUI with new status
                            let current_mode = state
                                .lifecycle
                                .as_ref()
                                .map(|l| l.current_mode().to_string())
                                .unwrap_or_else(|| "startup".to_string());
                            let running_nodes: Vec<String> = state.pids.keys().cloned().collect();
                            dev_tui.update_status(current_mode, running_nodes);
                        }
                        't' => {
                            // Exit TUI temporarily
                            disable_raw_mode()?;
                            execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
                            terminal.show_cursor()?;

                            // Clear screen and show header for teleop mode
                            print!("\x1B[2J\x1B[1;1H"); // Clear screen and move cursor to top
                            std::io::stdout().flush()?;
                            println!("╔════════════════════════════════════════════════════════════╗");
                            println!("║                 SWITCHING TO TELEOP MODE                   ║");
                            println!("╚════════════════════════════════════════════════════════════╝");
                            println!();

                            // Switch to teleop mode
                            if let Some(ref mut lifecycle_mgr) = state.lifecycle {
                                // Try to switch to teleop mode
                                if lifecycle_mgr.available_modes().contains(&"teleop") {
                                    handle_mode_change(
                                        ctx,
                                        lifecycle_mgr,
                                        "teleop",
                                        &mut state.pids,
                                        config.project_name,
                                        config.project_config,
                                        config.nodes_to_run,
                                        Some(&state.log_buffer),
                                    )
                                    .await?;

                                    // After switching mode, enter teleop UI
                                    enter_teleop_mode(
                                        config.project_config,
                                        &state.pids,
                                        &redis_url,
                                        &running,
                                        &state.log_buffer,
                                    )
                                    .await?;
                                } else {
                                    // Fallback to direct teleop UI if mode not configured
                                    enter_teleop_mode(
                                        config.project_config,
                                        &state.pids,
                                        &redis_url,
                                        &running,
                                        &state.log_buffer,
                                    )
                                    .await?;
                                }
                            } else {
                                println!("❌ Lifecycle configuration is required. Please add a 'lifecycle' section to mecha10.json");
                            }

                            // Re-enter TUI
                            enable_raw_mode()?;
                            execute!(terminal.backend_mut(), EnterAlternateScreen)?;
                            terminal.clear()?;

                            // Update TUI with new status
                            let current_mode = state
                                .lifecycle
                                .as_ref()
                                .map(|l| l.current_mode().to_string())
                                .unwrap_or_else(|| "startup".to_string());
                            let running_nodes: Vec<String> = state.pids.keys().cloned().collect();
                            dev_tui.update_status(current_mode, running_nodes);
                        }
                        'x' => {
                            // Stop simulation mode - only valid when in simulation mode
                            let current_mode = state
                                .lifecycle
                                .as_ref()
                                .map(|l| l.current_mode().to_lowercase())
                                .unwrap_or_default();

                            if current_mode != "simulation" {
                                // Not in simulation mode, ignore
                                continue;
                            }

                            // Exit TUI temporarily
                            disable_raw_mode()?;
                            execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
                            terminal.show_cursor()?;

                            // Clear screen and show header
                            print!("\x1B[2J\x1B[1;1H");
                            std::io::stdout().flush()?;
                            println!("╔════════════════════════════════════════════════════════════╗");
                            println!("║               STOPPING SIMULATION MODE                     ║");
                            println!("╚════════════════════════════════════════════════════════════╝");
                            println!();

                            state.log_buffer.push("MODE: Stopping simulation mode...".to_string());

                            // Stop Godot simulator (native or Docker)
                            println!("🛑 Stopping simulator...");
                            if let Some(ref sim_paths) = config.sim_paths {
                                if sim_paths.headless {
                                    // Stop Docker container
                                    let project_root = ctx.project().map(|p| p.root().to_path_buf()).ok();
                                    if let Some(root) = project_root {
                                        let compose_path = root.join(paths::docker::COMPOSE_FILE);
                                        if compose_path.exists() {
                                            let docker_service = crate::services::DockerService::with_compose_file(
                                                compose_path.to_string_lossy().to_string(),
                                            );
                                            if let Err(e) = docker_service.compose_stop(Some("simulation")).await {
                                                eprintln!("  Warning: Failed to stop simulation container: {}", e);
                                            } else {
                                                println!("  ✅ Simulation container stopped");
                                                state.log_buffer.push("🛑 STOPPED: Simulation container".to_string());
                                            }
                                        }
                                    }
                                } else {
                                    // Stop native Godot process (tracked via ctx.process().manager())
                                    let process_service = ctx.process();
                                    if let Err(e) = process_service.stop("godot-simulator") {
                                        eprintln!("  Warning: Failed to stop Godot: {}", e);
                                    } else {
                                        println!("  ✅ Godot simulator stopped");
                                        state.log_buffer.push("🛑 STOPPED: Godot simulator".to_string());
                                    }
                                }
                            } else {
                                // Try to stop native Godot anyway (might have been started)
                                let process_service = ctx.process();
                                let _ = process_service.stop("godot-simulator");
                            }

                            // Switch back to default mode
                            if let Some(ref mut lifecycle_mgr) = state.lifecycle {
                                // Get default mode from project config
                                let default_mode = config
                                    .project_config
                                    .lifecycle
                                    .as_ref()
                                    .map(|l| l.default_mode.clone())
                                    .unwrap_or_else(|| "dev".to_string());

                                if lifecycle_mgr.available_modes().contains(&default_mode.as_str()) {
                                    handle_mode_change(
                                        ctx,
                                        lifecycle_mgr,
                                        &default_mode,
                                        &mut state.pids,
                                        config.project_name,
                                        config.project_config,
                                        config.nodes_to_run,
                                        Some(&state.log_buffer),
                                    )
                                    .await?;
                                }
                            }

                            // Re-enter TUI
                            enable_raw_mode()?;
                            execute!(terminal.backend_mut(), EnterAlternateScreen)?;
                            terminal.clear()?;

                            // Update TUI with new status
                            let current_mode = state
                                .lifecycle
                                .as_ref()
                                .map(|l| l.current_mode().to_string())
                                .unwrap_or_else(|| "startup".to_string());
                            let running_nodes: Vec<String> = state.pids.keys().cloned().collect();
                            dev_tui.update_status(current_mode, running_nodes);
                        }
                        _ => {}
                    }
                }
            }
        }
    };

    // Cleanup terminal
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
    terminal.show_cursor()?;

    // Cleanup processes
    cleanup(ctx)?;

    result
}

/// Start the Godot simulator with the configured model and environment
///
/// # Arguments
///
/// * `ctx` - CLI context
/// * `config` - Project configuration
/// * `nodes_to_run` - List of nodes available to run
/// * `pids` - Map of running process PIDs
/// * `launch_config` - Simulator launch configuration (godot path, sim paths, project name, redis url)
/// * `running` - Shared running flag for clean shutdown
/// * `log_buffer` - Log buffer for TUI logs
pub async fn start_simulator(
    ctx: &mut CliContext,
    config: &ProjectConfig,
    _nodes_to_run: &[NodeToRun],
    pids: &mut HashMap<String, u32>,
    launch_config: &SimulatorLaunchConfig<'_>,
    running: &Arc<AtomicBool>,
    log_buffer: &LogBuffer,
) -> Result<()> {
    // Check if simulation already running
    {
        let dev_service = ctx.dev();
        if dev_service.is_port_in_use(11008)? {
            println!();
            println!("⚠️  Simulation already running on port 11008");
            log_buffer.push("SIMULATOR: Already running on port 11008".to_string());
            println!();
            println!("Only one simulation instance can run at a time.");
            println!("Stop the running simulation before starting a new one.");
            println!();
            return Ok(());
        }
    }

    // Launch simulator
    println!();
    println!("🎮 Launching simulator...");
    log_buffer.push("SIMULATOR: Launching Godot...".to_string());
    println!();

    if let Some(ref godot_exe) = launch_config.godot_path {
        if let Some(ref sim_paths) = launch_config.sim_paths {
            println!("   Model: {}", sim_paths.model_path.display());
            println!("   Environment: {}", sim_paths.environment_path.display());

            // Check if we should use Docker (headless mode) or native Godot
            if sim_paths.headless {
                // Headless mode: Launch via Docker with Xvfb
                println!("   Mode: Headless (Docker + Xvfb)");
                println!();

                // Get project root path (need to do this first to avoid borrow issues)
                let project_root = ctx.project()?.root().to_path_buf();

                // Get dev service and build Docker-specific paths
                let dev_service = ctx.dev();
                let compose_path = project_root.join(paths::docker::COMPOSE_FILE);

                if !compose_path.exists() {
                    eprintln!("❌ {} not found at {}", paths::docker::COMPOSE_FILE, compose_path.display());
                    eprintln!("   Headless mode requires Docker Compose configuration.");
                    return Ok(());
                }

                let docker_service = DockerService::with_compose_file(compose_path.to_string_lossy().to_string());

                // Check if Docker is available
                if let Err(e) = docker_service.check_installation() {
                    eprintln!("❌ Docker not available: {}", e);
                    eprintln!("   Install Docker or set headless: false in simulation config.");
                    return Ok(());
                }

                println!("🐳 Starting simulation container...");

                // Resolve simulation assets path for Docker mount
                // Priority: MECHA10_FRAMEWORK_PATH > cached assets from GitHub
                let simulation_mount_path = if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
                    // Monorepo dev mode - use framework path
                    std::path::PathBuf::from(framework_path)
                } else {
                    // Production mode - ensure assets are downloaded and use cached path
                    let assets_service = crate::services::SimulationAssetsService::new();
                    match assets_service.ensure_assets().await {
                        Ok(assets_path) => assets_path,
                        Err(e) => {
                            eprintln!("❌ Failed to get simulation assets: {}", e);
                            eprintln!();
                            eprintln!("Options:");
                            eprintln!("  1. Set MECHA10_FRAMEWORK_PATH for local development");
                            eprintln!("  2. Check your internet connection for asset download");
                            return Ok(());
                        }
                    }
                };

                // Set environment variable for docker-compose
                std::env::set_var("MECHA10_SIMULATION_PATH", &simulation_mount_path);
                println!("   Simulation assets: {}", simulation_mount_path.display());

                // Build Godot args with CONTAINER paths (not host paths)
                // Container mounts:
                //   - Simulation assets: /mecha10 (either framework or standalone assets)
                //   - Project configs: /project/configs
                //   - Project simulation: /project/simulation
                //
                // Path differs based on mode:
                //   - Framework dev: /mecha10/packages/simulation/godot-project (full monorepo)
                //   - Standalone: /mecha10/godot-project (extracted assets tarball)
                let container_godot_path = if std::env::var("MECHA10_FRAMEWORK_PATH").is_ok() {
                    std::path::PathBuf::from(paths::container::FRAMEWORK_GODOT_PROJECT)
                } else {
                    std::path::PathBuf::from(paths::container::FRAMEWORK_GODOT_PROJECT_LEGACY)
                };

                // Translate config paths from host to container
                let container_model_config = sim_paths.model_config_path.as_ref().map(|p| {
                    // Config paths are in project root, mounted at /project/configs
                    let relative = p.strip_prefix(&project_root).unwrap_or(p);
                    std::path::PathBuf::from(paths::container::PROJECT_ROOT).join(relative)
                });

                let container_env_config = sim_paths.environment_config_path.as_ref().map(|p| {
                    let relative = p.strip_prefix(&project_root).unwrap_or(p);
                    std::path::PathBuf::from(paths::container::PROJECT_ROOT).join(relative)
                });

                let args = dev_service.build_godot_args(
                    &container_godot_path,
                    &sim_paths.model_path,
                    &sim_paths.environment_path,
                    container_model_config.as_ref(),
                    container_env_config.as_ref(),
                    false, // Don't add --headless flag; Docker uses Xvfb instead
                    Some(&sim_paths.networking),
                );

                // Launch simulation via docker compose run
                match docker_service
                    .compose_run_service("simulation", true, true, &args)
                    .await
                {
                    Ok(_) => {
                        println!("✅ Simulation container started");
                        println!("   View logs: docker compose logs -f simulation");
                        println!();

                        // Check for teleop and auto-enter if available
                        check_and_enter_teleop(config, pids, launch_config.redis_url, running, log_buffer).await?;
                    }
                    Err(e) => {
                        eprintln!("❌ Failed to start simulation container: {}", e);
                        eprintln!("   Check: docker compose logs simulation");
                    }
                }
            } else {
                // Non-headless mode: Launch Godot directly
                println!("   Mode: Native Godot");
                println!();

                let dev_service = ctx.dev();
                let godot_project_path = dev_service.get_godot_project_path();

                // Validate Godot project path exists, try to download if not found
                let godot_project_path = if !godot_project_path.exists() {
                    // Try to download simulation assets from GitHub releases
                    println!("📦 Simulation assets not found locally, attempting download...");
                    let assets_service = crate::services::SimulationAssetsService::new();
                    match assets_service.ensure_assets().await {
                        Ok(assets_path) => {
                            let downloaded_godot_path = assets_path.join("godot-project");
                            if downloaded_godot_path.exists() {
                                println!("✅ Using downloaded simulation assets");
                                downloaded_godot_path
                            } else {
                                eprintln!("❌ Downloaded assets don't contain godot-project");
                                eprintln!();
                                eprintln!("Options:");
                                eprintln!("  1. Set MECHA10_FRAMEWORK_PATH environment variable:");
                                eprintln!("     export MECHA10_FRAMEWORK_PATH=/path/to/mecha10");
                                eprintln!();
                                eprintln!("  2. Use Docker-based headless simulation:");
                                eprintln!("     Set 'headless: true' in configs/dev/simulation/config.json");
                                eprintln!();
                                return Ok(());
                            }
                        }
                        Err(e) => {
                            eprintln!("❌ Failed to download simulation assets: {}", e);
                            eprintln!();
                            eprintln!("The CLI was unable to locate or download the Godot simulation project.");
                            eprintln!();
                            eprintln!("Options:");
                            eprintln!("  1. Set MECHA10_FRAMEWORK_PATH environment variable:");
                            eprintln!("     export MECHA10_FRAMEWORK_PATH=/path/to/mecha10");
                            eprintln!();
                            eprintln!("  2. Use Docker-based headless simulation:");
                            eprintln!("     Set 'headless: true' in configs/dev/simulation/config.json");
                            eprintln!();
                            return Ok(());
                        }
                    }
                } else {
                    godot_project_path
                };

                // Build Godot args for native execution (use host paths)
                let args = dev_service.build_godot_args(
                    &godot_project_path,
                    &sim_paths.model_path,
                    &sim_paths.environment_path,
                    sim_paths.model_config_path.as_ref(),
                    sim_paths.environment_config_path.as_ref(),
                    false, // Native mode doesn't use headless flag
                    Some(&sim_paths.networking),
                );

                let process_service = ctx.process();

                use std::process::{Command, Stdio};

                // Build command with environment variables
                let mut cmd = Command::new(godot_exe);
                cmd.args(&args)
                    .stdin(Stdio::null())
                    .stdout(Stdio::piped())
                    .stderr(Stdio::piped());

                // Pass MECHA10_FRAMEWORK_PATH if we're in framework dev mode
                if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
                    cmd.env("MECHA10_FRAMEWORK_PATH", framework_path);
                }

                match cmd.spawn() {
                    Ok(child) => {
                        let pid = child.id();
                        println!("✅ Simulator launched (PID: {})", pid);
                        log_buffer.push(format!("SIMULATOR: ✅ Godot launched (PID: {})", pid));
                        println!();

                        // Track the process
                        process_service.manager().track("godot-simulator".to_string(), child);

                        // Note: Simulation nodes are spawned via lifecycle mode change before this function is called
                        // No need to spawn them here anymore - the new lifecycle system handles it

                        // Check for teleop and auto-enter if available
                        check_and_enter_teleop(config, pids, launch_config.redis_url, running, log_buffer).await?;
                    }
                    Err(e) => {
                        eprintln!("❌ Failed to launch simulator: {}", e);
                        log_buffer.push(format!("SIMULATOR: ❌ Failed to launch: {}", e));
                    }
                }
            }
        } else {
            print_no_simulation_config();
            log_buffer.push("SIMULATOR: ❌ No simulation configuration in mecha10.json".to_string());
        }
    } else {
        print_godot_not_found();
        log_buffer.push("SIMULATOR: ❌ Godot not found - install Godot 4.x".to_string());
    }

    println!();
    Ok(())
}

/// Enter teleop mode
///
/// # Arguments
///
/// * `config` - Project configuration
/// * `pids` - Map of running process PIDs
/// * `redis_url` - Redis connection URL
/// * `running` - Shared running flag for clean shutdown
/// * `log_buffer` - Log buffer for TUI logs
pub async fn enter_teleop_mode(
    config: &ProjectConfig,
    pids: &HashMap<String, u32>,
    redis_url: &str,
    running: &Arc<AtomicBool>,
    log_buffer: &LogBuffer,
) -> Result<()> {
    let has_teleop = config.nodes.contains("teleop");
    let teleop_running = pids.contains_key("teleop");

    if !has_teleop {
        println!("\n⚠️  Teleop node not found in project");
        println!();
        println!("To add teleop to your project:");
        println!("  mecha10 add teleop");
        println!();
        println!("The teleop coordination node manages multiple");
        println!("control sources with priority arbitration.");
        println!();
    } else if !teleop_running {
        println!("\n⚠️  Teleop node not running");
        println!();
        println!("Start the teleop node by adding it to dev nodes:");
        println!("  mecha10 dev teleop");
        println!();
    } else {
        println!("\n🎮 Entering teleop control mode...\n");

        // Create teleop TUI with dashboard URL from project config
        let dashboard_url = config.environments.dashboard_url();
        let mut teleop_ui = TeleopUi::new(redis_url.to_string(), log_buffer.clone(), dashboard_url);

        match teleop_ui.run_with_interrupt(running.clone()).await {
            Ok(()) => {
                println!("\n✅ Exited teleop mode\n");
            }
            Err(e) => {
                eprintln!("\n❌ Teleop UI error: {}\n", e);
            }
        }
    }

    Ok(())
}

// ========== Private Helper Functions ==========

async fn check_and_enter_teleop(
    config: &ProjectConfig,
    pids: &HashMap<String, u32>,
    redis_url: &str,
    running: &Arc<AtomicBool>,
    log_buffer: &LogBuffer,
) -> Result<()> {
    let has_teleop = config.nodes.contains("teleop");
    let teleop_running = pids.contains_key("teleop");

    if has_teleop && teleop_running {
        println!("🎮 Entering teleop control mode...");
        println!();

        // Create teleop TUI with dashboard URL from project config
        let dashboard_url = config.environments.dashboard_url();
        let mut teleop_ui = TeleopUi::new(redis_url.to_string(), log_buffer.clone(), dashboard_url);

        match teleop_ui.run_with_interrupt(running.clone()).await {
            Ok(()) => {
                println!();
                println!("✅ Exited teleop mode");
                println!();
            }
            Err(e) => {
                eprintln!();
                eprintln!("❌ Teleop UI error: {}", e);
                println!();
            }
        }
    } else if !has_teleop {
        println!("💡 Tip: Add teleop node to control the robot");
        println!("   mecha10 add teleop");
        println!();
    } else if !teleop_running {
        println!("💡 Tip: Start teleop node to control the robot");
        println!("   mecha10 dev teleop");
        println!();
    }

    Ok(())
}

fn print_no_simulation_config() {
    println!("⚠️  No simulation configuration found in mecha10.json");
    println!();
    println!("Add simulation config to mecha10.json:");
    println!(r#"  "simulation": {{"#);
    println!(r#"    "model": "@mecha10/simulation-models/rover","#);
    println!(r#"    "environment": "@mecha10/simulation-environments/basic_arena""#);
    println!(r#"  }}"#);
}

fn print_godot_not_found() {
    println!("⚠️  Godot not found");
    println!();
    println!("Make sure Godot is installed and in PATH.");
    println!("Or install it with: mecha10 setup");
}
