use crate::channel::Channel;
use crate::component;
use crate::config::{self, Settings};
use crate::dist;
use crate::doc;
use crate::env;
use crate::error::{CjvError, Result};
use crate::init_cmd;
use crate::override_cmd;
use crate::resolve;
use crate::self_update;
use crate::toolchain::Toolchain;
use clap::{Parser, Subcommand};

/// cjv - Cangjie Version Manager
#[derive(Parser, Debug)]
#[command(
    name = "cjv",
    version = "0.3.0-beta",
    about = "Cangjie SDK toolchain manager"
)]
#[command(propagate_version = true)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    Install {
        toolchain: String,
        #[arg(short = 't', long = "target", value_delimiter = ',')]
        target: Vec<String>,
        #[arg(short = 'c', long = "component", value_delimiter = ',')]
        component: Vec<String>,
        #[arg(long = "force")]
        force: bool,
    },
    Uninstall {
        toolchain: String,
        #[arg(short = 'y', long = "yes")]
        yes: bool,
    },
    Update {
        toolchain: Option<String>,
        #[arg(long = "no-self-update")]
        no_self_update: bool,
    },
    Check,
    Show {
        #[command(subcommand)]
        subcommand: Option<ShowSubcommand>,
    },
    Run {
        toolchain: String,
        command: String,
        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
        args: Vec<String>,
        #[arg(long = "install")]
        install: bool,
    },
    Exec {
        #[arg(value_name = "+toolchain")]
        plus_toolchain: Option<String>,
        command: String,
        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
        args: Vec<String>,
    },
    Envsetup {
        #[arg(value_name = "+toolchain")]
        plus_toolchain: Option<String>,
        #[arg(long = "shell")]
        shell: Option<String>,
        #[arg(long = "target")]
        target: Option<String>,
    },
    Which {
        command: Option<String>,
    },
    #[command(alias = "docs")]
    Doc {
        topic: Option<String>,
        #[arg(long = "path")]
        show_path: bool,
        #[arg(long = "toolchain")]
        toolchain: Option<String>,
    },
    Default {
        toolchain: Option<String>,
    },
    Override {
        #[command(subcommand)]
        subcommand: OverrideSubcommand,
    },
    Component {
        #[command(subcommand)]
        subcommand: ComponentSubcommand,
    },
    Toolchain {
        #[command(subcommand)]
        subcommand: ToolchainSubcommand,
    },
    Set {
        #[command(subcommand)]
        subcommand: SetSubcommand,
    },
    Self_ {
        #[command(subcommand)]
        subcommand: SelfSubcommand,
    },
    Downloads {
        #[command(subcommand)]
        subcommand: DownloadsSubcommand,
    },
    Init {
        #[arg(short = 'y', long = "yes")]
        yes: bool,
        #[arg(long = "default-toolchain")]
        default_toolchain: Option<String>,
        #[arg(short = 'c', long = "component", value_delimiter = ',')]
        component: Vec<String>,
        #[arg(long = "no-modify-path")]
        no_modify_path: bool,
    },
}

#[derive(Subcommand, Debug)]
pub enum ShowSubcommand {
    Active,
    Installed,
    Home,
}

#[derive(Subcommand, Debug)]
pub enum OverrideSubcommand {
    Set {
        toolchain: String,
        #[arg(long = "path")]
        path: Option<String>,
    },
    Unset {
        #[arg(long = "path")]
        path: Option<String>,
        #[arg(long = "nonexistent")]
        nonexistent: bool,
    },
    List,
}

#[derive(Subcommand, Debug)]
pub enum ComponentSubcommand {
    Add {
        names: Vec<String>,
        #[arg(long = "toolchain")]
        toolchain: Option<String>,
        #[arg(long = "force")]
        force: bool,
    },
    Link {
        name: String,
        path: String,
        #[arg(long = "toolchain")]
        toolchain: Option<String>,
        #[arg(long = "force")]
        force: bool,
    },
    #[command(alias = "uninstall", alias = "rm", alias = "delete", alias = "del")]
    Remove {
        names: Vec<String>,
        #[arg(long = "toolchain")]
        toolchain: Option<String>,
    },
    List {
        #[arg(long = "toolchain")]
        toolchain: Option<String>,
        #[arg(long = "installed")]
        installed: bool,
        #[arg(short = 'q', long = "quiet")]
        quiet: bool,
    },
}

