//! Init command handler
//!
//! Orchestrates project initialization using InitService and ProjectTemplateService.
//!
//! Templates are downloaded on-demand from GitHub releases and cached locally.
//! This ensures users always get the latest templates without rebuilding the CLI.

use crate::commands::InitArgs;
use crate::context::CliContext;
use crate::init::validation;
use crate::paths;
use crate::services::CredentialsService;
use crate::types::EnvironmentsConfig;
use crate::ui::init_wizard::run_wizard_tui;
use anyhow::Result;
use std::path::{Path, PathBuf};

/// Result type for init processing: (project_name, project_path, template, nodes, dev)
type InitProcessResult = (String, PathBuf, Option<String>, Vec<String>, bool);

/// Cleanup partially created project directory on error
async fn cleanup_on_error(project_path: &Path) {
    if project_path.exists() {
        if let Err(e) = tokio::fs::remove_dir_all(project_path).await {
            eprintln!("⚠️  Failed to cleanup directory: {}", e);
        } else {
            println!("🧹 Cleaned up incomplete project directory");
        }
    }
}

/// Download latest templates from GitHub releases
///
/// Returns the template version on success.
async fn download_templates(ctx: &mut CliContext) -> Result<String> {
    let template_download = ctx.template_download();

    // Get the latest template version from GitHub
    let version = template_download.get_latest_version().await?;

    // Ensure templates are downloaded and cached
    template_download.ensure_templates(&version).await?;

    Ok(version)
}

/// Copy templates from downloaded GitHub release
///
/// Copies config files, behaviors, and assets from the downloaded template cache.
async fn copy_downloaded_templates(ctx: &mut CliContext, project_path: &Path, version: &str) -> Result<()> {
    let template_download = ctx.template_download();

    // Copy node configs (config/nodes/* -> configs/nodes/@mecha10/*)
    template_download.copy_template_dir(version, "config/nodes", &project_path.join(paths::config::NODES_MECHA10_DIR))?;

    // Copy simulation config
    template_download.copy_template_file(
        version,
        "config/simulation/config.json",
        &project_path.join(paths::config::SIMULATION_CONFIG),
    )?;

    // Copy behaviors
    template_download.copy_template_dir(version, "behaviors", &project_path.join(paths::project::BEHAVIORS_DIR))?;

    // Copy assets
    template_download.copy_template_dir(version, "assets", &project_path.join(paths::project::ASSETS_DIR))?;

    Ok(())
}

/// Fallback: copy templates from framework path or embedded templates
///
/// Used when template download fails (e.g., no network, rate limited).
async fn copy_fallback_templates(ctx: &mut CliContext, project_path: &Path) -> Result<()> {
    // Copy simulation configs from framework (skips in standalone mode)
    ctx.init_service().copy_simulation_configs(project_path).await?;

    // Create simulation configs from embedded templates (standalone mode fallback)
    ctx.project_template_service()
        .create_simulation_configs(project_path)
        .await?;

    // Copy simulation assets (images) from framework (skips in standalone mode)
    ctx.init_service().copy_simulation_assets(project_path).await?;

    // Create simulation assets from embedded templates (standalone mode fallback)
    ctx.project_template_service()
        .create_simulation_assets(project_path)
        .await?;

    // Copy behavior tree templates from framework (skips in standalone mode)
    ctx.init_service().copy_behavior_templates(project_path).await?;

    // Create behavior templates from embedded files (standalone mode fallback)
    ctx.project_template_service()
        .create_behavior_templates(project_path)
        .await?;

    // Copy node configuration files from framework packages
    ctx.init_service().copy_all_node_configs(project_path).await?;

    // Create node configs from embedded templates (standalone mode fallback)
    ctx.project_template_service().create_node_configs(project_path).await?;

    Ok(())
}

