use sshca::cmd::*;
use sshca::config::{Config, ConfigBuilder, StoreHow};
use sshca::error::CAError;
use sshca::pass::{pass_load_store, pass_save_store};
use sshca::store::KeyStore;
use sshca::util::write_string;

use clap::Parser;
use log::{debug, info};
use std::path::PathBuf;

fn main() {
    if let Err(err) = try_main() {
        eprintln!("ERROR: {err}");
        let mut e: &dyn std::error::Error = &err;
        while let Some(source) = e.source() {
            eprintln!("caused by: {source}");
            e = source;
        }
        std::process::exit(1);
    }
}

fn try_main() -> Result<(), CAError> {
    env_logger::init_from_env("SSHCA_LOG");
    debug!("sshca starts");
    let mut opt = CommandLine::parse();
    let config = opt.load_config()?;
    let mut store = load_store(&config)?;
    opt.command.run(&config, &mut store)?;
    if store.is_dirty() {
        save_store(&config, &store)?;
    }
    debug!("sshca ends OK");
    Ok(())
}

fn load_store(config: &Config) -> Result<KeyStore, CAError> {
    match config.how {
        StoreHow::File => {
            if config.store.exists() {
                let yaml = std::fs::read(&config.store)
                    .map_err(|err| CAError::ReadError(config.store.clone(), err))?;
                let yaml = String::from_utf8(yaml)
                    .map_err(|err| CAError::Utf8(config.store.clone(), err))?;
                KeyStore::from_yaml(&yaml).map_err(CAError::YamlError)
            } else {
                Ok(KeyStore::new())
            }
        }
        StoreHow::Pass => pass_load_store(&config.pass_key),
    }
}

fn save_store(config: &Config, store: &KeyStore) -> Result<(), CAError> {
    info!("sshca save changed store");
    match config.how {
        StoreHow::File => write_string(&config.store, &store.to_yaml())?,
        StoreHow::Pass => pass_save_store(&config.pass_key, store)?,
    }
    Ok(())
}

#[derive(Debug, Parser)]
#[clap(author, version)]
struct CommandLine {
    #[clap(long)]
    config: Option<PathBuf>,

    #[clap(long)]
    store: Option<PathBuf>,

    #[clap(subcommand)]
    command: Command,
}

impl CommandLine {
    fn load_config(&self) -> Result<Config, CAError> {
        let mut builder = ConfigBuilder::new(&self.config)?;
        builder.store(&self.store);
        Ok(builder.build()?)
    }
}

#[derive(Debug, Parser)]
enum Command {
    Config(config::Config),
    Ca(ca::Ca),
    Host(host::Host),
    User(user::User),
}

impl Runnable for Command {
    fn run(&mut self, config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        match self {
            Self::Config(x) => x.run(config, store)?,
            Self::Ca(x) => x.run(config, store)?,
            Self::Host(x) => x.run(config, store)?,
            Self::User(x) => x.run(config, store)?,
        }
        Ok(())
    }
}
