//! CLI command handlers for `mcp-gateway`.
//!
//! Each public function corresponds to a top-level `Command` variant and
//! returns an `ExitCode` so `main` can remain a thin dispatcher.

#[cfg(feature = "webui")]
mod add_remove;
mod cap;
#[cfg(feature = "config-export")]
mod config_export;
#[cfg(feature = "discovery")]
pub(crate) mod discover;
mod doctor;
pub mod paths;
mod plugin;
mod setup;
mod skills;
mod stats;
mod upgrade;

#[cfg(feature = "webui")]
pub use add_remove::{run_add_command, run_get_command, run_list_command, run_remove_command};
pub use cap::run_cap_command;
#[cfg(feature = "config-export")]
pub use config_export::run_config_export;
pub use doctor::{run_doctor_command, run_doctor_shadow_command};
pub use plugin::{run_plugin_install, run_plugin_list, run_plugin_search, run_plugin_uninstall};
pub use setup::run_setup_command;
pub use skills::{
    run_skills_generate, run_skills_import, run_skills_list, run_skills_remove, run_skills_search,
    run_skills_show,
};
pub use stats::run_stats_command;
pub use upgrade::{check_upgrade, data_dir as upgrade_data_dir, run_upgrade_command};

use std::process::ExitCode;

use mcp_gateway::{
    cli::{
        TlsCommand, ToolCommand,
        completion::{ShellTarget, generate_completion},
        invoke::{ToolCatalogue, build_completion_tool_names, execute_tool, resolve_args},
        output::{OutputFormat, print_tool_inspect, print_tool_list, print_tool_result},
    },
    mtls::{CaParams, CertGenerator, LeafCertParams},
};

// ── init ─────────────────────────────────────────────────────────────────────

/// Generate a starter gateway configuration file.
///
/// Writes a commented YAML configuration to `output`. When `with_examples` is
/// `true` three free-tier capability definitions are included so the user can
/// start experimenting immediately.
pub fn run_init_command(output: &std::path::Path, with_examples: bool) -> ExitCode {
    if output.exists() {
        eprintln!(
            "Error: {} already exists. Remove it first or choose a different path with --output.",
            output.display()
        );
        return ExitCode::FAILURE;
    }

    let config_content = build_init_config(with_examples);

    match std::fs::write(output, config_content) {
        Ok(()) => {
            print_init_success(output);
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: Failed to write {}: {e}", output.display());
            ExitCode::FAILURE
        }
    }
}

fn build_init_config(with_examples: bool) -> String {
    let examples_section = if with_examples {
        r#"
# Capabilities - direct REST API integration (no MCP server needed)
# These free capabilities work out of the box with zero API keys.
capabilities:
  enabled: true
  directories:
    - capabilities

# Example MCP backends (uncomment to enable)
# backends:
#   filesystem:
#     command: "npx -y @anthropic/mcp-server-filesystem /path/to/dir"
#     description: "File system access"
#   brave-search:
#     command: "npx -y @anthropic/mcp-server-brave-search"
#     description: "Web search via Brave"
#     env:
#       BRAVE_API_KEY: "${BRAVE_API_KEY}"
"#
    } else {
        r#"
# Capabilities - direct REST API integration
capabilities:
  enabled: true
  directories:
    - capabilities

# Add your MCP backends here:
# backends:
#   my-server:
#     command: "npx -y @my/mcp-server"
#     description: "My MCP server"
"#
    };

    format!(
        r#"# MCP Gateway Configuration
# ========================
# Generated by: mcp-gateway init
#
# Documentation: https://github.com/MikkoParkkola/mcp-gateway#readme

# Server settings
server:
  host: "127.0.0.1"
  port: 39400

# Meta-MCP mode - exposes a compact gateway tool surface
# Common deployment: 14 tools (12 minimum, 15 with webhooks)
# Keeps prompt overhead low by discovering backend tools on demand
meta_mcp:
  enabled: true
  cache_tools: true
  cache_ttl: 300s
{examples_section}"#
    )
}

fn print_init_success(output: &std::path::Path) {
    println!("Created {}", output.display());
    println!();
    println!("Next steps:");
    println!("  1. Review and edit {}", output.display());
    println!("  2. Start the gateway:");
    println!("     mcp-gateway -c {}", output.display());
    println!("  3. Add to your MCP client (e.g. Claude Desktop):");
    println!("     {{");
    println!("       \"mcpServers\": {{");
    println!("         \"gateway\": {{");
    println!("           \"url\": \"http://127.0.0.1:39400/mcp\"");
    println!("         }}");
    println!("       }}");
    println!("     }}");
}

// ── tls ──────────────────────────────────────────────────────────────────────

