use crate::{
    database::data::{Password, PasswordError, Username},
    SdkState,
};
use axum::{
    extract::State,
    response::{Html, Redirect},
    Form,
};
use serde::Deserialize;

use crate::{
    database::{self, DbError},
    routes,
};

use super::*;

const REGISTRATION_PAGE_HTML: &str = include_str!("../../html/register.html");
const REGISTRATION_RESULT_HTML: &str = include_str!("../../html/result.html");

routes! {
    "/account/register" get(|| async { Html(REGISTRATION_PAGE_HTML.to_string()) });
    "/account/register" post(process_register);
    "/" get(|| async { Redirect::permanent("/account/register") });
}

#[derive(Deserialize)]
struct RegisterRequest {
    pub username: String,
    pub password: String,
    pub password_v2: String,
}

async fn process_register(
    State(state): State<SdkState>,
    Form(form): Form<RegisterRequest>,
) -> Html<String> {
    let Some(username) = Username::parse(form.username) else {
        return html_result(ResultStatus::Failure, "Invalid username format; should consists of characters [A-Za-z0-9_] and be at least 6 characters long.");
    };

    let password = match Password::new(form.password, form.password_v2) {
        Ok(password) => password,
        Err(PasswordError::PairMismatch) => {
            return html_result(ResultStatus::Failure, "Passwords pair doesn't match")
        }
        Err(PasswordError::RequirementsMismatch) => {
            return html_result(
                ResultStatus::Failure,
                "Password should contain at least 8 and not more than 30 characters",
            )
        }
        Err(PasswordError::HashFailed(err)) => {
            tracing::error!("failed to hash password, err: {err}");
            return html_result(ResultStatus::Failure, "Internal server error");
        }
    };

    match database::insert_sdk_account(&state.pg_pool, username, password).await {
        Ok(_) => html_result(
            ResultStatus::Success,
            "Account successfully registered, now you can use in-game login",
        ),
        Err(DbError::SqlxError(sqlx::Error::Database(e)))
            if e.constraint() == Some("t_sdk_account_username_key") =>
        {
            html_result(
                ResultStatus::Failure,
                "Account with specified username already exists",
            )
        }
        Err(err) => {
            tracing::error!("database operation error: {err}");
            html_result(ResultStatus::Failure, "Internal server error")
        }
    }
}

enum ResultStatus {
    Failure,
    Success,
}

impl ResultStatus {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Failure => "error",
            Self::Success => "success",
        }
    }
}

fn html_result(result: ResultStatus, message: &str) -> Html<String> {
    Html(
        REGISTRATION_RESULT_HTML
            .replace("%RESULT%", result.as_str())
            .replace("%MESSAGE%", message),
    )
}
