Skip to content
use crate::docker::{self, discover_files, handle_dockerignore};
use inquire::Text;
use std::fs;
use std::path::Path;

fn ask_for_tools(
    existing_tools: &[String],
) -> Result<(Vec<String>, bool), Box<dyn std::error::Error>> {
    let mut selected = existing_tools.to_vec();
    let mut new_tools_added = false;

    println!("\nAdditional tools installation:");
    println!(
        "   (Already configured: {})",
        if existing_tools.is_empty() {
            "none".to_string()
        } else {
            existing_tools.join(", ")
        }
    );

    let should_add_tools =
        inquire::Confirm::new("Do you want to add additional tools to install in the Dockerfile?")
            .with_default(false)
            .with_help_message(
                "These are apt packages that will be installed during the build stage",
            )
            .prompt()?;

    if should_add_tools {
        loop {
            let tool: String = Text::new("Enter tool name (or press Enter to finish):")
                .with_help_message("Example: curl, jq, vim, git")
                .prompt()?;

            let tool = tool.trim();
            if tool.is_empty() {
                break;
            }

            if !selected.contains(&tool.to_string()) {
                selected.push(tool.to_string());
                new_tools_added = true;
                println!("  [OK] Added '{}' to additional tools", tool);
            } else {
                println!("  '{}' is already in the list", tool);
            }

            let add_more = inquire::Confirm::new("Add another tool?")
                .with_default(true)
                .prompt()?;

            if !add_more {
                break;
            }
        }
    }

    Ok((selected, new_tools_added))
}

fn ask_for_runtime_deps(
    existing_deps: &[String],
) -> Result<(Vec<String>, bool), Box<dyn std::error::Error>> {
    let mut selected = existing_deps.to_vec();
    let mut new_deps_added = false;

    println!("\nAdditional runtime dependencies:");
    println!(
        "   (Already configured: {})",
        if existing_deps.is_empty() {
            "none".to_string()
        } else {
            existing_deps.join(", ")
        }
    );

    let should_add_deps = inquire::Confirm::new(
        "Do you want to add additional runtime dependencies?",
    )
    .with_default(false)
    .with_help_message(
        "These are apt packages that will be installed in the runtime stage (not build stage)",
    )
    .prompt()?;

    if should_add_deps {
        loop {
            let dep: String = Text::new("Enter runtime dependency (or press Enter to finish):")
                .with_help_message("Example: postgresql-client, redis-tools")
                .prompt()?;

            let dep = dep.trim();
            if dep.is_empty() {
                break;
            }

            if !selected.contains(&dep.to_string()) {
                selected.push(dep.to_string());
                new_deps_added = true;
                println!("  [OK] Added '{}' to runtime dependencies", dep);
            } else {
                println!("  '{}' is already in the list", dep);
            }

            let add_more = inquire::Confirm::new("Add another runtime dependency?")
                .with_default(true)
                .prompt()?;

            if !add_more {
                break;
            }
        }
    }

    Ok((selected, new_deps_added))
}

fn ask_for_files(
    existing_includes: &[String],
    existing_excludes: &[String],
) -> Result<(Vec<String>, Vec<String>), Box<dyn std::error::Error>> {
    let (available_includes, _) = discover_files();
    let mut selected = existing_includes.to_vec();
    let mut excludes = existing_excludes.to_vec();

    let new_files: Vec<String> = available_includes
        .into_iter()
        .filter(|f| !existing_includes.contains(f) && !existing_excludes.contains(f))
        .collect();

    if new_files.is_empty() {
        println!("  No new files to add.");
        return Ok((selected, excludes));
    }

    println!("\nSelect files/directories to include in the Docker build:");
    println!("   (Files you skip will be excluded from the Docker build context)");
    println!(
        "   (Already included: {})",
        if existing_includes.is_empty() {
            "none".to_string()
        } else {
            existing_includes.join(", ")
        }
    );
    println!();

    for file in new_files {
        let should_include = inquire::Confirm::new(&format!("Include '{}'?", file))
            .with_default(false)
            .prompt()?;

        if should_include {
            selected.push(file);
        } else {
            excludes.push(file);
        }
    }

    let add_custom = inquire::Confirm::new("Add custom exclude patterns?")
        .with_default(false)
        .with_help_message("Add patterns like '*.log', 'tests/', etc.")
        .prompt()?;

    if add_custom {
        loop {
            let pattern: String = Text::new("Enter exclude pattern (or press Enter to finish):")
                .with_help_message("Examples: *.log, tests/, *.tmp, docs/")
                .prompt()?;

            let pattern = pattern.trim();
            if pattern.is_empty() {
                break;
            }

            if !excludes.contains(&pattern.to_string()) {
                excludes.push(pattern.to_string());
                println!("  [OK] Added '{}' to excludes", pattern);
            } else {
                println!("  '{}' is already in the list", pattern);
            }

            let add_more = inquire::Confirm::new("Add another exclude pattern?")
                .with_default(true)
                .prompt()?;

            if !add_more {
                break;
            }
        }
    }

    excludes.sort();
    excludes.dedup();
    Ok((selected, excludes))
}