/// Create mecha10.json from template file
///
/// Loads the mecha10.json.template from downloaded templates and replaces placeholders.
/// This ensures the generated config always matches the template (single source of truth).
async fn create_mecha10_json_from_template(
    ctx: &mut CliContext,
    project_path: &Path,
    project_name: &str,
    template: &Option<String>,
    template_version: Option<&str>,
) -> Result<()> {
    let platform = template.as_deref().unwrap_or("basic");
    let project_id = project_name.replace('-', "_");

    // Try to load from downloaded templates first
    let template_content = if let Some(version) = template_version {
        ctx.template_download()
            .read_template(version, "config/mecha10.json.template")
            .ok()
    } else {
        None
    };

    // Replace placeholders in template
    let config_content = if let Some(content) = template_content {
        let mut config_str = content
            .replace("{{project_name}}", project_name)
            .replace("{{project_id}}", &project_id)
            .replace("{{platform}}", platform);

        // Add template_version if available
        if let Some(version) = template_version {
            // Parse, add version, and re-serialize to ensure valid JSON
            let mut config: serde_json::Value = serde_json::from_str(&config_str)?;
            config["template_version"] = serde_json::json!(version);
            config_str = serde_json::to_string_pretty(&config)?;
        }

        config_str
    } else {
        // Fallback to embedded template (for offline/framework dev mode)
        const EMBEDDED_TEMPLATE: &str = include_str!("../../templates/config/mecha10.json.template");

        let mut config_str = EMBEDDED_TEMPLATE
            .to_string()
            .replace("{{project_name}}", project_name)
            .replace("{{project_id}}", &project_id)
            .replace("{{platform}}", platform);

        // Add template_version if available
        if let Some(version) = template_version {
            let mut config: serde_json::Value = serde_json::from_str(&config_str)?;
            config["template_version"] = serde_json::json!(version);
            config_str = serde_json::to_string_pretty(&config)?;
        }

        config_str
    };

    tokio::fs::write(project_path.join(paths::PROJECT_CONFIG), config_content).await?;

    Ok(())
}

/// Process init command arguments in non-interactive mode
///
/// Validates and processes command-line arguments for project initialization.
/// This is used when:
/// - Running in a non-TTY environment
/// - User specifies --non-interactive flag
/// - Interactive wizard fails
///
/// # Arguments
///
/// * `args` - Init command arguments
/// * `working_dir` - Working directory for resolving relative paths
/// * `catalog` - Component catalog service for loading nodes
///
/// # Returns
///
/// A tuple of (project_name, project_path, template, nodes, dev_mode)
fn process_non_interactive_args(
    args: &InitArgs,
    working_dir: &Path,
    catalog: &crate::services::ComponentCatalogService,
) -> Result<InitProcessResult> {
    use crate::init::get_template_defaults;

    // Require project name in non-interactive mode
    let name = args.name.clone().ok_or_else(|| {
        anyhow::anyhow!(
            "Project name required in non-interactive mode.\n\
             Usage: mecha10 init <PROJECT_NAME> [OPTIONS]\n\
             Example: mecha10 init my-robot"
        )
    })?;

    // Check if it's a path or just a name
    let path_buf = PathBuf::from(&name);
    let (project_name, project_path) = if path_buf.is_absolute() || name.contains('/') || name.contains('\\') {
        // It's a path - extract the final component as the name
        let final_name = path_buf
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or(&name)
            .to_string();
        (final_name, path_buf)
    } else {
        // It's just a name - create path relative to working directory
        let final_path = working_dir.join(&name);
        (name, final_path)
    };

    let template = args.template.clone();

    // Apply template defaults for nodes unless --no-examples is specified
    let nodes = if args.no_examples {
        Vec::new()
    } else {
        let mut node_components = catalog.get_by_category("nodes");
        node_components.sort_by(|a, b| a.name.cmp(&b.name));
        let node_ids: Vec<String> = node_components.iter().map(|c| c.id.clone()).collect();

        let defaults = get_template_defaults(&template, &node_ids);

        node_ids
            .iter()
            .enumerate()
            .filter(|(i, _)| defaults[*i])
            .map(|(_, id)| id.clone())
            .collect()
    };

    let dev = args.dev;

    Ok((project_name, project_path, template, nodes, dev))
}