#[derive(Subcommand, Debug)]
pub enum ToolchainSubcommand {
    List,
    Link {
        name: String,
        source: String,
        #[arg(long = "sha256")]
        sha256: Option<String>,
        #[arg(long = "force")]
        force: bool,
        #[arg(long = "no-stdx")]
        no_stdx: bool,
    },
    Uninstall {
        name: String,
        #[arg(short = 'y', long = "yes")]
        yes: bool,
    },
    #[command(name = "list-remote")]
    ListRemote,
}

#[derive(Subcommand, Debug)]
pub enum SetSubcommand {
    #[command(name = "auto-self-update")]
    AutoSelfUpdate { value: String },
    #[command(name = "auto-install")]
    AutoInstall { value: String },
    #[command(name = "default-host")]
    DefaultHost { value: String },
    #[command(name = "gitcode-api-key")]
    GitcodeApiKey { key: String },
    #[command(name = "manifest-url")]
    ManifestUrl { url: String },
    #[command(name = "home")]
    Home { path: Option<String> },
}

#[derive(Subcommand, Debug)]
pub enum SelfSubcommand {
    Update,
    Uninstall {
        #[arg(short = 'y', long = "yes")]
        yes: bool,
    },
}

#[derive(Subcommand, Debug)]
pub enum DownloadsSubcommand {
    Purge,
}

pub async fn execute(cli: &Cli) -> Result<()> {
    match &cli.command {
        Commands::Install {
            toolchain,
            target,
            component: components,
            force,
        } => cmd_install(toolchain, target, components, *force).await,
        Commands::Uninstall { toolchain, yes } => cmd_uninstall(toolchain, *yes),
        Commands::Update {
            toolchain,
            no_self_update,
        } => cmd_update(toolchain.as_deref(), *no_self_update).await,
        Commands::Check => cmd_check().await,
        Commands::Show { subcommand } => cmd_show(subcommand.as_ref()),
        Commands::Run {
            toolchain,
            command,
            args,
            install,
        } => cmd_run(toolchain, command, args, *install).await,
        Commands::Exec {
            plus_toolchain,
            command,
            args,
        } => cmd_exec(plus_toolchain, command, args),
        Commands::Envsetup {
            plus_toolchain,
            shell,
            target,
        } => cmd_envsetup(plus_toolchain, shell.as_deref(), target.as_deref()),
        Commands::Which { command } => cmd_which(command.as_deref()),
        Commands::Doc {
            topic,
            show_path,
            toolchain,
        } => cmd_doc(topic.as_deref(), *show_path, toolchain.as_deref()),
        Commands::Default { toolchain } => cmd_default(toolchain.as_deref()),
        Commands::Override { subcommand } => cmd_override(subcommand),
        Commands::Component { subcommand } => cmd_component(subcommand).await,
        Commands::Toolchain { subcommand } => cmd_toolchain(subcommand).await,
        Commands::Set { subcommand } => cmd_set(subcommand),
        Commands::Self_ { subcommand } => cmd_self(subcommand),
        Commands::Downloads { subcommand } => cmd_downloads(subcommand),
        Commands::Init {
            yes,
            default_toolchain,
            component,
            no_modify_path,
        } => {
            init_cmd::run_init(
                *yes,
                default_toolchain.as_deref(),
                component,
                *no_modify_path,
            )
            .await
        }
    }
}

// ========== cmd_install ==========

