mod ansible_assets;
mod commands;
mod config;
mod hosts;
mod key_registry;
mod output;
mod playbook_meta;
mod prompt;
mod services;
mod signal;
mod ssh_config;
mod ssh_session;

use clap::{Parser, Subcommand};
use commands::ansible::{AnsibleCommands, run_ansible_bootstrap, run_ansible_run};
use commands::backup::{
    BackupCommands, RestoreOptions, run_backup_create, run_backup_list, run_backup_prune,
    run_backup_push, run_backup_restore, run_backup_sync, run_export_opml, run_import_opml,
};
use commands::bichon::{BichonCommands, run_bichon_command};
use commands::config_cmd::{
    ConfigCommands, run_config_edit, run_config_get, run_config_init, run_config_list,
    run_config_path, run_config_remove, run_config_set,
};
use commands::deploy::{DeployCmd, run_deploy};
use commands::dns::{
    DnsCommands, run_dns_delete, run_dns_list, run_dns_migrate, run_dns_set, run_dns_set_all,
    run_dns_status,
};
use commands::headscale::{
    HeadscaleCommands, run_headscale_add_user, run_headscale_list_nodes, run_headscale_list_users,
    run_headscale_remove_user,
};
use commands::host::{
    AddHostArgs, HostCommands, run_host_add, run_host_detect_tailscale_ip, run_host_edit,
    run_host_list, run_host_remove, run_host_show,
};
use commands::select::{SelectCommands, run_select_host, run_select_playbook};
use commands::ssh::{SshCommands, run_ssh_add_key, run_ssh_keygen};
use commands::sync::{SyncCommands, run_sync_hermes, run_sync_music};
use eyre::Result;

#[derive(Parser)]
#[command(name = "auberge")]
#[command(about = "CLI for selfhost infrastructure management")]
#[command(version)]
struct Cli {
    #[arg(short, long, global = true, help = "Enable verbose output")]
    verbose: bool,
    #[arg(
        short,
        long,
        global = true,
        help = "Suppress non-essential output",
        conflicts_with = "verbose"
    )]
    quiet: bool,
    #[arg(
        long,
        global = true,
        help = "Disable colored output (also honored via NO_COLOR env var)"
    )]
    no_color: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    #[command(alias = "dp", about = "Deploy apps to a host")]
    Deploy(DeployCmd),
    #[command(
        subcommand,
        alias = "se",
        about = "Select hosts or playbooks interactively"
    )]
    Select(SelectCommands),
    #[command(subcommand, alias = "a", about = "Run ansible playbooks")]
    Ansible(AnsibleCommands),
    #[command(subcommand, alias = "b", about = "Backup and restore application data")]
    Backup(BackupCommands),
    #[command(
        subcommand,
        alias = "hs",
        about = "Manage Headscale VPN users and nodes"
    )]
    Headscale(HeadscaleCommands),
    #[command(subcommand, alias = "h", about = "Manage VPS hosts")]
    Host(HostCommands),
    #[command(subcommand, alias = "ss", about = "SSH key management")]
    Ssh(SshCommands),
    #[command(subcommand, alias = "sy", about = "Sync files to remote hosts")]
    Sync(SyncCommands),
    #[command(subcommand, alias = "d", about = "DNS management via Namecheap")]
    Dns(DnsCommands),
    #[command(subcommand, alias = "c", about = "Manage user configuration")]
    Config(ConfigCommands),
    #[command(subcommand, about = "Manage Bichon email archive behavior")]
    Bichon(BichonCommands),
}