/// Handle init command
///
/// Creates a new Mecha10 project with interactive wizard or command-line arguments.
///
/// # Arguments
///
/// * `ctx` - CLI execution context
/// * `args` - Init command arguments
pub async fn handle_init(ctx: &mut CliContext, args: &InitArgs) -> Result<()> {
    // Clear terminal before starting to avoid menu display issues
    print!("\x1B[2J\x1B[1;1H");

    // Check if user is logged in
    let credentials_service = CredentialsService::new();
    if !credentials_service.is_logged_in() {
        println!();
        println!("You must be logged in to create a project.");
        println!();
        println!("Run `mecha10 auth login` to authenticate.");
        println!();
        return Ok(());
    }

    println!();
    println!("🤖 Initialize Mecha10 Project");
    println!();

    // Capture working_dir before borrowing catalog
    let working_dir = ctx.working_dir.clone();

    // Load component catalog for example selection
    let catalog = ctx.component_catalog();

    // Determine if we should use interactive wizard
    let is_tty = atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout);
    let use_wizard = is_tty && !args.non_interactive;

    let (project_name, project_path, template, nodes, dev) = if use_wizard {
        // Try interactive wizard with TUI
        let default_name = args.name.clone();
        match run_wizard_tui(default_name.as_deref(), catalog) {
            Ok((name, template, selected_nodes)) => {
                // Check if it's a path or just a name
                let path_buf = std::path::PathBuf::from(&name);
                let (final_name, path) = if path_buf.is_absolute() || name.contains('/') || name.contains('\\') {
                    // It's a path - extract the final component as the name
                    let final_name = path_buf
                        .file_name()
                        .and_then(|n| n.to_str())
                        .unwrap_or(&name)
                        .to_string();
                    (final_name, path_buf)
                } else {
                    // It's just a name - create path relative to working directory
                    let final_path = ctx.working_dir.join(&name);
                    (name, final_path)
                };

                // Use --dev flag from args even in interactive mode
                (final_name, path, template, selected_nodes, args.dev)
            }
            Err(e) => {
                println!("⚠️  Wizard failed: {}", e);
                println!("Using command-line arguments...\n");

                // Fall back to non-interactive mode
                process_non_interactive_args(args, &working_dir, catalog)?
            }
        }
    } else {
        if !is_tty {
            println!("ℹ️  Non-interactive mode\n");
        }

        // Non-interactive mode - use provided arguments
        process_non_interactive_args(args, &working_dir, catalog)?
    };

    // Check if directory already exists BEFORE any processing
    validation::check_directory_not_exists(&project_path)?;

    // Download latest templates from GitHub
    let template_version = match download_templates(ctx).await {
        Ok(version) => {
            println!("📦 Using templates v{}", version);
            Some(version)
        }
        Err(e) => {
            println!("⚠️  Could not download templates: {}", e);
            println!("   Using embedded fallback templates");
            None
        }
    };

    // Wrap the entire project creation in cleanup logic
    let result = perform_init(
        ctx,
        &project_path,
        &project_name,
        &template,
        &nodes,
        dev,
        template_version.as_deref(),
    )
    .await;

    // If init failed, cleanup and propagate error
    if let Err(e) = result {
        cleanup_on_error(&project_path).await;
        return Err(e);
    }

    // Success message
    print_success_message(&project_name);

    Ok(())
}

