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

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

/// Create a new account
pub async fn create_account(remote_ip: RemoteIP, req: web::Json<CreateAccountBody>) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::CreateAccount).await? {
        return Ok(HttpResponse::TooManyRequests().finish());
    }

    // Check if email is valid
    if !mailchecker::is_valid(&req.email) {
        return Ok(HttpResponse::BadRequest().json("Email address is invalid!"));
    }

    // Check parameters
    let constraints = StaticConstraints::default();
    if !constraints.user_name_len.validate(&req.name) || !constraints.mail_len.validate(&req.email)
    {
        return Ok(HttpResponse::BadRequest().json("Size constraints were not respected!"));
    }

    rate_limiter_service::record_action(remote_ip.0, RatedAction::CreateAccount).await?;

    // Perform cleanup
    users_service::delete_not_validated_accounts().await?;

    // Check if email is already attached to an account
    if users_service::exists_email(&req.email).await? {
        return Ok(
            HttpResponse::Conflict().json("An account with the same email address already exists!")
        );
    }

    // Create the account
    let mut user = users_service::create_account(&req.name, &req.email).await?;

    // Trigger reset password (send mail)
    users_service::request_reset_password(&mut user).await?;

    // Account successfully created
    Ok(HttpResponse::Created().finish())
}

#[derive(serde::Deserialize)]
pub struct RequestResetPasswordBody {
    mail: String,
}

/// Request the creation of a new password reset link
pub async fn request_reset_password(
    remote_ip: RemoteIP,
    req: web::Json<RequestResetPasswordBody>,
) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(
        remote_ip.0,
        RatedAction::RequestNewPasswordResetLink,
    )
    .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }
    rate_limiter_service::record_action(remote_ip.0, RatedAction::RequestNewPasswordResetLink)
        .await?;

    match users_service::get_by_mail(&req.mail).await {
        Ok(mut user) => users_service::request_reset_password(&mut user).await?,
        Err(e) => {
            log::error!("Could not locate user account {e}! (error silently ignored)");
        }
    }

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

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

#[derive(serde::Serialize)]
pub struct CheckResetPasswordTokenResponse {
    name: String,
}

/// Check reset password token
pub async fn check_reset_password_token(
    remote_ip: RemoteIP,
    req: web::Json<CheckResetPasswordTokenBody>,
) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(
        remote_ip.0,
        RatedAction::CheckResetPasswordTokenFailed,
    )
    .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }

    let user = match users_service::get_by_pwd_reset_token(&req.token).await {
        Ok(t) => t,
        Err(e) => {
            rate_limiter_service::record_action(
                remote_ip.0,
                RatedAction::CheckResetPasswordTokenFailed,
            )
            .await?;
            log::error!("Password reset token could not be used: {e}");
            return Ok(HttpResponse::NotFound().finish());
        }
    };

    Ok(HttpResponse::Ok().json(CheckResetPasswordTokenResponse { name: user.name }))
}

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

/// Reset password
pub async fn reset_password(remote_ip: RemoteIP, req: web::Json<ResetPasswordBody>) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(
        remote_ip.0,
        RatedAction::CheckResetPasswordTokenFailed,
    )
    .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }

    let mut user = match users_service::get_by_pwd_reset_token(&req.token).await {
        Ok(t) => t,
        Err(e) => {
            rate_limiter_service::record_action(
                remote_ip.0,
                RatedAction::CheckResetPasswordTokenFailed,
            )
            .await?;
            log::error!("Password reset token could not be used: {e}");
            return Ok(HttpResponse::NotFound().finish());
        }
    };

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

    // Validate account, if required
    users_service::validate_account(&mut user).await?;

    // Change user password
    users_service::change_password(&mut user, &req.password).await?;

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

#[derive(serde::Deserialize)]
pub struct PasswordLoginQuery {
    mail: String,
    password: String,
}

/// Handle login with password
pub async fn password_login(remote_ip: RemoteIP, req: web::Json<PasswordLoginQuery>) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::FailedPasswordLogin)
        .await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }

    // Get user account
    let user = match users_service::get_by_mail(&req.mail).await {
        Ok(u) => u,
        Err(e) => {
            log::error!("Auth failed: could not find account by mail! {e}");
            rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin)
                .await?;
            return Ok(HttpResponse::Unauthorized().json("Invalid credentials"));
        }
    };

    if !user.check_password(&req.password) {
        log::error!("Auth failed: invalid password for mail {}", user.email);
        rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin).await?;
        return Ok(HttpResponse::Unauthorized().json("Invalid credentials"));
    }

    finish_login(&user).await
}

#[derive(serde::Serialize)]
struct LoginResponse {
    user_id: UserID,
    token: String,
}

async fn finish_login(user: &User) -> HttpResult {
    if !user.active {
        log::error!("Auth failed: account for mail {} is disabled!", user.email);
        return Ok(HttpResponse::ExpectationFailed().json("This account is disabled!"));
    }

    Ok(HttpResponse::Ok().json(LoginResponse {
        user_id: user.id(),
        token: login_token_service::gen_new_token(user).await?,
    }))
}

#[derive(serde::Deserialize)]
pub struct StartOpenIDLoginQuery {
    provider: String,
}

#[derive(serde::Serialize)]
pub struct StartOpenIDLoginResponse {
    url: String,
}

/// Start OpenID login
pub async fn start_openid_login(
    remote_ip: RemoteIP,
    req: web::Json<StartOpenIDLoginQuery>,
) -> HttpResult {
    // Rate limiting
    if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::StartOpenIDLogin).await?
    {
        return Ok(HttpResponse::TooManyRequests().finish());
    }
    rate_limiter_service::record_action(remote_ip.0, RatedAction::StartOpenIDLogin).await?;

    let url = openid_service::start_login(&req.provider, remote_ip.0).await?;

    Ok(HttpResponse::Ok().json(StartOpenIDLoginResponse { url }))
}

#[derive(serde::Deserialize)]
pub struct FinishOpenIDLoginQuery {
    code: String,
    state: String,
}

/// Finish OpenID login
pub async fn finish_openid_login(
    remote_ip: RemoteIP,
    req: web::Json<FinishOpenIDLoginQuery>,
) -> HttpResult {
    let user_info = openid_service::finish_login(remote_ip.0, &req.code, &req.state).await?;

    if user_info.email_verified != Some(true) {
        log::error!("Email is not verified!");
        return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!"));
    }

    let mail = match user_info.email {
        Some(m) => m,
        None => {
            return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!"));
        }
    };

    // Create the account, if required
    if !users_service::exists_email(&mail).await? {
        let name = match (user_info.name, user_info.given_name, user_info.family_name) {
            (Some(name), _, _) => name,
            (None, Some(g), Some(f)) => format!("{g} {f}"),
            (_, _, _) => {
                return Ok(HttpResponse::Unauthorized().json("Name unspecified by the IDP!"));
            }
        };

        users_service::create_account(&name, &mail).await?;
    }

    let mut user = users_service::get_by_mail(&mail).await?;

    // OpenID auth is enough to validate accounts
    users_service::validate_account(&mut user).await?;

    finish_login(&user).await
}

/// Logout user
pub async fn logout(token: LoginTokenValue) -> HttpResult {
    login_token_service::delete_token(&token).await?;

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