/*
    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>
*/
use super::format_form_error;
use crate::{
    auth::hash_password, request_info::RequestInfo, ui::error::MyResult, ui_responder::UiResponse,
};
use anyhow::anyhow;
use jellycommon::{
    MESSAGE_KIND, MESSAGE_TEXT, USER_LOGIN, USER_NAME, USER_PASSWORD, VIEW_MESSAGE,
    VIEW_USER_SETTINGS,
    jellyobject::{Object, ObjectBuffer, ObjectBufferBuilder, Path},
    routes::u_account_settings,
};
use jellydb::{Filter, Query, Sort};
use jellyui::tr;
use rocket::{
    FromForm,
    form::{self, Contextual, Form, validate::len},
    get, post,
    request::FlashMessage,
    response::{Flash, Redirect},
};
use std::ops::Range;

#[derive(FromForm)]
pub struct SettingsForm {
    #[field(validate = option_len(4..64))]
    password: Option<String>,
    #[field(validate = option_len(4..32))]
    name: Option<String>,
}

fn option_len<'v>(value: &Option<String>, range: Range<usize>) -> form::Result<'v, ()> {
    value.as_ref().map(|v| len(v, range)).unwrap_or(Ok(()))
}

#[get("/account/settings")]
pub fn r_account_settings(ri: RequestInfo, flash: Option<FlashMessage>) -> MyResult<UiResponse> {
    let user = ri.require_user()?;
    let mut view = ObjectBufferBuilder::default();
    view.push(VIEW_USER_SETTINGS, user);
    if let Some(flash) = flash {
        view.push(
            VIEW_MESSAGE,
            ObjectBuffer::new(&mut [
                (MESSAGE_KIND.0, &flash.kind()),
                (MESSAGE_TEXT.0, &flash.message()),
            ])
            .as_object(),
        );
    }
    Ok(ri.respond_ui(view.finish()))
}

#[post("/account/settings", data = "<form>")]
pub fn r_account_settings_post(
    ri: RequestInfo,
    form: Form<Contextual<SettingsForm>>,
) -> MyResult<Flash<Redirect>> {
    let form = match &form.value {
        Some(v) => v,
        None => {
            return Ok(Flash::error(
                Redirect::to(u_account_settings()),
                format_form_error(form),
            ));
        }
    };

    let mut out = String::new();

    if let Some(password) = &form.password {
        let login = ri
            .require_user()?
            .get(USER_LOGIN)
            .ok_or(anyhow!("user has no login"))?;
        let password = hash_password(login, password);
        update_user(&ri, |user| user.insert(USER_PASSWORD, &password))?;
        out += &*tr(ri.lang, "settings.account.password.changed");
        out += "\n";
    }
    if let Some(name) = &form.name {
        update_user(&ri, |user| user.insert(USER_NAME, name))?;
        out += &*tr(ri.lang, "settings.account.display_name.changed");
        out += "\n";
    }
    // if let Some(theme) = form.theme {
    //     update_user_theme(&ri.session, theme.0)?;
    //     out += &*tr(ri.lang, "settings.account.theme.changed");
    //     out += "\n";
    // }
    // if let Some(player_preference) = form.player_preference {
    //     update_user_player_preference(&ri.session, player_preference.0)?;
    //     out += &*tr(ri.lang, "settings.player_preference.changed");
    //     out += "\n";
    // }
    // if let Some(native_secret) = &form.native_secret {
    //     update_user_native_secret(&ri.session, native_secret)?;
    //     out += &*tr(ri.lang, "settings.native_secret.changed");
    //     out += "\n";
    // }
    let out = if out.is_empty() {
        tr(ri.lang, "settings.no_change").to_string()
    } else {
        out
    };

    Ok(Flash::success(Redirect::to(u_account_settings()), out))
}

fn update_user(ri: &RequestInfo, update: impl Fn(Object) -> ObjectBuffer) -> MyResult<()> {
    let login = ri
        .require_user()?
        .get(USER_LOGIN)
        .ok_or(anyhow!("user has no login"))?;
    ri.state.database.transaction(&mut |txn| {
        let user_row = txn
            .query_single(Query {
                filter: Filter::Match(Path(vec![USER_LOGIN.0]), login.into()),
                sort: Sort::None,
            })?
            .ok_or(anyhow!("user vanished"))?;

        let user = txn.get(user_row)?.unwrap();
        let new_user = update(user.as_object());
        txn.update(user_row, new_user)?;

        Ok(())
    })?;
    Ok(())
}
