use std::path::PathBuf;

use clap::{Parser, Subcommand};
use libduralumin::{
    passphrase_gen::{PassPhraseGenerator, Words},
    password_gen::PasswordGenerator,
};

/// program that generates random passphrases.
#[derive(Parser)]
#[clap(
    name = "duralumin",
    version = "0.2.0",
    author = "No One <noonebtw@nirgendwo.xyz>"
)]
struct Opts {
    #[clap(subcommand)]
    command: Subcommands,
}

#[derive(Subcommand)]
enum Subcommands {
    Passphrase(PassphraseGenArgs),
    Password(PasswordGenArgs),
}

#[derive(Parser)]
struct PassphraseGenArgs {
    #[clap(short = 'l', long)]
    num_words: Option<i16>,
    #[clap(long)]
    capitalize: Option<bool>,
    #[clap(long)]
    min_word_length: Option<i16>,
    #[clap(short, long)]
    count: Option<i16>,
    #[clap(
        long,
        default_value = "useapassphrase",
        long_help = "word list to use for passphrase generation, a filepath or any of `useapassphrase`, `english`"
    )]
    backend: String,
}

#[derive(Parser)]
struct PasswordGenArgs {
    /// number of passwords to generate
    #[clap(short = 'c', long = "count", default_value = "1")]
    count: u16,
    /// if given, generate a password with an exact length, otherwise choses a length between min and max length.
    #[clap(long = "length")]
    exact_length: Option<u16>,
    /// minimum length of the generated password
    #[clap(long = "min", default_value = "10")]
    min_length: u16,
    /// maximum length of the generated password
    #[clap(long = "max", default_value = "20")]
    max_length: u16,
    /// whether to use lowercase letters in the password
    #[clap(short = 'l', long)]
    use_letters: bool,
    /// whether to use uppercase letters in the password
    #[clap(short = 'L', long)]
    use_capital_letters: bool,
    /// whether to use numbers in the password
    #[clap(short = 'N', long)]
    use_numbers: bool,
    /// whether to use special symbols `(!"§$%$%&\/\\()=?_:;,.-*+#'\\)` in the password
    #[clap(short = 'S', long)]
    use_symbols: bool,
}

impl PassphraseGenArgs {
    /// gets the number of words per passphrase
    pub fn words_per_passphrase(&self) -> i16 {
        self.num_words.unwrap_or(6)
    }

    /// should the first letter of each word be capitalized?
    pub fn capitalize(&self) -> bool {
        self.capitalize.unwrap_or(false)
    }

    /// gets the number of passphrases to generate
    pub fn passphrase_count(&self) -> i16 {
        self.count.unwrap_or(5)
    }

    /// which source to use for words
    pub fn backend(&self) -> Option<Backend> {
        Backend::from_str(&self.backend)
    }
}

enum Backend {
    UseAPassPhrase,
    EnglishWords,
    File(PathBuf),
}

impl Backend {
    pub fn from_str<S>(s: S) -> Option<Self>
    where
        S: AsRef<str>,
    {
        match s.as_ref() {
            "useapassphrase" => Some(Backend::UseAPassPhrase),
            "english" => Some(Backend::EnglishWords),
            _ => {
                let path = PathBuf::from(s.as_ref());
                if path.exists() {
                    Some(Backend::File(path))
                } else {
                    None
                }
            }
        }
    }
}

fn main() {
    let opts: Opts = Opts::parse();

    match opts.command {
        Subcommands::Passphrase(opts) => {
            let gen = PassPhraseGenerator::new()
                .with_capitalized(opts.capitalize())
                .with_min_word_length(opts.min_word_length)
                .with_length(opts.words_per_passphrase());

            let words = match opts.backend() {
                Some(Backend::UseAPassPhrase) => {
                    Words::from_str(include_str!("../../useapassphrase.txt"))
                }
                Some(Backend::EnglishWords) => {
                    Words::from_str(include_str!("../../words_alpha.txt"))
                }
                Some(Backend::File(path)) => Words::from_text_file(
                    std::fs::File::open(path).expect("failed to read word list."),
                )
                .expect("word list from path."),
                None => {
                    panic!("invalid backend.")
                }
            };

            for passphrase in gen
                .generate_n(&words, opts.passphrase_count() as usize)
                .expect("failed to generate passphrases. this is bad.")
            {
                println!("{}", passphrase);
            }
        }
        Subcommands::Password(args) => {
            let min_length = args.exact_length.unwrap_or(args.min_length);
            let max_length = args.exact_length.unwrap_or(args.max_length);
            let gen = PasswordGenerator::new(
                min_length,
                max_length,
                args.use_letters,
                args.use_capital_letters,
                args.use_numbers,
                args.use_symbols,
            );

            for password in gen.generate_n(args.count as usize) {
                println!("{}", password);
            }
        }
    }
}
