//! Command for managing CA information for SSH CA tool.

use crate::cmd::Runnable;
use crate::config::Config;
use crate::error::CAError;
use crate::info::{self, Info, SafeCA, CA};
use crate::key::{KeyKind, KeyPair, SshKey};
use crate::store::KeyStore;
use clap::{Parser, ValueEnum};

/// The `ca` command.
#[derive(Debug, Parser)]
pub struct Ca {
    #[clap(subcommand)]
    cmd: Subcommand,
}

impl Runnable for Ca {
    fn run(&mut self, config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        match &mut self.cmd {
            Subcommand::New(x) => x.run(config, store)?,
            Subcommand::Show(x) => x.run(config, store)?,
            Subcommand::List(x) => x.run(config, store)?,
            Subcommand::PublicKey(x) => x.run(config, store)?,
            Subcommand::Remove(x) => x.run(config, store)?,
            Subcommand::Rename(x) => x.run(config, store)?,
        }
        Ok(())
    }
}

#[allow(missing_docs)]
#[derive(Debug, Parser)]
pub enum Subcommand {
    New(New),
    Show(Show),
    List(List),
    PublicKey(PublicKey),
    #[clap(alias = "delete")]
    Remove(Remove),
    Rename(Rename),
}

/// Create a new CA.
#[derive(Debug, Parser)]
pub struct New {
    /// Generate a key tied to a U2F hardware security token.
    #[clap(long)]
    token: bool,

    /// Kind of CA?
    kind: CaKind,

    /// Name of CA.
    name: String,
}

impl Runnable for New {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        let kind = if self.token {
            KeyKind::Ed25519Sk
        } else {
            KeyKind::Ed25519
        };
        let pair = KeyPair::generate(kind).map_err(CAError::KeyError)?;
        let ca = CA::new(&self.name, pair);
        match self.kind {
            CaKind::Host => store.insert_host_ca(ca),
            CaKind::User => store.insert_user_ca(ca),
        }
        Ok(())
    }
}

/// Possible values for `sshca ca new --kind`
#[derive(Debug, Clone, ValueEnum)]
pub enum CaKind {
    /// A CA for hosts.
    Host,

    /// A CA for users.
    User,
}

/// Show information about a single CA, as JSON.
#[derive(Debug, Parser)]
pub struct Show {
    /// Name of CA to show.
    name: String,
}

impl Show {
    fn get(&self, name: &str, store: &KeyStore) -> Option<SafeCA> {
        for ca in store.host_cas() {
            if ca.name() == name {
                return Some(SafeCA::from(&ca));
            }
        }
        for ca in store.user_cas() {
            if ca.name() == name {
                return Some(SafeCA::from(&ca));
            }
        }
        None
    }
}

impl Runnable for Show {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(ca) = self.get(&self.name, store) {
            let json = serde_json::to_string_pretty(&ca).map_err(CAError::Json)?;
            println!("{json}");
        } else {
            return Err(CAError::NoSuchCA(self.name.clone()));
        }
        Ok(())
    }
}

/// List CAs.
#[derive(Debug, Parser)]
pub struct List {
    /// Output JSON.
    #[clap(long)]
    json: bool,
}

impl Runnable for List {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        let mut list: info::List<SafeCA> = info::List::new();
        for ca in store.host_cas() {
            let ca = SafeCA::from(&ca);
            list.push(ca.clone());
        }
        for ca in store.user_cas() {
            let ca = SafeCA::from(&ca);
            list.push(ca.clone());
        }
        list.sort();
        if self.json {
            let json = serde_json::to_string_pretty(&list).map_err(CAError::Json)?;
            println!("{json}");
        } else {
            for ca in list.iter() {
                println!("{}", ca.name());
            }
        }
        Ok(())
    }
}

/// Show public key of CA.
#[derive(Debug, Parser)]
pub struct PublicKey {
    name: String,
}

impl Runnable for PublicKey {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(ca) = store.get_host_ca(&self.name) {
            print!("{}", ca.keypair().public().as_str());
        } else if let Some(ca) = store.get_user_ca(&self.name) {
            print!("{}", ca.keypair().public().as_str());
        } else {
            return Err(CAError::NoSuchHostCA(self.name.clone()));
        }
        Ok(())
    }
}

/// Rename a CA.
#[derive(Debug, Parser)]
pub struct Rename {
    /// Old name of CA
    oldname: String,

    /// New name of CA
    newname: String,
}

impl Runnable for Rename {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        store
            .rename_ca(&self.oldname, &self.newname)
            .map_err(CAError::KeyStoreError)?;
        Ok(())
    }
}

/// Remove a CA.
#[derive(Debug, Parser)]
pub struct Remove {
    name: String,
}

impl Runnable for Remove {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        store.delete_host_ca(&self.name);
        store.delete_user_ca(&self.name);
        Ok(())
    }
}