/// Run `mcp-gateway tls` subcommands.
#[allow(clippy::too_many_lines)]
pub fn run_tls_command(cmd: TlsCommand) -> ExitCode {
    match cmd {
        TlsCommand::InitCa {
            cn,
            validity_days,
            out,
        } => tls_init_ca(&cn, validity_days, &out),
        TlsCommand::IssueServer {
            ca_cert,
            ca_key,
            cn,
            san_dns,
            validity_days,
            out,
        } => tls_issue_server(&ca_cert, &ca_key, &cn, &san_dns, validity_days, &out),
        TlsCommand::IssueClient {
            ca_cert,
            ca_key,
            cn,
            ou,
            spiffe_uri,
            validity_days,
            out,
        } => tls_issue_client(
            &ca_cert,
            &ca_key,
            &cn,
            ou.as_deref(),
            spiffe_uri,
            validity_days,
            &out,
        ),
    }
}

fn tls_init_ca(cn: &str, validity_days: u32, out: &std::path::Path) -> ExitCode {
    println!("Generating Root CA: {cn}");
    let params = CaParams { cn, validity_days };
    match CertGenerator::init_ca(&params) {
        Ok(cert) => {
            if let Err(e) = CertGenerator::write_to_dir(&cert, out, "ca") {
                eprintln!("Error: Failed to write CA files: {e}");
                return ExitCode::FAILURE;
            }
            println!("  CA cert: {}", out.join("ca.crt").display());
            println!("  CA key:  {}", out.join("ca.key").display());
            println!();
            println!("Keep the CA key offline or in a vault.");
            println!("Add to gateway.yaml:");
            println!("  mtls:");
            println!("    ca_cert: \"{}\"", out.join("ca.crt").display());
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: CA generation failed: {e}");
            ExitCode::FAILURE
        }
    }
}

fn tls_issue_server(
    ca_cert: &std::path::Path,
    ca_key: &std::path::Path,
    cn: &str,
    san_dns: &str,
    validity_days: u32,
    out: &std::path::Path,
) -> ExitCode {
    println!("Issuing server certificate for: {cn}");
    let (ca_cert_pem, ca_key_pem) = match read_ca_files(ca_cert, ca_key) {
        Ok(pair) => pair,
        Err(code) => return code,
    };
    let sans: Vec<String> = san_dns
        .split(',')
        .filter(|s| !s.is_empty())
        .map(str::to_owned)
        .collect();
    let params = LeafCertParams {
        cn,
        ou: None,
        san_dns: sans,
        san_uris: vec![],
        validity_days,
    };
    match CertGenerator::issue_leaf(&params, &ca_cert_pem, &ca_key_pem) {
        Ok(cert) => {
            if let Err(e) = CertGenerator::write_to_dir(&cert, out, "server") {
                eprintln!("Error: Failed to write server cert files: {e}");
                return ExitCode::FAILURE;
            }
            println!("  Cert: {}", out.join("server.crt").display());
            println!("  Key:  {}", out.join("server.key").display());
            println!();
            println!("Add to gateway.yaml:");
            println!("  mtls:");
            println!("    enabled: true");
            println!("    server_cert: \"{}\"", out.join("server.crt").display());
            println!("    server_key:  \"{}\"", out.join("server.key").display());
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: Server cert generation failed: {e}");
            ExitCode::FAILURE
        }
    }
}

