use std::io::Write;

#[macro_use]
use clap::Parser;
use libduralumin::ed25519::{generate_ed25519_keypair, randomart};
use osshkeys::{error::OsshResult, PublicParts};

/// program that generates ed25519 keypairs seeded by a passphrase and an optional ID.
#[derive(Parser)]
#[clap(
    name = "duralumin",
    version = "0.1.0",
    author = "No One <noonebtw@nirgendwo.xyz>"
)]
struct Opts {
    #[clap(short, long, default_value = "id_ed25519")]
    file: String,
}

fn fix_newline_ref(line: &mut String) {
    if line.ends_with('\n') {
        line.pop();

        if line.ends_with('\r') {
            line.pop();
        }
    }
}

#[allow(dead_code)]
fn fix_newline(mut line: String) -> String {
    fix_newline_ref(&mut line);

    line
}

fn main() -> OsshResult<()> {
    let opts = Opts::parse();
    println!("Generating ed25519 keypair:");

    print!("Enter a passphrase: ");
    std::io::stdout().flush()?;
    let passphrase = rpassword::read_password()?;
    print!("Re-enter the same passphrase: ");
    std::io::stdout().flush()?;
    let passphrase2 = rpassword::read_password()?;

    if passphrase != passphrase2 {
        println!("passphrases do not match.");
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "passphrase did not match.",
        ))?;
    }

    print!("Enter an optional ID []: ");
    std::io::stdout().flush()?;
    let id = {
        let mut id = String::new();
        std::io::stdin().read_line(&mut id)?;
        fix_newline_ref(&mut id);

        if id.is_empty() {
            None
        } else {
            Some(id)
        }
    };

    let keypair = generate_ed25519_keypair(&passphrase, id.as_ref())?;

    print!("Encrypt keypair with passphrase? [Y/n]: ");
    std::io::stdout().flush()?;

    let encrypt = {
        let mut line = String::new();
        std::io::stdin().read_line(&mut line)?;
        fix_newline_ref(&mut line);

        line.to_lowercase() == "y"
    };

    if encrypt {
        println!("Using passphrase to encrypt key..");
    }

    let fingerprint = keypair.fingerprint(osshkeys::keys::FingerprintHash::SHA256)?;

    println!(
        "Your key fingerprint is: Sha256:{}",
        base64::encode(&fingerprint)
    );

    let randomart =
        randomart::RandomArt::from_digest(&fingerprint).render("ED25519 256", "SHA256")?;

    println!("RandomArt:\n{}", randomart);

    let private_path = opts.file.clone();
    let public_path = opts.file.clone() + ".pub";

    let private_key = keypair.serialize_openssh(
        encrypt.then(|| passphrase.as_str()),
        osshkeys::cipher::Cipher::Aes256_Ctr,
    )?;
    std::fs::write(&private_path, private_key)?;

    let public_key = keypair.serialize_publickey()?;
    std::fs::write(&public_path, public_key)?;

    #[cfg(target_family = "unix")]
    use std::fs::Permissions;
    #[cfg(target_family = "unix")]
    use std::os::unix::fs::PermissionsExt;

    #[cfg(target_family = "unix")]
    std::fs::set_permissions(private_path, Permissions::from_mode(0o0600))?;
    #[cfg(target_family = "unix")]
    std::fs::set_permissions(public_path, Permissions::from_mode(0o0600))?;

    Ok(())
}
