//! Commands to manage information about users for SSH CA tool.

use crate::cert::{parse_validity, CertificateAuthority, UserCa};
use crate::cmd::Runnable;
use crate::config::Config;
use crate::error::CAError;
use crate::info::{self, Info, SafeUser};
use crate::key::{PublicKey, SshKey};
use crate::store::{KeyStore, KeyStoreError};
use crate::util::read_as_string;

use clap::{ArgAction, Parser};
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

const DEFAULT_VALIDITY: &str = "90d";

/// Manage information bout users.
#[derive(Debug, Parser)]
pub struct User {
    #[clap(subcommand)]
    cmd: Subcommand,
}

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

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

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

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

/// List users.
#[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<SafeUser> = info::List::new();
        for user in store.users() {
            let user = SafeUser::from(user);
            list.push(user.clone());
        }
        list.sort();
        if self.json {
            let json = serde_json::to_string_pretty(&list).map_err(CAError::Json)?;
            println!("{json}");
        } else {
            for user in list.iter() {
                println!("{}", user.name());
            }
        }
        Ok(())
    }
}

/// Add new user and their public key.
#[derive(Debug, Parser)]
pub struct New {
    username: String,
    keyfile: PathBuf,

    /// All the principals for this user (i.e., usernames on target
    /// host). Defaults to USERNAME if none given.
    #[clap(short, long = "principal", action = ArgAction::Append)]
    principals: Vec<String>,
}

impl Runnable for New {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        let key = read_as_string(&self.keyfile)?;
        let key = PublicKey::new(key);
        let principals = if self.principals.is_empty() {
            vec![self.username.clone()]
        } else {
            self.principals.to_vec()
        };
        let user = info::User::new(key, self.username.clone(), principals);
        store.insert_user(user).map_err(CAError::KeyStoreError)?;
        Ok(())
    }
}

/// Certify a user.
#[derive(Debug, Parser)]
pub struct Certify {
    /// Write output to this file. If --all is used, to a file in this directory.
    #[clap(long)]
    output: Option<PathBuf>,

    /// Set validity period.
    ///
    /// Default unit is day. Suffix 'w', 'd', 'h', or 'm' can specify
    /// weeks, days, hours, or minutes.
    #[clap(long, default_value = DEFAULT_VALIDITY)]
    expires_in: String,

    /// CA to use to certify.
    #[clap(long)]
    ca: String,

    /// User to certify, unless --all is used.
    username: Option<String>,

    /// Certify all users?
    #[clap(long)]
    all: bool,
}

impl Certify {
    fn certify(
        &self,
        username: &str,
        store: &KeyStore,
        output: &Option<PathBuf>,
    ) -> Result<(), CAError> {
        let ca = if let Some(pair) = store.get_user_ca(&self.ca) {
            pair
        } else {
            return Err(CAError::NoSuchUserCA(self.ca.clone()));
        };

        let user = if let Some(user) = store.get_user(username) {
            user
        } else {
            return Err(CAError::NoSuchUser(username.into()));
        };

        let key = user.public();
        let ca = UserCa::new(self.ca.clone(), ca.keypair().clone());
        let valid_for = parse_validity(&self.expires_in)?;
        let cert = ca.certify(key, &valid_for, user.principals())?;

        if let Some(filename) = output {
            let mut file =
                File::create(filename).map_err(|e| CAError::Create(filename.into(), e))?;
            write!(file, "{cert}").map_err(|e| CAError::Write(filename.into(), e))?;
        } else {
            print!("{cert}")
        };

        Ok(())
    }
}

impl Runnable for Certify {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if self.all {
            let dirname = if let Some(output) = &self.output {
                output.clone()
            } else {
                PathBuf::from(".")
            };
            for user in store.users() {
                println!("Certifying user {}", user.name());
                let filename = dirname.join(format!("{}-cert.pub", user.name()));
                self.certify(user.name(), store, &Some(filename))?;
            }
        } else if let Some(user) = self.username.as_ref() {
            self.certify(user, store, &self.output)?;
        } else {
            return Err(CAError::UserOrAll);
        };