async fn cmd_install(
    toolchain: &str,
    targets: &[String],
    components: &[String],
    force: bool,
) -> Result<()> {
    let normalized_targets =
        crate::target::normalize_target_list(targets).map_err(CjvError::InvalidArgument)?;
    let tc_name = resolve::resolve_toolchain_name(toolchain);

    if Toolchain::is_installed(&tc_name) && !force {
        return Err(CjvError::AlreadyExists(format!(
            "Toolchain '{}' is already installed. Use --force to reinstall.",
            tc_name
        )));
    }

    let settings = Settings::load()?;
    let distribution = dist::resolve_toolchain_distribution(toolchain, &settings).await?;
    let target_base_url = distribution
        .url
        .rsplit_once('/')
        .map(|(base, _)| base.to_string())
        .unwrap_or_else(|| "https://download.cangjie-lang.cn".to_string());
    dist::download_and_install(
        &distribution.install_name,
        &distribution.url,
        distribution.checksum.as_deref(),
    )
    .await?;
    let tc_name = distribution.install_name;

    for comp in components {
        component::install_component(&tc_name, comp, false).await?;
    }

    for target in &normalized_targets {
        println!("Installing target '{}'...", target);
        let target_url = format!("{}/targets/{}/{}.tar.gz", target_base_url, tc_name, target);
        let target_path = Toolchain::get_path(&tc_name).join("target").join(target);
        if let Some(parent) = target_path.parent() {
            crate::utils::ensure_dir(parent)?;
        }
        let archive_path = crate::utils::cjv_home()
            .join("downloads")
            .join(format!("target-{}-{}.tar.gz", tc_name, target));
        match dist::download_file(&target_url, &archive_path).await {
            Ok(_) => {
                crate::toolchain::extract_archive_to(&archive_path, &target_path)?;
                component::update_target_manifest(&tc_name, target, true)?;
                let _ = std::fs::remove_file(&archive_path);
                println!("Target '{}' installed.", target);
            }
            Err(e) => eprintln!("Warning: Failed to install target '{}': {}", target, e),
        }
    }

    Ok(())
}

// ========== cmd_uninstall ==========

fn cmd_uninstall(toolchain: &str, yes: bool) -> Result<()> {
    let tc_name = resolve::resolve_toolchain_name(toolchain);
    if !Toolchain::is_installed(&tc_name) {
        return Err(CjvError::ToolchainNotInstalled(tc_name));
    }
    if !yes {
        use dialoguer::Confirm;
        let confirmed = Confirm::new()
            .with_prompt(format!("Uninstall toolchain '{}'?", tc_name))
            .default(false)
            .interact()
            .map_err(|e| CjvError::Other(format!("Failed to get confirmation: {}", e)))?;
        if !confirmed {
            println!("Uninstall cancelled.");
            return Ok(());
        }
    }
    let mut settings = Settings::load()?;
    if settings.get_default_toolchain().as_deref() == Some(&tc_name) {
        settings.set_default_toolchain(None)?;
    }
    let overrides = settings.get_overrides();
    for (path, tc) in &overrides {
        if tc == &tc_name {
            settings.remove_override(path)?;
        }
    }
    Toolchain::uninstall(&tc_name)?;
    println!("Toolchain '{}' has been uninstalled.", tc_name);
    Ok(())
}

// ========== cmd_update (incremental) ==========