fn tls_issue_client(
    ca_cert: &std::path::Path,
    ca_key: &std::path::Path,
    cn: &str,
    ou: Option<&str>,
    spiffe_uri: Option<String>,
    validity_days: u32,
    out: &std::path::Path,
) -> ExitCode {
    println!("Issuing client certificate for: {cn}");
    let (ca_cert_pem, ca_key_pem) = match read_ca_files(ca_cert, ca_key) {
        Ok(pair) => pair,
        Err(code) => return code,
    };
    let san_uris = spiffe_uri.map(|u| vec![u]).unwrap_or_default();
    let params = LeafCertParams {
        cn,
        ou,
        san_dns: vec![],
        san_uris,
        validity_days,
    };
    let stem = cn.replace(['/', ' '], "-");
    match CertGenerator::issue_leaf(&params, &ca_cert_pem, &ca_key_pem) {
        Ok(cert) => {
            if let Err(e) = CertGenerator::write_to_dir(&cert, out, &stem) {
                eprintln!("Error: Failed to write client cert files: {e}");
                return ExitCode::FAILURE;
            }
            println!("  Cert: {}", out.join(format!("{stem}.crt")).display());
            println!("  Key:  {}", out.join(format!("{stem}.key")).display());
            println!();
            println!("Configure the agent:");
            println!(
                "  export MCP_GATEWAY_CLIENT_CERT={}",
                out.join(format!("{stem}.crt")).display()
            );
            println!(
                "  export MCP_GATEWAY_CLIENT_KEY={}",
                out.join(format!("{stem}.key")).display()
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: Client cert generation failed: {e}");
            ExitCode::FAILURE
        }
    }
}

/// Read CA cert and key PEM files, returning early with `FAILURE` on I/O errors.
fn read_ca_files(
    ca_cert: &std::path::Path,
    ca_key: &std::path::Path,
) -> Result<(String, String), ExitCode> {
    let cert = std::fs::read_to_string(ca_cert).map_err(|e| {
        eprintln!("Error: Cannot read CA cert '{}': {e}", ca_cert.display());
        ExitCode::FAILURE
    })?;
    let key = std::fs::read_to_string(ca_key).map_err(|e| {
        eprintln!("Error: Cannot read CA key '{}': {e}", ca_key.display());
        ExitCode::FAILURE
    })?;
    Ok((cert, key))
}

// ── tool ─────────────────────────────────────────────────────────────────────

/// Run `mcp-gateway tool` subcommands (CLI bridge for direct tool invocation).
pub async fn run_tool_command(cmd: ToolCommand) -> ExitCode {
    match cmd {
        ToolCommand::List {
            capabilities,
            format,
        } => tool_list(capabilities, format).await,
        ToolCommand::Inspect {
            tool,
            capabilities,
            format,
        } => tool_inspect(tool, capabilities, format).await,
        ToolCommand::Invoke {
            tool,
            capabilities,
            args,
            kv_args,
            format,
        } => tool_invoke(tool, capabilities, args, kv_args, format).await,
        ToolCommand::Completions {
            shell,
            capabilities,
        } => tool_completions(shell, capabilities).await,
    }
}

async fn tool_list(capabilities: std::path::PathBuf, format: OutputFormat) -> ExitCode {
    let dir = capabilities.to_string_lossy();
    // `tool list` scans a *local* capability-YAML directory. It is independent
    // of the running gateway's `-c gateway.yaml` config (including
    // `capabilities.enabled`), which controls the server, not this CLI scan.
    // When the directory is absent, report an empty catalogue with a one-line
    // explanation rather than hard-failing (see issue #225); the `discover`
    // path already degrades this way for the same condition.
    if !capabilities.exists() {
        eprintln!(
            "No capability catalogue at '{dir}'. `tool list` scans a local directory of \
             capability YAML files (set -C/--capabilities or MCP_GATEWAY_CAPABILITIES) and is \
             independent of your server config. A configured gateway exposes its tools over MCP \
             at runtime, not via this command."
        );
        print_tool_list(&[], format);
        return ExitCode::SUCCESS;
    }
    match ToolCatalogue::load(&dir).await {
        Ok(cat) => {
            print_tool_list(&cat.list_entries(), format);
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: Failed to load capabilities from '{dir}': {e}");
            ExitCode::FAILURE
        }
    }
}

async fn tool_inspect(
    tool: String,
    capabilities: std::path::PathBuf,
    format: OutputFormat,
) -> ExitCode {
    let dir = capabilities.to_string_lossy();
    match ToolCatalogue::load(&dir).await {
        Ok(cat) => {
            if let Some(cap) = cat.find(&tool) {
                print_tool_inspect(&cap.name, &cap.description, &cap.schema.input, format);
                ExitCode::SUCCESS
            } else {
                eprintln!(
                    "Error: Tool '{tool}' not found. Run 'tool list' to see available tools."
                );
                ExitCode::FAILURE
            }
        }
        Err(e) => {
            eprintln!("Error: Failed to load capabilities from '{dir}': {e}");
            ExitCode::FAILURE
        }
    }
}

async fn tool_invoke(
    tool: String,
    capabilities: std::path::PathBuf,
    args: Option<String>,
    kv_args: Vec<String>,
    format: OutputFormat,
) -> ExitCode {
    let dir = capabilities.to_string_lossy();
    let catalogue = match ToolCatalogue::load(&dir).await {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Error: Failed to load capabilities from '{dir}': {e}");
            return ExitCode::FAILURE;
        }
    };
    let resolved = match resolve_args(args.as_deref(), &kv_args, true) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("Error: {e}");
            return ExitCode::FAILURE;
        }
    };
    match execute_tool(&catalogue, &tool, resolved).await {
        Ok(result) => {
            print_tool_result(&result, format);
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: {e}");
            ExitCode::FAILURE
        }
    }
}

async fn tool_completions(
    shell: clap_complete::Shell,
    capabilities: std::path::PathBuf,
) -> ExitCode {
    let dir = capabilities.to_string_lossy();
    let tool_names = build_completion_tool_names(&dir).await;
    let target = ShellTarget::from_shell(shell).unwrap_or(ShellTarget::Bash);
    let script = generate_completion(target, &tool_names);
    print!("{script}");
    ExitCode::SUCCESS
}
