/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/
// pub mod settings;

pub mod settings;

use crate::{
    auth::{hash_password, login},
    request_info::RequestInfo,
    routes::error::MyResult,
};
use jellycommon::{
    jellyobject::Path,
    routes::{u_account_login, u_home},
    *,
};
use jellydb::{Filter, Query};
use jellyui::components::login::{AccountLogin, AccountLogout, AccountSetPassword};
use rocket::{
    Either, FromForm,
    form::{Contextual, Form},
    get,
    http::{Cookie, CookieJar},
    post,
    response::{Flash, Redirect, content::RawHtml},
};
use serde::{Deserialize, Serialize};

#[get("/account/login")]
pub async fn r_account_login(ri: RequestInfo<'_>) -> RawHtml<String> {
    ri.respond_ui(&AccountLogin {
        ri: &ri.render_info(),
    })
}

#[get("/account/logout")]
pub fn r_account_logout(ri: RequestInfo<'_>) -> RawHtml<String> {
    ri.respond_ui(&AccountLogout {
        ri: &ri.render_info(),
    })
}

#[derive(FromForm, Serialize, Deserialize)]
pub struct LoginForm {
    #[field(validate = len(..32))]
    pub username: String,
    #[field(validate = len(..64))]
    pub password: String,
    #[field(validate = len(..64))]
    pub new_password: Option<String>,
    #[field(validate = len(..64))]
    pub display_name: Option<String>,
    #[field(default = 604800)] // one week
    pub expire: u64,
}

#[post("/account/login", data = "<form>")]
pub fn r_account_login_post(
    ri: RequestInfo<'_>,
    jar: &CookieJar,
    form: Form<Contextual<LoginForm>>,
) -> MyResult<Either<Redirect, Either<Flash<Redirect>, RawHtml<String>>>> {
    let form = match &form.value {
        Some(v) => v,
        None => {
            return Ok(Either::Right(Either::Left(Flash::error(
                Redirect::to(u_account_login()),
                format_form_error(form),
            ))));
        }
    };
    let (session, need_pw_change) = match login(&ri.state, &form.username, &form.password, None) {
        Ok(x) => x,
        Err(e) => {
            return Ok(Either::Right(Either::Left(Flash::error(
                Redirect::to(u_account_login()),
                format!("{e:#}"),
            ))));
        }
    };
    if need_pw_change {
        if let Some(new_password) = &form.new_password {
            let password_hash = hash_password(&form.username, &new_password);
            ri.state.database.transaction(&mut |txn| {
                let user_row = txn.query_single(Query {
                    filter: Filter::Match(Path(vec![USER_LOGIN.0]), form.username.clone().into()),
                    ..Default::default()
                })?;
                if let Some(ur) = user_row {
                    let mut user = txn.get(ur)?.unwrap();
                    user = user.remove(USER_PASSWORD_REQUIRE_CHANGE);
                    user = user.insert(USER_PASSWORD, &password_hash);
                    if let Some(name) = &form.display_name {
                        user = user.insert(USER_NAME, &name);
                    }
                    txn.update(ur, user)?;
                }
                Ok(())
            })?;
        } else {
            return Ok(Either::Right(Either::Right(ri.respond_ui(
                &AccountSetPassword {
                    ri: &ri.render_info(),
                    password: &form.password,
                    username: &form.username,
                },
            ))));
        }
    }

    jar.add(Cookie::build(("session", session)).permanent().build());
    Ok(Either::Left(Redirect::found(u_home())))
}

#[post("/account/logout")]
pub fn r_account_logout_post(jar: &CookieJar) -> MyResult<Flash<Redirect>> {
    jar.remove(Cookie::build("session"));
    Ok(Flash::success(
        Redirect::found(u_account_login()),
        "Logged out!",
    ))
}

pub fn format_form_error<T>(form: Form<Contextual<T>>) -> String {
    let mut k = String::from("form validation failed:");
    for e in form.context.errors() {
        k += &format!(
            "\n\t{}: {e}",
            e.name
                .as_ref()
                .map(|e| e.to_string())
                .unwrap_or("<unknown>".to_string())
        )
    }
    k
}