async fn cmd_update(toolchain: Option<&str>, no_self_update: bool) -> Result<()> {
    let settings = Settings::load()?;
    if !no_self_update && settings.auto_self_update.as_deref() == Some("enable") {
        eprintln!(
            "Skipping self update: 'cjv self update' is not implemented in this beta. Use --no-self-update to suppress this notice."
        );
    }
    let manifest_url = settings.get_manifest_url();
    let manifest = dist::fetch_manifest(&manifest_url).await;
    let installed = Toolchain::list_installed()?;
    let platform = crate::utils::current_platform()?;

    if let Some(tc) = toolchain {
        let tc_name = resolve::resolve_toolchain_name(tc);
        if !Toolchain::is_installed(&tc_name) {
            return Err(CjvError::ToolchainNotInstalled(tc_name));
        }
        if let Some(ref m) = manifest {
            let parsed = crate::toolchain_name::ToolchainName::parse(tc)
                .map_err(CjvError::InvalidArgument)?;
            let ch = match parsed.channel {
                crate::toolchain_name::Channel::Lts => Channel::Lts,
                crate::toolchain_name::Channel::Sts => Channel::Sts,
                crate::toolchain_name::Channel::Nightly => Channel::Nightly,
                crate::toolchain_name::Channel::Unknown => Channel::Unknown,
            };
            if let Some(latest) = m.latest_for_channel(ch.as_str()) {
                let current = installed.iter().find(|t| t.name == tc_name);
                if let Some(c) = current {
                    if !c.version.is_empty() && c.version == latest.version {
                        println!(
                            "Toolchain '{}' is already at the latest version ({}).",
                            tc_name, latest.version
                        );
                        return Ok(());
                    }
                }
                println!("Updating '{}' to version {}...", tc_name, latest.version);
            }
        }
        install_update_candidate(tc, &tc_name, &settings, manifest.as_ref(), &platform).await?;
        println!("Toolchain '{}' updated successfully.", tc_name);
    } else {
        for tc in &installed {
            if tc.is_linked {
                println!("Skipping linked toolchain '{}' (cannot update)", tc.name);
                continue;
            }
            if tc.channel == Channel::Unknown || tc.is_custom {
                println!("Skipping custom toolchain '{}' (cannot update)", tc.name);
                continue;
            }
            if let Some(ref m) = manifest {
                let ch = Channel::parse(&tc.name);
                if let Some(latest) = m.latest_for_channel(ch.as_str()) {
                    if !tc.version.is_empty() && tc.version == latest.version {
                        println!("'{}' is already up to date ({}).", tc.name, tc.version);
                        continue;
                    }
                }
            }
            println!("Updating '{}'...", tc.name);
            let tc_name = tc.name.clone();
            install_update_candidate(
                tc.channel.as_str(),
                &tc_name,
                &settings,
                manifest.as_ref(),
                &platform,
            )
            .await?;
            println!("Toolchain '{}' updated successfully.", tc_name);
        }
    }
    Ok(())
}

async fn install_update_candidate(
    requested: &str,
    install_name: &str,
    settings: &Settings,
    manifest: Option<&crate::manifest::Manifest>,
    platform: &str,
) -> Result<()> {
    let parsed = crate::toolchain_name::ToolchainName::parse(requested)
        .map_err(CjvError::InvalidArgument)?;
    if parsed.is_custom() {
        return Err(CjvError::InvalidArgument(format!(
            "Cannot update custom toolchain '{}'",
            requested
        )));
    }
    let channel = match parsed.channel {
        crate::toolchain_name::Channel::Lts => Channel::Lts,
        crate::toolchain_name::Channel::Sts => Channel::Sts,
        crate::toolchain_name::Channel::Nightly => Channel::Nightly,
        crate::toolchain_name::Channel::Unknown => Channel::Unknown,
    };
    let version = parsed.version.as_str();
    if let Some(manifest) = manifest {
        if channel != Channel::Unknown {
            if let Some(download) = manifest.get_download(channel.as_str(), version, platform) {
                return dist::download_and_install(
                    install_name,
                    &download.url,
                    download.sha256.as_deref(),
                )
                .await;
            }
        }
    }

    let registry = crate::channel::Registry::new(settings.get_gitcode_api_key());
    let url = registry.get_download_url(&channel, version, platform)?;
    let checksum = registry.get_checksum(&channel, version, platform).await?;
    dist::download_and_install(install_name, &url, checksum.as_deref()).await
}

// ========== cmd_check with manifest ==========

