use anyhow::Context;
use clap::Parser;
use directories_next::ProjectDirs;
use log::debug;
use std::path::PathBuf;
use vmadm::cmd;
use vmadm::config::Configuration;
use vmadm::progress::{MessageKind, Progress};
use vmadm::spec::Specification;

const QUALIFIER: &str = "";
const ORG: &str = "";
const APP: &str = "vmadm";

#[derive(Debug, Parser)]
struct Cli {
    #[clap(subcommand)]
    cmd: Command,
}

#[derive(Debug, Parser)]
enum Command {
    #[clap(visible_alias = "create")]
    New {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    Recreate {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    Config {
        #[clap(flatten)]
        common: CommonOptions,
    },

    Spec {
        #[clap(flatten)]
        common: CommonOptions,

        spec: PathBuf,
    },

    List {
        #[clap(flatten)]
        common: CommonOptions,
    },

    #[clap(visible_alias = "remove")]
    Delete {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    Start {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    #[clap(visible_alias = "restart")]
    Reboot {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    #[clap(visible_alias = "stop")]
    Shutdown {
        #[clap(flatten)]
        common: CommonOptions,

        specs: Vec<PathBuf>,
    },

    CloudInit {
        #[clap(flatten)]
        common: CommonOptions,

        spec: PathBuf,
        dirname: PathBuf,
    },
}

#[derive(Debug, Parser)]
struct CommonOptions {
    #[clap(short, long)]
    config: Option<PathBuf>,

    #[clap(short, long)]
    quiet: bool,

    #[clap(short, long)]
    verbose: bool,
}

fn main() -> anyhow::Result<()> {
    env_logger::init_from_env("VMADM_LOG");
    let cli = Cli::parse();
    debug!("{:#?}", cli);

    match cli.cmd {
        Command::New { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::new(&specs, &progress)?;
        }

        Command::Recreate { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::recreate(&specs, &progress)?;
        }

        Command::Config { common } => {
            let progress = get_progress(&common);
            let config = config(&common)?;
            cmd::config(&config, &progress)?;
        }

        Command::Spec { common, spec } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &[spec])?;
            cmd::spec(&specs, &progress)?;
        }

        Command::List { common } => {
            let progress = get_progress(&common);
            let config = config(&common)?;
            cmd::list(&config, &progress)?;
        }

        Command::Delete { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::delete(&specs, &progress)?;
        }

        Command::Start { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::start(&specs, &progress)?;
        }

        Command::Reboot { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::reboot(&specs, &progress)?;
        }

        Command::Shutdown { common, specs } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &specs)?;
            cmd::shutdown(&specs, &progress)?;
        }

        Command::CloudInit {
            common,
            spec,
            dirname,
        } => {
            let progress = get_progress(&common);
            let specs = get_specs(&common, &[spec])?;
            cmd::cloud_init(&specs, &progress, &dirname)?;
        }
    }
    Ok(())
}

fn get_progress(common: &CommonOptions) -> Progress {
    if common.quiet {
        Progress::new(MessageKind::OnlyErrors)
    } else if common.verbose {
        Progress::new(MessageKind::Everything)
    } else {
        Progress::new(MessageKind::Steps)
    }
}

fn get_specs(common: &CommonOptions, specs: &[PathBuf]) -> anyhow::Result<Vec<Specification>> {
    let config = config(common)?;
    let mut all_specs = vec![];
    for spec in specs {
        let mut specs = Specification::from_file(&config, spec)?;
        all_specs.append(&mut specs);
    }
    Ok(all_specs)
}

fn config(common: &CommonOptions) -> anyhow::Result<Configuration> {
    let filename = config_filename(common);
    let config = Configuration::from_file(&filename)
        .with_context(|| format!("reading configuration file {}", filename.display()))?;
    Ok(config)
}

fn config_filename(common: &CommonOptions) -> PathBuf {
    if let Some(ref filename) = common.config {
        filename.to_path_buf()
    } else if let Some(dirs) = ProjectDirs::from(QUALIFIER, ORG, APP) {
        dirs.config_dir().join("config.yaml")
    } else {
        PathBuf::from("xxx")
    }
}
