Open source Nest implementation
use rocket::{
    form::{Context, Form},
    http::{Cookie, CookieJar},
    response::{Flash, Redirect},
    Route, State,
};
use rocket_dyn_templates::Template;

use crate::database::Database;
use crate::models::users::User;

// TODO decide if this controller file is the place for this guard to be at?
// Feels like a constructor for a model, thus needs moving

#[get("/users/new", rank = 1)]
fn new_already_user(_user: User) -> Redirect {
    Redirect::to("/")
}

#[get("/users/new", rank = 2)]
fn new() -> Template {
    Template::render("users/new", &Context::default())
}

#[derive(FromForm)]
struct NewUser {
    // TODO validate against regexp: [a-z_][a-z0-9_-]*[$] - Needs a valid linux username
    #[field(validate = len(..20))]
    pub user_name: String,
    #[field(validate = len(3..))]
    pub email: String,
    #[field(validate = len(6..64))]
    pub password: String,
}

#[post("/users", data = "<user>")]
async fn create(
    db: &State<Database>,
    cookie: &CookieJar<'_>,
    user: Form<NewUser>,
) -> Result<Flash<Redirect>, Flash<Redirect>> {
    match User::create(
        db,
        user.user_name.clone(),
        user.password.clone(),
        user.email.clone(),
    )
    .await
    {
        Ok(new_user) => {
            set_user_cookie(cookie, new_user);
            return Ok(Flash::success(Redirect::to("/"), "Signed up succesfully"));
        }
        Err(_e) => Err(Flash::error(
            Redirect::to("/users/new"),
            "Something went wrong",
        )), //TODO Show the error to the user in a flash message,
    }
}

#[derive(FromForm)]
struct SignIn {
    #[field(validate = len(..20))]
    pub user_name: String,
    #[field(validate = len(6..64))]
    pub password: String,
}

#[get("/users/sign_in")]
async fn get_sign_in() -> Template {
    Template::render("users/sign_in", &Context::default())
}

#[post("/users/sign_in", data = "<user>")]
async fn sign_in(
    db: &State<Database>,
    jar: &CookieJar<'_>,
    user: Form<SignIn>,
) -> Result<Flash<Redirect>, Flash<Redirect>> {
    // TODO figure out Rust and rewrite this without the nested matching
    match User::authenticate(db, user.user_name.clone(), user.password.clone()).await {
        Ok(u) => match u {
            Some(u2) => {
                set_user_cookie(jar, u2);
                return Ok(Flash::success(Redirect::to("/"), "Signed in!"));
            }
            None => return Err(Flash::error(Redirect::to("./sign_in"), "Error signing in")),
        },
        Err(_e) => {
            return Err(Flash::error(
                Redirect::to("./sign_in"),
                "SQL Error signing in",
            ))
        }
    }
}

static COOKIE_USER_ID_KEY: &str = "user_id";

#[get("/users/sign_out")]
fn sign_out(jar: &CookieJar<'_>) -> Flash<Redirect> {
    jar.remove_private(Cookie::named(COOKIE_USER_ID_KEY));

    Flash::success(Redirect::to("/"), "Signed out succesfully")
}

fn set_user_cookie(jar: &CookieJar<'_>, user: User) {
    jar.add_private(Cookie::new(COOKIE_USER_ID_KEY, user.id.to_string()))
}

pub fn routes() -> Vec<Route> {
    routes![
        new,
        new_already_user,
        create,
        get_sign_in,
        sign_in,
        sign_out
    ]
}