        Ok(())
    }
}

/// Rename user.
#[derive(Debug, Parser)]
pub struct Rename {
    /// Old username.
    oldname: String,

    /// New username.
    newname: String,
}

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

/// Remove user.
#[derive(Debug, Parser)]
pub struct Remove {
    username: String,
}

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

/// Show user's stored public key.
#[derive(Debug, Parser)]
pub struct PublicKeyCmd {
    username: String,
}

impl Runnable for PublicKeyCmd {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(pair) = store.get_user(&self.username) {
            print!("{}", pair.public().as_str());
        } else {
            return Err(CAError::NoSuchUser(self.username.clone()));
        }
        Ok(())
    }
}

/// Manage a user's principals.
#[derive(Debug, Parser)]
pub struct Principals {
    #[clap(subcommand)]
    command: PrincipalsCommand,
}

impl Runnable for Principals {
    fn run(&mut self, config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        match &mut self.command {
            PrincipalsCommand::Add(x) => x.run(config, store)?,
            PrincipalsCommand::List(x) => x.run(config, store)?,
            PrincipalsCommand::Remove(x) => x.run(config, store)?,
            PrincipalsCommand::Set(x) => x.run(config, store)?,
        }
        Ok(())
    }
}

#[allow(missing_docs)]
#[derive(Debug, Parser)]
enum PrincipalsCommand {
    List(ListPrincipals),
    Set(SetPrincipals),
    Add(AddPrincipals),
    Remove(RemovePrincipals),
}

/// Set a user's principals.
#[derive(Debug, Parser)]
pub struct ListPrincipals {
    /// Which user to operate on?
    user: String,
}

impl Runnable for ListPrincipals {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(user) = store.get_user(&self.user) {
            for p in user.principals() {
                println!("{p}");
            }
            Ok(())
        } else {
            Err(CAError::KeyStoreError(KeyStoreError::UnknownUser(
                self.user.clone(),
            )))
        }
    }
}

/// Set a user's principals.
#[derive(Debug, Parser)]
pub struct SetPrincipals {
    /// Which user to operate on?
    #[clap(short, long)]
    user: String,

    /// A principal for the user.
    #[clap(value_name = "PRINCIPAL")]
    principals: Vec<String>,
}

impl Runnable for SetPrincipals {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(user) = store.get_user_mut(&self.user) {
            user.set_principals(&self.principals);
            Ok(())
        } else {
            Err(CAError::KeyStoreError(KeyStoreError::UnknownUser(
                self.user.clone(),
            )))
        }
    }
}

/// Add to a user's principals.
#[derive(Debug, Parser)]
pub struct AddPrincipals {
    /// Which user to operate on?
    #[clap(short, long)]
    user: String,

    /// A principal for the user.
    #[clap(value_name = "PRINCIPAL")]
    principals: Vec<String>,
}

impl Runnable for AddPrincipals {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(user) = store.get_user_mut(&self.user) {
            user.add_principals(&self.principals);
            assert!(store.is_dirty());
            Ok(())
        } else {
            Err(CAError::KeyStoreError(KeyStoreError::UnknownUser(
                self.user.clone(),
            )))
        }
    }
}

/// Remove from a user's principals.
#[derive(Debug, Parser)]
pub struct RemovePrincipals {
    /// Which user to operate on?
    #[clap(short, long)]
    user: String,

    /// A principal for the user.
    #[clap(value_name = "PRINCIPAL")]
    principals: Vec<String>,
}

impl Runnable for RemovePrincipals {
    fn run(&mut self, _config: &Config, store: &mut KeyStore) -> Result<(), CAError> {
        if let Some(user) = store.get_user_mut(&self.user) {
            user.remove_principals(&self.principals);
            Ok(())
        } else {
            Err(CAError::KeyStoreError(KeyStoreError::UnknownUser(
                self.user.clone(),
            )))
        }
    }
}
