use std::{convert::From, fs};

use clap::Parser;
use color_eyre::eyre::{Context, Result, eyre};

use mendia::db::{DbConn, User, connect};
use mendia::settings::DatabaseConfig;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Settings {
    pub database: DatabaseConfig,
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    #[arg(long)]
    create_user: Option<String>,

    #[arg(long)]
    config: String,
}

fn create_user(connection: &mut DbConn, user_name: &String) -> Result<()> {
    use argon2::Config;
    use rand::{Rng, distributions::Alphanumeric};

    let random_generator = rand::thread_rng();

    let password = inquire::Password::new("Enter a safe password")
        .with_display_mode(inquire::PasswordDisplayMode::Masked)
        .with_validator(inquire::validator::MinLengthValidator::new(8))
        .prompt()
        .wrap_err("Failed to inquire the user password.")?;

    let salt = random_generator
        .sample_iter(&Alphanumeric)
        .take(64)
        .map(char::from)
        .collect::<String>();

    let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &Config::original())
        .wrap_err("Failed to hash the password.")?;

    let Ok(true) = argon2::verify_encoded(&hash, password.as_bytes()) else {
        return Err(eyre!("Failed to verify the hashed password."));
    };

    let new_user = User {
        name: user_name.to_string(),
        hash,
        salt,
    };

    connection
        .replace_user(&new_user)
        .wrap_err("Failed to create/update the usertable.")?;

    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    color_eyre::install()?;

    let args = Args::parse();

    let database_config = serde_yaml::from_str::<Settings>(
        &fs::read_to_string(&args.config)
            .wrap_err(format!("The file '{}' does not exist.", args.config))?,
    )
    .wrap_err("Failed to read settings from file.")?
    .database;

    let mut connection = connect(&database_config)
        .await
        .wrap_err("Failed to connect to the database.")?;

    if let Some(user_name) = args.create_user {
        create_user(&mut connection, &user_name)?;
    }

    Ok(())
}
