//! # User controller
//!
//! The actions of the user on his account when he is authenticated.

use crate::constants::StaticConstraints;
use crate::controllers::HttpResult;
use crate::models::User;
use crate::services::login_token_service::LoginToken;
use crate::services::rate_limiter_service::RatedAction;
use crate::services::{rate_limiter_service, users_service};
use actix_remote_ip::RemoteIP;
use actix_web::HttpResponse;
use actix_web::web::Json;

#[derive(serde::Serialize)]
struct UserAPI<'a> {
    #[serde(flatten)]
    user: &'a User,
    has_password: bool,
}

/// Get account information
pub async fn auth_info(token: LoginToken) -> HttpResult {
    let user = users_service::get_by_id(token.user_id).await?;

    Ok(HttpResponse::Ok().json(UserAPI {
        user: &user,
        has_password: user
            .password
            .as_deref()
            .map(|p| !p.is_empty())
            .unwrap_or_default(),
    }))
}

#[derive(serde::Deserialize)]
pub struct ProfileUpdateQuery {
    name: String,
}

/// Update profile information
pub async fn update_profile(token: LoginToken, profile: Json<ProfileUpdateQuery>) -> HttpResult {
    if !StaticConstraints::default()
        .user_name_len
        .validate(&profile.name)
    {
        return Ok(HttpResponse::BadRequest().json("Nom invalide!"));
    }

    let mut user = users_service::get_by_id(token.user_id).await?;
    user.name = profile.0.name;
    users_service::update_account(&user).await?;

    Ok(HttpResponse::Accepted().finish())
}

#[derive(serde::Deserialize)]
pub struct ReplacePasswordQuery {
    old_password: String,
    new_password: String,
}

/// Replace user password
pub async fn replace_password(
    remote_ip: RemoteIP,
    token: LoginToken,
    q: Json<ReplacePasswordQuery>,
) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(
        remote_ip.0,
        RatedAction::RequestReplacePasswordSignedIn,
    )
    .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }

    if !StaticConstraints::default()
        .password_len
        .validate(&q.new_password)
    {
        return Ok(HttpResponse::BadRequest().json("Invalid new password!"));
    }

    let mut user = users_service::get_by_id(token.user_id).await?;
    if !user.check_password(&q.old_password) {
        rate_limiter_service::record_action(
            remote_ip.0,
            RatedAction::RequestReplacePasswordSignedIn,
        )
        .await?;
        return Ok(HttpResponse::Unauthorized().json("Invalid old password!"));
    }

    users_service::change_password(&mut user, &q.new_password).await?;

    Ok(HttpResponse::Accepted().finish())
}

/// Request delete account
pub async fn request_delete_account(remote_ip: RemoteIP, token: LoginToken) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::RequestDeleteAccount)
        .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }
    rate_limiter_service::record_action(remote_ip.0, RatedAction::RequestDeleteAccount).await?;

    let mut user = users_service::get_by_id(token.user_id).await?;
    users_service::request_delete_account(&mut user).await?;

    Ok(HttpResponse::Accepted().finish())
}

#[derive(serde::Deserialize)]
pub struct DeleteAccountTokenBody {
    token: String,
}

#[derive(serde::Serialize)]
struct CheckDeleteTokenResponse {
    email: String,
}

/// Check delete account token
pub async fn check_delete_token(req: Json<DeleteAccountTokenBody>) -> HttpResult {
    let user = users_service::get_by_account_delete_token(&req.token).await?;

    Ok(HttpResponse::Ok().json(CheckDeleteTokenResponse { email: user.email }))
}

/// Delete account
pub async fn delete_account(req: Json<DeleteAccountTokenBody>) -> HttpResult {
    let user = users_service::get_by_account_delete_token(&req.token).await?;

    users_service::delete_account(&user).await?;

    Ok(HttpResponse::Accepted().finish())
}