#[tokio::main]
async fn main() -> Result<()> {
    color_eyre::install()?;

    let cli = Cli::parse();
    output::set_verbose(cli.verbose);
    output::set_quiet(cli.quiet);
    output::set_no_color(cli.no_color);

    match cli.command {
        Commands::Deploy(cmd) => signal::with_ctrlc(|| run_deploy(cmd)),
        Commands::Select(cmd) => match cmd {
            SelectCommands::Host { group } => run_select_host(group),
            SelectCommands::Playbook => run_select_playbook(),
        },
        Commands::Headscale(cmd) => match cmd {
            HeadscaleCommands::AddUser {
                name,
                expiration,
                host,
            } => run_headscale_add_user(name, expiration, host),
            HeadscaleCommands::ListUsers { output, host } => run_headscale_list_users(output, host),
            HeadscaleCommands::ListNodes { output, host } => run_headscale_list_nodes(output, host),
            HeadscaleCommands::RemoveUser { name, yes, host } => {
                run_headscale_remove_user(name, yes, host)
            }
        },
        Commands::Host(cmd) => match cmd {
            HostCommands::Add {
                name,
                address,
                user,
                port,
                ssh_key,
                tags,
                description,
                no_input,
            } => run_host_add(AddHostArgs {
                name,
                address,
                user,
                port,
                ssh_key,
                tags,
                description,
                no_input,
            }),
            HostCommands::List { tags, output } => run_host_list(tags, output),
            HostCommands::Remove { name, yes } => run_host_remove(name, yes),
            HostCommands::Show { name } => run_host_show(name),
            HostCommands::Edit { name } => run_host_edit(name),
            HostCommands::DetectTailscaleIp { name } => run_host_detect_tailscale_ip(name),
        },
        Commands::Ansible(cmd) => match cmd {
            AnsibleCommands::Run {
                host,
                playbook,
                check,
                tags,
                skip_tags,
                user,
                ask_pass,
                force,
            } => signal::with_ctrlc(|| {
                run_ansible_run(
                    host, playbook, check, tags, skip_tags, user, ask_pass, force,
                )
            }),
            AnsibleCommands::Bootstrap {
                host,
                port,
                ip,
                user,
                force,
            } => run_ansible_bootstrap(host, port, ip, user, force),
        },
        Commands::Backup(cmd) => match cmd {
            BackupCommands::Create {
                host,
                apps,
                dest,
                ssh_key,
                include_music,
                dry_run,
            } => signal::with_ctrlc(|| {
                run_backup_create(host, apps, dest, ssh_key, include_music, dry_run).and_then(
                    |outcome| {
                        let failed = outcome.failed_apps();
                        if failed.is_empty() {
                            Ok(())
                        } else {
                            eyre::bail!("{} backup(s) failed", failed.len());
                        }
                    },
                )
            }),
            BackupCommands::Sync {
                host,
                apps,
                ssh_key,
                include_music,
                dry_run,
            } => {
                signal::with_ctrlc(|| run_backup_sync(host, apps, ssh_key, include_music, dry_run))
            }
            BackupCommands::List { host, app, output } => run_backup_list(host, app, output),
            BackupCommands::Restore {
                backup_id,
                host,
                from_host,
                apps,
                ssh_key,
                dry_run,
                yes,
                skip_playbook_unsafe,
            } => signal::with_ctrlc(|| {
                run_backup_restore(RestoreOptions {
                    backup_id,
                    host_arg: host,
                    from_host_arg: from_host,
                    apps,
                    ssh_key,
                    dry_run,
                    yes,
                    skip_playbook_unsafe,
                })
            }),
            BackupCommands::Push { host, backup_id } => {
                signal::with_ctrlc(|| run_backup_push(host, backup_id))
            }
            BackupCommands::Prune { dry_run } => signal::with_ctrlc(|| run_backup_prune(dry_run)),
            BackupCommands::ExportOpml {
                host,
                output,
                ssh_key,
                user,
            } => run_export_opml(host, output, ssh_key, user),
            BackupCommands::ImportOpml {
                host,
                input,
                ssh_key,
                user,
            } => run_import_opml(host, input, ssh_key, user),
        },
        Commands::Ssh(cmd) => match cmd {
            SshCommands::Keygen { host, user, force } => run_ssh_keygen(host, user, force),
            SshCommands::AddKey {
                host,
                connect_with,
                authorize,
                user,
                yes,
            } => run_ssh_add_key(host, connect_with, authorize, user, yes),
        },
        Commands::Sync(cmd) => match cmd {
            SyncCommands::Music {
                host,
                source,
                dry_run,
            } => signal::with_ctrlc(|| run_sync_music(host, source, dry_run)),
            SyncCommands::Hermes {
                host,
                source,
                dry_run,
                pull,
            } => signal::with_ctrlc(|| run_sync_hermes(host, source, dry_run, pull)),
        },
        Commands::Dns(cmd) => match cmd {
            DnsCommands::List {
                subdomain,
                output,
                production,
            } => run_dns_list(subdomain, output, production).await,
            DnsCommands::Status { output, production } => run_dns_status(output, production).await,
            DnsCommands::Set {
                subdomain,
                ip,
                production,
            } => run_dns_set(subdomain, ip, production).await,
            DnsCommands::Delete {
                subdomain,
                dry_run,
                output,
                production,
                yes,
            } => run_dns_delete(subdomain, dry_run, output, production, yes).await,
            DnsCommands::Migrate {
                ip,
                dry_run,
                output,
                production,
            } => run_dns_migrate(ip, dry_run, output, production).await,
            DnsCommands::SetAll {
                host,
                ip,
                dry_run,
                yes,
                strict,
                subdomains,
                skip,
                output,
                continue_on_error,
                production,
            } => {
                run_dns_set_all(
                    host,
                    ip,
                    dry_run,
                    yes,
                    strict,
                    subdomains,
                    skip,
                    output,
                    continue_on_error,
                    production,
                )
                .await
            }
        },
        Commands::Config(cmd) => match cmd {
            ConfigCommands::Init(args) => run_config_init(args),
            ConfigCommands::Set { key, value } => run_config_set(key, value),
            ConfigCommands::Get { key } => run_config_get(key),
            ConfigCommands::List => run_config_list(),
            ConfigCommands::Remove { key } => run_config_remove(key),
            ConfigCommands::Edit => run_config_edit(),
            ConfigCommands::Path => run_config_path(),
        },
        Commands::Bichon(cmd) => run_bichon_command(cmd).await,
    }
}