async fn cmd_check() -> Result<()> {
    let toolchains = Toolchain::list_installed()?;
    if toolchains.is_empty() {
        println!("No toolchains installed.");
        return Ok(());
    }
    let settings = Settings::load()?;
    let manifest_url = settings.get_manifest_url();
    let manifest = dist::fetch_manifest(&manifest_url).await;
    println!("Checking for updates...");
    for tc in &toolchains {
        print!("{}: ", tc.name);
        if tc.is_linked {
            println!("linked (skipped)");
            continue;
        }
        if let Some(ref m) = manifest {
            let ch = tc.channel;
            if let Some(latest) = m.latest_for_channel(ch.as_str()) {
                if tc.version.is_empty() || tc.version != latest.version {
                    if tc.version.is_empty() {
                        println!("{} available (current version unknown)", latest.version);
                    } else {
                        println!("{} → {} (update available)", tc.version, latest.version);
                    }
                } else {
                    println!("✓ up to date ({})", tc.version);
                }
            } else if ch == Channel::Unknown {
                println!("custom (skipped)");
            } else {
                println!("no release info");
            }
        } else {
            println!("no manifest (cannot check)");
        }
    }
    println!();
    println!("cjv version: 0.3.0-beta");
    Ok(())
}

// ========== cmd_show ==========

fn cmd_show(subcommand: Option<&ShowSubcommand>) -> Result<()> {
    match subcommand {
        Some(ShowSubcommand::Active) => {
            let settings = Settings::load()?;
            let active = config::resolve_active_toolchain_with_source(&settings);
            match active {
                Some((tc, source)) => {
                    println!("Active toolchain: {}", tc);
                    println!("Source: {}", source.as_str());
                }
                None => {
                    println!("No active toolchain configured.");
                    println!("Install one with 'cjv install lts' and set it as default with 'cjv default lts'");
                }
            }
        }
        Some(ShowSubcommand::Installed) => {
            let toolchains = Toolchain::list_installed()?;
            if toolchains.is_empty() {
                println!("No toolchains installed.");
            } else {
                println!("Installed toolchains:");
                for tc in &toolchains {
                    let link_marker = if tc.is_linked { " (linked)" } else { "" };
                    print!("  {} {}", tc.name, if tc.is_linked { "🔗" } else { "✓" });
                    print!("{}", link_marker);
                    if !tc.components.is_empty() {
                        print!(" [components: {}]", tc.components.join(", "));
                    }
                    if !tc.version.is_empty() {
                        print!(" (v{})", tc.version);
                    }
                    println!();
                }
            }
        }
        Some(ShowSubcommand::Home) => {
            let home = crate::utils::cjv_home();
            let source = if std::env::var("CJV_HOME").is_ok() {
                "CJV_HOME environment variable"
            } else {
                "default"
            };
            println!("{}", home.display());
            println!("Source: {}", source);
        }
        None => {
            let settings = Settings::load()?;
            let active = config::resolve_active_toolchain(&settings);
            let platform = crate::utils::current_platform()?;
            println!("Active toolchain:");
            match active {
                Some(ref tc) => {
                    let installed = Toolchain::is_installed(tc);
                    let status = if installed {
                        "✓ installed"
                    } else {
                        "✗ not installed"
                    };
                    println!("  {} ({})", tc, status);
                }
                None => println!("  (none configured)"),
            }
            println!();
            println!("Default host: {}", platform);
            let toolchains = Toolchain::list_installed()?;
            println!();
            println!("Installed toolchains:");
            if toolchains.is_empty() {
                println!("  (none)");
            } else {
                for tc in &toolchains {
                    let link_marker = if tc.is_linked { " 🔗" } else { "" };
                    let active_marker = if active.as_deref() == Some(&tc.name) {
                        " ← active"
                    } else {
                        ""
                    };
                    println!("  {}{}{}", tc.name, link_marker, active_marker);
                }
            }
        }
    }
    Ok(())
}

// ========== cmd_run ==========

