#![feature(allocator_api)]

use std::{
    alloc::Global,
    ffi::{CStr, CString},
    io::{Cursor, Write, stdin},
    str::FromStr,
};

use byteorder::{BigEndian, WriteBytesExt};
use itertools::Itertools;
use password_hash::{Key, derive_encryption_key, encrypt_with};
use spectre_api_sys::SpectreAlgorithm;

macro_rules! read_line {
    ($lines:expr, $label:expr) => {{
        print!("{}: ", $label);
        std::io::stdout().flush().unwrap();
        CString::from_str(
            &$lines
                .next()
                .transpose()
                .ok()
                .flatten()
                .unwrap_or_else(|| panic!("Expected {}.", $label)),
        )
        .unwrap()
    }};
}

fn main() {
    let print_secrets = std::env::args().contains("--print-secrets");

    let mut lines = stdin().lines();
    let username = read_line!(lines, "username");
    let secret = read_line!(lines, "secret");

    let user_key: Key = unsafe {
        &*spectre_api_sys::spectre_user_key(
            username.as_ptr(),
            secret.as_ptr(),
            SpectreAlgorithm::Current,
        )
    }
    .bytes;
    let user_key_id = unsafe { spectre_api_sys::spectre_id_buf(user_key.as_ptr(), user_key.len()) };
    let salt = derive_user_key_salt(&username);
    let encryption_key = derive_encryption_key(&salt, secret.as_bytes(), Global);

    if print_secrets {
        println!("\nUser Key (SECRET):\n{:02x}", user_key.iter().format(""));
        println!(
            "\nEncryption Key (SECRET):\n{:02x}",
            encryption_key.iter().format("")
        );
    }

    let mut encrypted_user_key = user_key;

    encrypt_with(&mut encrypted_user_key, &encryption_key);
    println!(
        "\nUser Key ID:\n{:02x}",
        user_key_id.bytes.iter().format("")
    );
    println!(
        "\nEncrypted User Key:\n{:02x}",
        encrypted_user_key.iter().format("")
    );
}

// Copied from the body of the `spectre_user_key_v3` function.
fn derive_user_key_salt(username: &CStr) -> Vec<u8> {
    use spectre_api_sys::*;

    let mut salt = Cursor::new(Vec::new());

    unsafe {
        let key_scope: &'static CStr =
            CStr::from_ptr(spectre_purpose_scope(SpectreKeyPurpose::Authentication));

        salt.write_all(key_scope.to_bytes()).unwrap();
        salt.write_u32::<BigEndian>(username.count_bytes() as u32)
            .unwrap();
        salt.write_all(username.to_bytes()).unwrap();
    }

    salt.into_inner()
}
