use super::{Context, Handle};
use anyhow::Result;
use clap::{ArgEnum, Parser};
use otp::{Generator, HmacSha1, HOTP, TOTP};

/// Generates a two-factor token which is valid for
/// only one login session or transaction.
#[derive(Parser, Debug)]
pub struct Otp {
    /// Specifies the token to generate.
    #[clap(long, short)]
    token: Option<String>,

    /// Specifies the key of the token stored in the store.
    #[clap(long, short, conflicts_with = "token")]
    key: Option<String>,

    /// If --clip or -c is specified, do not print the password but instead copy
    /// it to the clipboard.
    #[cfg(feature = "clipcopy")]
    #[clap(long, short)]
    clip: bool,

    /// Specifies the algorithm to hash the token.
    #[clap(long, arg_enum, default_value = "t")]
    algo: Algo,
}

#[derive(ArgEnum, Debug, Clone, PartialEq, PartialOrd)]
pub enum Algo {
    #[clap(alias = "h")]
    Hotp,

    #[clap(alias = "t")]
    Totp,
}

impl Handle for Otp {
    fn invoke(&self, ctx: &Context) -> Result<()> {
        let token = if let Some(k) = &self.key {
            String::from_utf8(ctx.decrypt_file(&k)?.unsecure_ref().to_vec())?
        } else if let Some(t) = &self.token {
            t.to_owned()
        } else {
            return Err(anyhow::anyhow!("token or key MUST specified"));
        };

        let code = match self.algo {
            Algo::Hotp => HOTP::<HmacSha1>::new()
                .with_base32_secret(token)
                .generate()?,
            Algo::Totp => TOTP::<HmacSha1>::new()
                .with_base32_secret(token)
                .generate()?,
        };

        #[cfg(feature = "clipcopy")]
        if self.clip {
            use clipboard::{ClipboardContext, ClipboardProvider};
            ClipboardContext::new()
                .and_then(|mut cp| cp.set_contents(code))
                .map_err(|e| anyhow::anyhow!("{}", e))?;
        } else {
            println!("{}", &code);
        }

        #[cfg(not(feature = "clipcopy"))]
        println!("{}", &code);

        Ok(())
    }
}