async fn cmd_run(toolchain: &str, command: &str, args: &[String], install: bool) -> Result<()> {
    let tc_name = resolve::resolve_toolchain_name(toolchain);
    if !Toolchain::is_installed(&tc_name) {
        if install {
            let settings = Settings::load()?;
            dist::download_and_install_toolchain(toolchain, &settings).await?;
        } else {
            return Err(CjvError::ToolchainNotInstalled(tc_name));
        }
    }
    let tool_path = resolve::find_tool_path(&tc_name, command)?;
    let mut cmd = std::process::Command::new(&tool_path);
    cmd.args(args);
    env::inject_runtime_env(&mut cmd, &tc_name);
    let mut child = cmd
        .stdin(std::process::Stdio::inherit())
        .stdout(std::process::Stdio::inherit())
        .stderr(std::process::Stdio::inherit())
        .spawn()
        .map_err(|e| {
            CjvError::Proxy(format!(
                "Failed to execute '{}': {}",
                tool_path.display(),
                e
            ))
        })?;
    let status = child
        .wait()
        .map_err(|e| CjvError::Proxy(format!("Failed to wait for process: {}", e)))?;
    std::process::exit(status.code().unwrap_or(1));
}

// ========== cmd_exec ==========

fn cmd_exec(plus_toolchain: &Option<String>, command: &str, args: &[String]) -> Result<()> {
    let tc = plus_toolchain.as_deref().and_then(|s| s.strip_prefix('+'));
    let exit_code = env::exec_in_env(tc, command, args)?;
    std::process::exit(exit_code);
}

// ========== cmd_envsetup ==========

fn cmd_envsetup(
    plus_toolchain: &Option<String>,
    shell: Option<&str>,
    target: Option<&str>,
) -> Result<()> {
    let tc = plus_toolchain.as_deref().and_then(|s| s.strip_prefix('+'));
    let output = env::envsetup(tc, shell, target)?;
    println!("{}", output);
    Ok(())
}

// ========== cmd_which ==========

fn cmd_which(command: Option<&str>) -> Result<()> {
    let settings = Settings::load()?;
    let active_tc = config::resolve_active_toolchain(&settings)
        .ok_or_else(|| CjvError::Other("No active toolchain configured".to_string()))?;
    match command {
        Some(cmd) => {
            let path = resolve::find_tool_path(&active_tc, cmd)?;
            println!("{}", path.display());
        }
        None => {
            let path = Toolchain::get_path(&active_tc);
            println!("{}", path.display());
        }
    }
    Ok(())
}

// ========== cmd_doc ==========

fn cmd_doc(topic: Option<&str>, show_path: bool, toolchain: Option<&str>) -> Result<()> {
    doc::open_doc(topic, show_path, toolchain)
}

// ========== cmd_default ==========

fn cmd_default(toolchain: Option<&str>) -> Result<()> {
    let mut settings = Settings::load()?;
    match toolchain {
        Some("none") => {
            settings.set_default_toolchain(None)?;
            println!("Default toolchain cleared.");
        }
        Some(tc) => {
            settings.set_default_toolchain(Some(tc.to_string()))?;
            println!("Default toolchain set to '{}'.", tc);
        }
        None => match settings.get_default_toolchain() {
            Some(tc) => println!("{}", tc),
            None => println!("No default toolchain configured."),
        },
    }
    Ok(())
}

// ========== cmd_override ==========

fn cmd_override(subcommand: &OverrideSubcommand) -> Result<()> {
    match subcommand {
        OverrideSubcommand::Set { toolchain, path } => {
            override_cmd::set_override(path.as_deref(), toolchain)
        }
        OverrideSubcommand::Unset { path, nonexistent } => {
            if *nonexistent {
                override_cmd::clean_nonexistent_overrides()
            } else {
                override_cmd::unset_override(path.as_deref())
            }
        }
        OverrideSubcommand::List => {
            let overrides = override_cmd::list_overrides()?;
            if overrides.is_empty() {
                println!("No directory overrides configured.");
            } else {
                println!("Directory overrides:");
                for (path, tc) in &overrides {
                    println!("  {} -> {}", path, tc);
                }
            }
            Ok(())
        }
    }
}

// ========== cmd_component ==========

