/*
    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;

use super::error::MyError;
use crate::{
    helper::{language::AcceptLanguage, A},
    ui::{error::MyResult, home::rocket_uri_macro_r_home},
};
use anyhow::anyhow;
use jellyimport::is_importing;
use jellylogic::{account::register_user, login::login_logic, session::Session};
use jellyui::{
    account::{AccountLogin, AccountLogout, AccountRegister, AccountRegisterSuccess},
    render_page,
    scaffold::{RenderInfo, SessionInfo},
};
use rocket::{
    form::{Contextual, Form},
    get,
    http::{Cookie, CookieJar},
    post,
    response::{content::RawHtml, Redirect},
    FromForm,
};
use serde::{Deserialize, Serialize};

#[derive(FromForm)]
pub struct RegisterForm {
    #[field(validate = len(8..128))]
    pub invitation: String,
    #[field(validate = len(4..32))]
    pub username: String,
    #[field(validate = len(4..64))]
    pub password: String,
}

#[get("/account/register")]
pub async fn r_account_register(lang: AcceptLanguage) -> RawHtml<String> {
    let AcceptLanguage(lang) = lang;
    RawHtml(render_page(
        &AccountRegister { lang: &lang },
        RenderInfo {
            importing: false,
            session: None,
            lang,
        },
    ))
}

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

#[get("/account/login")]
pub fn r_account_login(session: Option<A<Session>>, lang: AcceptLanguage) -> RawHtml<String> {
    let AcceptLanguage(lang) = lang;
    let logged_in = session.is_some();
    RawHtml(render_page(
        &AccountLogin {
            lang: &lang,
            logged_in,
        },
        RenderInfo {
            session: session.map(|s| SessionInfo { user: s.0.user }),
            importing: is_importing(),
            lang,
        },
    ))
}

#[get("/account/logout")]
pub fn r_account_logout(session: Option<A<Session>>, lang: AcceptLanguage) -> RawHtml<String> {
    let AcceptLanguage(lang) = lang;
    RawHtml(render_page(
        &AccountLogout { lang: &lang },
        RenderInfo {
            session: session.map(|s| SessionInfo { user: s.0.user }),
            importing: is_importing(),
            lang,
        },
    ))
}

#[post("/account/register", data = "<form>")]
pub fn r_account_register_post<'a>(
    session: Option<A<Session>>,
    lang: AcceptLanguage,
    form: Form<Contextual<'a, RegisterForm>>,
) -> MyResult<RawHtml<String>> {
    let AcceptLanguage(lang) = lang;
    let logged_in = session.is_some();
    let form = match &form.value {
        Some(v) => v,
        None => return Err(MyError(anyhow!(format_form_error(form)))),
    };

    register_user(&form.invitation, &form.username, &form.password)?;

    Ok(RawHtml(render_page(
        &AccountRegisterSuccess {
            lang: &lang,
            logged_in,
        },
        RenderInfo {
            session: session.map(|s| SessionInfo { user: s.0.user }),
            importing: is_importing(),
            lang,
        },
    )))
}

#[post("/account/login", data = "<form>")]
pub fn r_account_login_post(
    jar: &CookieJar,
    form: Form<Contextual<LoginForm>>,
) -> MyResult<Redirect> {
    let form = match &form.value {
        Some(v) => v,
        None => return Err(MyError(anyhow!(format_form_error(form)))),
    };
    jar.add(
        Cookie::build((
            "session",
            login_logic(&form.username, &form.password, None, None)?,
        ))
        .permanent()
        .build(),
    );

    Ok(Redirect::found(rocket::uri!(r_home())))
}

#[post("/account/logout")]
pub fn r_account_logout_post(jar: &CookieJar) -> MyResult<Redirect> {
    jar.remove_private(Cookie::build("session"));
    Ok(Redirect::found(rocket::uri!(r_home())))
}

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
}