/// Perform the actual project initialization
///
/// # Arguments
///
/// * `ctx` - CLI context
/// * `project_path` - Path to create the project
/// * `project_name` - Name of the project
/// * `template` - Robot template (rover, humanoid, etc.)
/// * `nodes` - List of nodes to add
/// * `dev` - Whether to use framework development mode
/// * `template_version` - Downloaded template version (None = use embedded fallback)
async fn perform_init(
    ctx: &mut CliContext,
    project_path: &Path,
    project_name: &str,
    template: &Option<String>,
    nodes: &[String],
    dev: bool,
    template_version: Option<&str>,
) -> Result<()> {
    // Create project directory structure
    ctx.init_service().create_project_directories(project_path).await?;

    // Copy templates from downloaded templates (if available), framework, or embedded fallback
    // Priority: 1. Framework path (dev mode), 2. Downloaded templates, 3. Embedded fallback
    if let Some(version) = template_version {
        // Use downloaded templates
        copy_downloaded_templates(ctx, project_path, version).await?;
    } else {
        // Fallback to framework path + embedded templates
        copy_fallback_templates(ctx, project_path).await?;
    }

    // Generate mecha10.json from template file (single source of truth)
    create_mecha10_json_from_template(ctx, project_path, project_name, template, template_version).await?;

    // Generate simulation/models/model.json
    ctx.project_template_service()
        .create_simulation_model_json(project_path)
        .await?;

    // Generate simulation/environments/basic_arena/environment.json
    ctx.project_template_service()
        .create_simulation_environment_json(project_path)
        .await?;
    println!("✅ Simulation assets");
    println!("✅ Node configurations");

    // Create project files
    ctx.project_template_service()
        .create_readme(project_path, project_name)
        .await?;

    ctx.project_template_service().create_gitignore(project_path).await?;

    ctx.project_template_service()
        .create_package_json(project_path, project_name)
        .await?;

    ctx.project_template_service()
        .create_requirements_txt(project_path)
        .await?;

    // Create .cargo/config.toml with patch directives (only in framework dev mode)
    if dev {
        let framework_path = ctx.init_service().detect_framework_path()?;
        ctx.project_template_service()
            .create_cargo_config(project_path, &framework_path)
            .await?;
        println!("🔧 Framework dev mode: Created .cargo/config.toml with source patches");
    }

    // Create Rust project files
    ctx.project_template_service()
        .create_cargo_toml(project_path, project_name, dev)
        .await?;

    ctx.project_template_service()
        .create_main_rs(project_path, project_name)
        .await?;

    ctx.project_template_service().create_build_rs(project_path).await?;

    // Detect framework path for development mode
    let framework_path = if dev {
        ctx.init_service().detect_framework_path().ok()
    } else {
        None
    };

    ctx.project_template_service()
        .create_env_example(project_path, project_name, framework_path)
        .await?;

    ctx.project_template_service().create_rustfmt_toml(project_path).await?;

    ctx.project_template_service()
        .create_docker_compose(project_path, project_name)
        .await?;

    // Create remote node Docker files (for AI nodes running in containers)
    ctx.project_template_service()
        .create_remote_docker_files(project_path)
        .await?;
    println!("✅ Project files");

    // Add nodes if any were selected
    if !nodes.is_empty() {
        for node_name in nodes {
            if let Err(e) = ctx.init_service().add_example_node(project_path, node_name).await {
                println!("⚠️  Failed to add {}: {}", node_name, e);
            } else {
                println!("✅ Node: {}", node_name);
            }
        }
        println!();
    }

    Ok(())
}

/// Print success message after initialization
fn print_success_message(project_name: &str) {
    println!("✨ Project created successfully!");
    println!();
    println!("📋 Next steps:");
    println!("  1. cd {}", project_name);
    println!("  2. Set OpenAI API key in .env (for AI features):");
    println!("     OPENAI_API_KEY=your-key-here");
    println!("  3. Start development:");
    println!("     mecha10 dev");
    println!();
    println!("Useful commands:");
    println!("  mecha10 topics list            # View message topics");
    println!("  mecha10 logs [node]            # Monitor node logs");
    println!("  mecha10 topology               # Visualize system topology");
    println!("  mecha10 diagnostics watch      # Monitor diagnostics in real-time");
    println!("  mecha10 models                 # Manage AI models");
    println!();
    println!("In dev mode:");
    println!("  's' → Start simulation         'wasd' → Teleop control");
    // Use default config since this matches what's generated in mecha10.json
    println!("  Dashboard: {}", EnvironmentsConfig::default().dashboard_url());
    println!();
}