async fn cmd_component(subcommand: &ComponentSubcommand) -> Result<()> {
    match subcommand {
        ComponentSubcommand::Add {
            names,
            toolchain,
            force,
        } => {
            let tc = selected_component_toolchain(toolchain)?;
            for name in names {
                component::install_component(&tc, name, *force).await?;
            }
            Ok(())
        }
        ComponentSubcommand::Link {
            name,
            path,
            toolchain,
            force,
        } => {
            let tc = selected_component_toolchain(toolchain)?;
            component::link_component(&tc, name, &std::path::PathBuf::from(path), *force)
        }
        ComponentSubcommand::Remove { names, toolchain } => {
            let tc = selected_component_toolchain(toolchain)?;
            for name in names {
                component::remove_component(&tc, name)?;
            }
            Ok(())
        }
        ComponentSubcommand::List {
            toolchain,
            installed,
            quiet,
        } => {
            let tc = selected_component_toolchain(toolchain)?;
            let components = component::list_components(&tc, *installed)?;
            if *quiet {
                for c in &components {
                    println!("{}", c);
                }
            } else {
                println!("Components for '{}':", tc);
                if components.is_empty() {
                    println!("  (none)");
                } else {
                    for c in &components {
                        println!("  {}", c);
                    }
                }
            }
            Ok(())
        }
    }
}

fn selected_component_toolchain(toolchain: &Option<String>) -> Result<String> {
    toolchain
        .clone()
        .or_else(Toolchain::active_toolchain_name)
        .ok_or_else(|| {
            CjvError::Other(
                "No active toolchain configured. Pass --toolchain <tc> or set a default toolchain."
                    .to_string(),
            )
        })
}

// ========== cmd_toolchain ==========

async fn cmd_toolchain(subcommand: &ToolchainSubcommand) -> Result<()> {
    match subcommand {
        ToolchainSubcommand::List => {
            let toolchains = Toolchain::list_installed()?;
            if toolchains.is_empty() {
                println!("No toolchains installed.");
            } else {
                println!("Installed toolchains:");
                for tc in &toolchains {
                    println!(
                        "  {}{}",
                        tc.name,
                        if tc.is_linked { " (linked)" } else { "" }
                    );
                }
            }
            Ok(())
        }
        ToolchainSubcommand::Link {
            name,
            source,
            sha256,
            force,
            no_stdx,
        } => {
            if crate::utils::is_reserved_name(name) {
                return Err(CjvError::InvalidArgument(format!(
                    "Cannot use reserved name '{}' for a custom toolchain",
                    name
                )));
            }
            if Toolchain::is_installed(name) && !force {
                return Err(CjvError::AlreadyExists(format!(
                    "Toolchain '{}' already exists. Use --force to overwrite.",
                    name
                )));
            }
            if source.starts_with("http://") || source.starts_with("https://") {
                let downloads_dir = crate::utils::cjv_home().join("downloads");
                crate::utils::ensure_dir(&downloads_dir)?;
                let archive_name = format!("{}.tar.gz", name);
                let archive_path = downloads_dir.join(&archive_name);
                println!("Downloading from {}...", source);
                dist::download_file(source, &archive_path).await?;
                if let Some(hash) = sha256 {
                    dist::verify_checksum(&archive_path, hash)?;
                }
                if *force && Toolchain::is_installed(name) {
                    Toolchain::uninstall(name)?;
                }
                Toolchain::install_from_archive(name, &archive_path)?;
                let _ = std::fs::remove_file(&archive_path);
                if !no_stdx {
                    println!("stdx component can be installed with 'cjv component add stdx --toolchain {}'", name);
                }
                println!("Toolchain '{}' installed from URL.", name);
            } else {
                let source_path = std::path::PathBuf::from(source);
                if !source_path.exists() {
                    return Err(CjvError::NotFound(format!("Path '{}' not found", source)));
                }
                if source_path.is_dir() {
                    if *force && Toolchain::is_installed(name) {
                        Toolchain::uninstall(name)?;
                    }
                    Toolchain::link(name, &source_path)?;
                    println!("Toolchain '{}' linked to '{}'", name, source_path.display());
                } else {
                    if *force && Toolchain::is_installed(name) {
                        Toolchain::uninstall(name)?;
                    }
                    if let Some(hash) = sha256 {
                        dist::verify_checksum(&source_path, hash)?;
                    }
                    Toolchain::install_from_archive(name, &source_path)?;
                    println!(
                        "Toolchain '{}' installed from '{}'",
                        name,
                        source_path.display()
                    );
                }
            }
            Ok(())
        }
        ToolchainSubcommand::ListRemote => cmd_toolchain_list_remote().await,
        ToolchainSubcommand::Uninstall { name, yes } => cmd_uninstall(name, *yes),
    }
}

