//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2

use rand::{rngs::OsRng, RngCore};
use scrypt::{scrypt, Params};
use sea_orm::{entity::prelude::*, ActiveValue::Set, QuerySelect};
use tokio::task::spawn_blocking;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "master_pass")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub user_id: u64,
    #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")]
    pub salt: Vec<u8>,
    #[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")]
    pub password_hash: Vec<u8>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

/// Hashes the password with Scrypt with the given salt
fn hash_password(password: &[u8], salt: &[u8]) -> crate::Result<Vec<u8>> {
    let params = Params::new(14, Params::RECOMMENDED_R, Params::RECOMMENDED_P, 64)?;
    let mut password_hash = vec![0; 64];
    scrypt(password, salt, &params, &mut password_hash)?;
    Ok(password_hash)
}

impl ActiveModel {
    /// Hashes the password and creates an ActiveModel with all fields set to Set variant
    pub fn from_unencrypted(user_id: u64, password: &str) -> crate::Result<Self> {
        let mut salt = vec![0; 64];
        OsRng.fill_bytes(&mut salt);
        let password_hash = Set(hash_password(password.as_ref(), &salt)?);
        Ok(Self {
            user_id: Set(user_id),
            salt: Set(salt),
            password_hash,
        })
    }
}

impl Entity {
    /// Verifies the provided master password against the one from DB
    pub async fn verify_master_pass(
        user_id: u64,
        master_pass: String,
        db: &DatabaseConnection,
    ) -> crate::Result<Option<bool>> {
        let model = match Self::find_by_id(user_id).one(db).await? {
            Some(model) => model,
            None => return Ok(None),
        };
        let salt = model.salt;
        let password_hash =
            spawn_blocking(move || hash_password(master_pass.as_bytes(), &salt)).await??;
        Ok(Some(password_hash == model.password_hash))
    }

    /// Checks if the master password for the user exists
    pub async fn exists(user_id: u64, db: &DatabaseConnection) -> crate::Result<bool> {
        let id = Self::find()
            .select_only()
            .column(Column::UserId)
            .filter(Column::UserId.eq(user_id))
            .into_tuple::<u64>()
            .one(db)
            .await?;
        Ok(id.is_some())
    }
}