//! # Users service

use crate::app_config::AppConfig;
use crate::connections::db_connection;
use crate::constants::{ACCOUNT_DELETE_TOKEN_DURATION, PASSWORD_RESET_TOKEN_DURATION};
use crate::models::{NewUser, User, UserID};
use crate::schema::users;
use crate::services::{families_service, login_token_service, mail_service};
use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time;
use bcrypt::DEFAULT_COST;
use diesel::prelude::*;

/// Get the information of a user, by its id
pub async fn get_by_id(id: UserID) -> anyhow::Result<User> {
    db_connection::execute(|conn| users::table.filter(users::dsl::id.eq(id.0)).first(conn))
}

/// Get the information of a user, by its mail
pub async fn get_by_mail(mail: &str) -> anyhow::Result<User> {
    db_connection::execute(|conn| users::table.filter(users::dsl::email.eq(mail)).first(conn))
}

/// Get the information of a user, by its password reset token
pub async fn get_by_pwd_reset_token(token: &str) -> anyhow::Result<User> {
    if token.is_empty() {
        return Err(anyhow::Error::from(std::io::Error::other(
            "Token is empty!",
        )));
    }

    db_connection::execute(|conn| {
        users::table
            .filter(
                users::dsl::reset_password_token.eq(token.to_string()).and(
                    users::dsl::time_gen_reset_token
                        .ge(time() as i64 - PASSWORD_RESET_TOKEN_DURATION.as_secs() as i64),
                ),
            )
            .first(conn)
    })
}

/// Get the information of a user, by its account deletion token
pub async fn get_by_account_delete_token(token: &str) -> anyhow::Result<User> {
    if token.is_empty() {
        return Err(anyhow::Error::from(std::io::Error::other(
            "Token is empty!",
        )));
    }

    db_connection::execute(|conn| {
        users::table
            .filter(
                users::dsl::delete_account_token.eq(token.to_string()).and(
                    users::dsl::time_gen_delete_account_token
                        .ge(time() as i64 - ACCOUNT_DELETE_TOKEN_DURATION.as_secs() as i64),
                ),
            )
            .first(conn)
    })
}

/// Create a new account
pub async fn create_account(name: &str, email: &str) -> anyhow::Result<User> {
    db_connection::execute(|conn| {
        let res = diesel::insert_into(users::table)
            .values(&NewUser {
                name: name.trim(),
                email: email.trim(),
                time_create: time() as i64,
            })
            .get_result(conn)?;

        Ok(res)
    })
}

/// Check if an email address is already associated with an account
pub async fn exists_email(email: &str) -> anyhow::Result<bool> {
    db_connection::execute(|conn| {
        let count: i64 = users::table
            .filter(users::email.eq(email))
            .count()
            .get_result(conn)?;

        Ok(count != 0)
    })
}

/// Request password reset
pub async fn request_reset_password(user: &mut User) -> anyhow::Result<()> {
    // If required, regenerate reset token
    if user.reset_password_token.is_none() || user.time_gen_reset_token as u64 + 3600 * 2 < time() {
        user.reset_password_token = Some(rand_str(149));
        user.time_gen_reset_token = time() as i64;

        update_account(user).await?;
    }

    // Send mail
    mail_service::send_mail(
        &user.email,
        "Réinitialisation de votre mot de passe",
        format!(
            "Bonjour, \n\n\
            Vous pouvez réinitialiser le mot de passe de votre compte à l'adresse suivante : {} \n\n\
            Ce lien est valide durant 24 heures.\n\n\
            Cordialement,\n\n\
            L'équipe de GeneIT",
            AppConfig::get()
                .get_password_reset_url(user.reset_password_token.as_deref().unwrap_or(""))
        ),
    )
    .await?;

    Ok(())
}

/// Request delete account
pub async fn request_delete_account(user: &mut User) -> anyhow::Result<()> {
    // If required, regenerate token
    if user.delete_account_token.is_none()
        || user.time_gen_delete_account_token as u64 + 3600 * 2 < time()
    {
        user.delete_account_token = Some(rand_str(149));
        user.time_gen_delete_account_token = time() as i64;

        update_account(user).await?;
    }

    // Send mail
    mail_service::send_mail(
        &user.email,
        "Suppression de votre compte",
        format!(
            "Bonjour, \n\n\
            Vous avez demandé la suppression de votre compte GeneIT. Cette opération peut être effectuée via le lien suivant : {} \n\n\
            Ce lien est valide durant 12 heures.\n\n\
            Cordialement,\n\n\
            L'équipe de GeneIT",
            AppConfig::get()
                .get_account_delete_url(user.delete_account_token.as_deref().unwrap_or(""))
        ),
    )
        .await?;

    Ok(())
}

/// Delete not validated accounts whose reset token has expired
pub async fn delete_not_validated_accounts() -> anyhow::Result<()> {
    db_connection::execute(|conn| {
        diesel::delete(
            users::dsl::users.filter(
                users::dsl::time_activate.eq(0).and(
                    users::dsl::time_gen_reset_token
                        .lt(time() as i64 - PASSWORD_RESET_TOKEN_DURATION.as_secs() as i64),
                ),
            ),
        )
        .execute(conn)?;
        Ok(())
    })
}

/// Delete account
pub async fn delete_account(user: &User) -> anyhow::Result<()> {
    log::info!("Delete account #{:?}", user.id());

    // Remove families memberships
    families_service::remove_all_user_membership(user.id()).await?;

    login_token_service::disconnect_user_from_all_devices(user.id()).await?;

    db_connection::execute(|conn| {
        diesel::delete(users::dsl::users.filter(users::dsl::id.eq(user.id().0))).execute(conn)
    })?;

    Ok(())
}

/// Mark account as validated
pub async fn validate_account(user: &mut User) -> anyhow::Result<()> {
    if user.time_activate > 0 {
        log::debug!(
            "Did not activate account {:?} because it is already activated!",
            user.id()
        );
        return Ok(());
    }

    user.time_activate = time() as i64;

    update_account(user).await?;

    mail_service::send_mail(
        &user.email,
        "Activation de votre compte GeneIT",
        "Bonjour,\n\n\
    Votre compte GeneIT a été activé avec succès !\n\n\
    Cordialement,\n\n\
    L'équipe de GeneIT",
    )
    .await?;

    Ok(())
}

/// Update account information
pub async fn update_account(user: &User) -> anyhow::Result<()> {
    db_connection::execute(|conn| {
        diesel::update(users::dsl::users.filter(users::dsl::id.eq(user.id().0)))
            .set((
                users::dsl::name.eq(user.name.clone()),
                users::dsl::admin.eq(user.admin),
                users::dsl::active.eq(user.active),
                users::dsl::email.eq(user.email.clone()),
                users::dsl::time_gen_reset_token.eq(user.time_gen_reset_token),
                users::dsl::reset_password_token.eq(user.reset_password_token.clone()),
                users::dsl::time_gen_delete_account_token.eq(user.time_gen_delete_account_token),
                users::dsl::delete_account_token.eq(user.delete_account_token.clone()),
                users::dsl::time_activate.eq(user.time_activate),
                users::dsl::password.eq(user.password.clone()),
            ))
            .execute(conn)
    })?;

    Ok(())
}

/// Change user paswsord
pub async fn change_password(user: &mut User, new_password: &str) -> anyhow::Result<()> {
    let hash = bcrypt::hash(new_password, DEFAULT_COST)?;

    user.reset_password_token = None;
    user.password = Some(hash);

    update_account(user).await?;

    Ok(())
}