pub fn init(namespace: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
    if !Path::new("Cargo.toml").exists() {
        eprintln!(
            "Error: No Cargo.toml found in current directory. Please run this in a Rust project."
        );
        std::process::exit(1);
    }

    let cargo_content = fs::read_to_string("Cargo.toml")?;
    let mut cargo_toml: toml::Value = cargo_content.parse()?;

    let namespace = match namespace {
        Some(ns) => ns,
        None => {
            let existing_namespace = cargo_toml
                .get("spruce")
                .and_then(|s| s.get("namespace"))
                .and_then(|n| n.as_str())
                .map(String::from);

            match existing_namespace {
                Some(ns) => {
                    println!("Using existing namespace from Cargo.toml: '{}'", ns);
                    ns
                }
                None => Text::new("Enter the Docker namespace (e.g., your-dockerhub-username):")
                    .with_help_message("This will be used as the registry namespace")
                    .prompt()?
                    .trim()
                    .to_string(),
            }
        }
    };

    if namespace.is_empty() {
        eprintln!("Error: Namespace cannot be empty");
        std::process::exit(1);
    }

    let project_name: String = cargo_toml["package"]["name"]
        .as_str()
        .ok_or("Could not find package name in Cargo.toml")?
        .to_string();

    let existing_includes: Vec<String> = cargo_toml
        .get("spruce")
        .and_then(|s| s.get("includes"))
        .and_then(|i| i.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();

    let existing_tools: Vec<String> = cargo_toml
        .get("spruce")
        .and_then(|s| s.get("additional_tools"))
        .and_then(|t| t.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();

    let existing_runtime_deps: Vec<String> = cargo_toml
        .get("spruce")
        .and_then(|s| s.get("runtime_deps"))
        .and_then(|d| d.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();

    let existing_excludes: Vec<String> = cargo_toml
        .get("spruce")
        .and_then(|s| s.get("excludes"))
        .and_then(|e| e.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();

    let (includes, excludes) = ask_for_files(&existing_includes, &existing_excludes)?;

    let (additional_tools, _new_tools_added) = ask_for_tools(&existing_tools)?;

    let (runtime_deps, _new_runtime_deps_added) = ask_for_runtime_deps(&existing_runtime_deps)?;

    let vps_host: String = Text::new("Enter VPS host (e.g., user@myvps, leave empty to skip):")
        .with_help_message("This is used by 'spruce deploy' to SSH into your VPS")
        .with_default("")
        .prompt()?
        .trim()
        .to_string();

    let domain: String = Text::new("Enter domain (e.g., silenlocatelli.ch, leave empty to skip):")
        .with_help_message("Used by 'spruce deploy' to construct the app hostname as <name>.<domain>")
        .with_default("")
        .prompt()?
        .trim()
        .to_string();

    let mut spruce_table = toml::map::Map::new();
    spruce_table.insert(
        "namespace".to_string(),
        toml::Value::String(namespace.clone()),
    );
    spruce_table.insert("mode".to_string(), toml::Value::String("alpha".to_string()));

    if !vps_host.is_empty() {
        spruce_table.insert("host".to_string(), toml::Value::String(vps_host.clone()));
    }

    if !domain.is_empty() {
        spruce_table.insert("domain".to_string(), toml::Value::String(domain.clone()));
    }

    let includes_array: Vec<toml::Value> = includes
        .iter()
        .map(|s| toml::Value::String(s.clone()))
        .collect();
    spruce_table.insert("includes".to_string(), toml::Value::Array(includes_array));

    let tools_array: Vec<toml::Value> = additional_tools
        .iter()
        .map(|s| toml::Value::String(s.clone()))
        .collect();
    spruce_table.insert(
        "additional_tools".to_string(),
        toml::Value::Array(tools_array),
    );

    let runtime_deps_array: Vec<toml::Value> = runtime_deps
        .iter()
        .map(|s| toml::Value::String(s.clone()))
        .collect();
    spruce_table.insert(
        "runtime_deps".to_string(),
        toml::Value::Array(runtime_deps_array),
    );

    let excludes_array: Vec<toml::Value> = excludes
        .iter()
        .map(|s| toml::Value::String(s.clone()))
        .collect();
    spruce_table.insert("excludes".to_string(), toml::Value::Array(excludes_array));

    if let toml::Value::Table(ref mut table) = cargo_toml {
        table.insert("spruce".to_string(), toml::Value::Table(spruce_table));
    }

    let updated_cargo = toml::to_string_pretty(&cargo_toml)?;
    fs::write("Cargo.toml", updated_cargo)?;
    println!("[OK] Added [spruce] section to Cargo.toml");
    println!("  namespace = '{}'", namespace);
    println!("  mode = 'alpha'");
    if !vps_host.is_empty() {
        println!("  host = '{}'", vps_host);
    }
    if !domain.is_empty() {
        println!("  domain = '{}'", domain);
    }
    if !includes.is_empty() {
        println!(
            "  includes = [{}]",
            includes
                .iter()
                .map(|s| format!("'{}'", s))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }
    if !additional_tools.is_empty() {
        println!(
            "  additional_tools = [{}]",
            additional_tools
                .iter()
                .map(|s| format!("'{}'", s))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }
    if !excludes.is_empty() {
        println!(
            "  excludes = [{}]",
            excludes
                .iter()
                .map(|s| format!("'{}'", s))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    let dockerfile_exists = Path::new("Dockerfile").exists();

    if dockerfile_exists {
        let overwrite = inquire::Confirm::new("Dockerfile already exists. Overwrite it?")
            .with_default(false)
            .prompt()?;

        if overwrite {
            let dockerfile_content =
                docker::generate_dockerfile(&project_name, &includes, &additional_tools, &runtime_deps);
            fs::write("Dockerfile", dockerfile_content)?;
            println!("[OK] Overwrote Dockerfile");
        } else {
            println!("  Skipped Dockerfile (kept existing)");
        }
    } else {
        let dockerfile_content =
            docker::generate_dockerfile(&project_name, &includes, &additional_tools, &runtime_deps);
        fs::write("Dockerfile", dockerfile_content)?;
        println!("[OK] Created Dockerfile");
    }

    handle_dockerignore(&excludes)?;

    println!("\nInitialization complete!");
    println!("   Image name: {}/{}\n", namespace, project_name);
    println!("The [spruce] section in Cargo.toml now contains:");
    println!(
        "   namespace = \"{}\"  # Docker registry namespace",
        namespace
    );
    println!("   mode = \"alpha\"       # version bump mode (alpha/beta/normal)");
    if !includes.is_empty() {
        println!(
            "   includes = {:?}  # files to include in Docker build",
            includes
        );
    }
    if !additional_tools.is_empty() {
        println!(
            "   additional_tools = {:?}  # tools to install in Dockerfile",
            additional_tools
        );
    }
    if !excludes.is_empty() {
        println!(
            "   excludes = {:?}  # files excluded from Docker build context",
            excludes
        );
    }
    println!("\nNext steps:");
    println!("   - Run 'spruce build' to build the image");
    println!("   - Run 'spruce push' to push to the registry");
    println!("   - Run 'spruce deploy' to deploy on your VPS");
    println!("   - Run 'spruce mode <alpha|beta|normal>' to change mode");
    println!("   - Run 'spruce init' again to add more files or tools");

    Ok(())
}