use crate::auth::utils::hash_plain_password_with_salt;
use crate::models::{Area, Role, Token, User};
use crate::utils::{password_help, ApplicationError};
use crate::{auth, filters};
use actix_web::HttpResponse;
use rinja::Template;
use sqlx::PgPool;
use zxcvbn::{zxcvbn, Score};

pub mod delete;
pub mod get_changepassword;
pub mod get_edit;
pub mod get_login;
pub mod get_logout;
pub mod get_new;
pub mod get_overview;
pub mod get_profile;
pub mod get_register;
pub mod get_reset;
pub mod post_changepassword;
pub mod post_edit;
pub mod post_login;
pub mod post_new;
pub mod post_register;
pub mod post_reset;
pub mod post_toggle;

#[derive(Template)]
#[template(path = "user/new_or_edit.html")]
pub struct NewOrEditUserTemplate {
    user: User,
    areas: Option<Vec<Area>>,
    id: Option<i32>,
    email: Option<String>,
    name: Option<String>,
    role: Option<u8>,
    function: Option<u8>,
    area_id: Option<i32>,
}

#[derive(Template)]
#[template(path = "user/change_password.html")]
struct ResetPasswordTemplate<'a> {
    token: &'a str,
    title: &'a str,
    endpoint: &'a str,
    new_password_label: &'a str,
    retype_label: &'a str,
    submit_button_label: &'a str,
}

async fn handle_password_change_request(
    pool: &PgPool,
    token: Option<&impl Token>,
    user_id: i32,
    password: &str,
    password_retyped: &str,
    current_password: Option<&str>,
    generate_message: bool,
) -> Result<HttpResponse, ApplicationError> {
    let no_message = HttpResponse::NoContent().finish();

    if password.chars().count() > 256 {
        if generate_message {
            return Ok(
                HttpResponse::BadRequest().body(password_help::format_message(
                    "danger",
                    "Password darf nicht länger als 256 Zeichen sein.",
                )),
            );
        }

        return Ok(no_message);
    }

    // TODO: make sure this unwrap is safe
    let user = User::read_by_id(pool, user_id).await?.unwrap();
    let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect();
    let mut user_inputs = vec![user.email.as_str()];
    user_inputs.append(&mut split_names);
    let entropy = zxcvbn(password, &user_inputs);

    if entropy.score() < Score::Three {
        if generate_message {
            let message = password_help::generate_for_entropy(&entropy);
            return Ok(HttpResponse::BadRequest().body(message));
        }

        return Ok(no_message);
    }

    if generate_message {
        if entropy.score() == Score::Three {
            return Ok(HttpResponse::Ok().body(password_help::format_message(
                "success",
                "Sicheres Passwort.",
            )));
        } else {
            return Ok(HttpResponse::Ok().body(password_help::format_message(
                "success",
                "Sehr sicheres Passwort.",
            )));
        }
    }

    if let Some(current_password) = current_password {
        // unwraps are safe, as login only works with password and salt and this call site requires login
        let hash = user.password.as_ref().unwrap();
        let salt = user.salt.as_ref().unwrap();

        if hash != &hash_plain_password_with_salt(current_password, salt)? {
            return Ok(HttpResponse::BadRequest().body("Aktuelles Passwort ist falsch."));
        }
    }

    if password != password_retyped {
        return Ok(HttpResponse::BadRequest().body("Passwörter stimmen nicht überein."));
    }

    let (hash, salt) = auth::utils::generate_salt_and_hash_plain_password(password).unwrap();

    User::update(
        pool,
        user_id,
        None,
        None,
        Some(&hash),
        Some(&salt),
        None,
        None,
        None,
        None,
        None,
    )
    .await?;

    if let Some(token) = token {
        token.delete(pool).await?;
    }

    Ok(HttpResponse::Ok().body(r#"<div class="block">Registrierung abgeschlossen.</div><a class="block button is-primary" hx-boost="true" href="/login">Zum Login</a>"#))
}