async fn cmd_toolchain_list_remote() -> Result<()> {
    let settings = Settings::load()?;
    let manifest_url = settings.get_manifest_url();
    println!("Fetching remote toolchains from {}...", manifest_url);
    match dist::fetch_manifest(&manifest_url).await {
        Some(manifest) => {
            println!();
            println!("Available remote toolchains:");
            for entry in &manifest.releases {
                let platforms: Vec<String> =
                    entry.downloads.iter().map(|d| d.platform.clone()).collect();
                println!(
                    "  {} {} [{}]",
                    entry.channel,
                    entry.version,
                    platforms.join(", ")
                );
            }
            return Ok(());
        }
        None => println!("Cannot reach distribution server. Showing default channels:"),
    }
    println!("  lts       - Long Term Support (latest)");
    println!("  sts       - Short Term Support (latest)");
    println!("  nightly   - Nightly builds (requires GitCode API key)");
    Ok(())
}

// ========== cmd_set ==========

fn cmd_set(subcommand: &SetSubcommand) -> Result<()> {
    let mut settings = Settings::load()?;
    match subcommand {
        SetSubcommand::AutoSelfUpdate { value } => {
            settings.set_auto_self_update(value.clone())?;
            println!("Setting 'auto-self-update' updated to '{}'", value);
        }
        SetSubcommand::AutoInstall { value } => {
            let val = match value.as_str() {
                "true" => true,
                "false" => false,
                _ => {
                    return Err(CjvError::InvalidArgument(format!(
                        "Invalid value '{}'. Must be 'true' or 'false'",
                        value
                    )))
                }
            };
            settings.set_auto_install(val)?;
            println!("Setting 'auto-install' updated to '{}'", value);
        }
        SetSubcommand::DefaultHost { value } => {
            settings.set_default_host(value.clone())?;
            println!("Setting 'default-host' updated to '{}'", value);
        }
        SetSubcommand::GitcodeApiKey { key } => {
            settings.set_gitcode_api_key(key.clone())?;
            println!("Setting 'gitcode-api-key' updated.");
        }
        SetSubcommand::ManifestUrl { url } => {
            settings.set_manifest_url(url.clone())?;
            println!("Setting 'manifest-url' updated to '{}'", url);
        }
        SetSubcommand::Home { path } => {
            settings.set_home(path.clone())?;
            match &path {
                Some(p) if !p.is_empty() => println!("Setting 'home' updated to '{}'", p),
                _ => println!("Setting 'home' cleared (reset to default)."),
            }
        }
    }
    Ok(())
}

// ========== cmd_self ==========

fn cmd_self(subcommand: &SelfSubcommand) -> Result<()> {
    match subcommand {
        SelfSubcommand::Update => self_update::self_update(),
        SelfSubcommand::Uninstall { yes } => self_update::self_uninstall(*yes),
    }
}

// ========== cmd_downloads ==========

fn cmd_downloads(subcommand: &DownloadsSubcommand) -> Result<()> {
    match subcommand {
        DownloadsSubcommand::Purge => {
            let downloads_dir = crate::utils::cjv_home().join("downloads");
            if downloads_dir.exists() {
                std::fs::remove_dir_all(&downloads_dir)?;
                crate::utils::ensure_dir(&downloads_dir)?;
                println!("Downloads cache cleared.");
            } else {
                println!("Downloads cache is empty.");
            }
            Ok(())
        }
    }
}