use std::process::ExitCode;

use clap::Parser;
use crypt::{
    rsa::RsaKeyOptions, AsymmetricDecryptOptions, AsymmetricEncryptOptions, AsymmetricKeyOptions,
    Error,
};
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};

#[derive(Parser, Debug)]
struct Args {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Debug, clap::Subcommand)]
enum Command {
    #[clap(subcommand, about = "Generate a key/keypair")]
    Generate(GenerateCommand),
    #[clap(subcommand, about = "Encrypt data using a public key")]
    Encrypt(EncryptCommand),
    #[clap(subcommand, about = "Decrypt data using a private key")]
    Decrypt(DecryptCommand),
}

#[derive(Debug, clap::Subcommand)]
enum GenerateCommand {
    Rsa {
        #[clap(short = 'b', long, help = "Key bitness")]
        bits: usize,
        #[clap(flatten)]
        options: AsymmetricKeyOptions,
        #[clap(flatten)]
        rsa_options: RsaKeyOptions,
    },
    Ed25519 {
        #[clap(flatten)]
        options: AsymmetricKeyOptions,
    },
}

#[derive(Debug, clap::Subcommand)]
enum EncryptCommand {
    Rsa {
        #[clap(flatten)]
        options: AsymmetricEncryptOptions,
    },
}

#[derive(Debug, clap::Subcommand)]
enum DecryptCommand {
    Rsa {
        #[clap(flatten)]
        options: AsymmetricDecryptOptions,
    },
}

fn generate_ed25519(options: &AsymmetricKeyOptions) -> Result<(), Error> {
    let mut rng = rand::thread_rng();
    let signing_key = ed25519_dalek::SigningKey::generate(&mut rng);

    println!("Generating a new ed25519 keypair");
    signing_key.write_pkcs8_pem_file(&options.private_key, LineEnding::LF)?;
    println!("Done, deriving a verifying key");

    if let Some(public_key) = options.public_key.as_ref() {
        signing_key
            .verifying_key()
            .write_public_key_pem_file(public_key, LineEnding::LF)?;
    } else {
        let key_string = signing_key
            .verifying_key()
            .to_public_key_pem(LineEnding::LF)?;
        println!("Public key:");
        println!("{key_string}");
    }
    println!("Done!");
    Ok(())
}

fn generate(command: GenerateCommand) -> Result<(), Error> {
    match command {
        GenerateCommand::Rsa {
            options,
            rsa_options,
            bits,
        } => crypt::rsa::generate_rsa(&options, &rsa_options, bits),
        GenerateCommand::Ed25519 { options } => generate_ed25519(&options),
    }
}

fn encrypt(command: EncryptCommand) -> Result<(), Error> {
    match command {
        EncryptCommand::Rsa { options } => crypt::rsa::encrypt_rsa(&options),
    }
}

fn decrypt(command: DecryptCommand) -> Result<(), Error> {
    match command {
        DecryptCommand::Rsa { options } => crypt::rsa::decrypt_rsa(&options),
    }
}

fn run(command: Command) -> Result<(), Error> {
    match command {
        Command::Generate(command) => generate(command),
        Command::Encrypt(command) => encrypt(command),
        Command::Decrypt(command) => decrypt(command),
    }
}

fn main() -> ExitCode {
    let args = Args::parse();

    match run(args.command) {
        Ok(_) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("Error: {error}");
            ExitCode::FAILURE
        }
    }
}
