//
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



use std::{path::Path, rc::Rc};

use native_db::{Builder, Database};

use crate::{
    modules::{
        database::META_MODELS,
        error::{code::ErrorCode, BichonResult},
        token::{AccessTokenModel, AccessTokenModelKey, TokenType},
        users::{UserModel, DEFAULT_ADMIN_USER_ID},
        utils::encrypt::internal_encrypt_string,
    },
    raise_error,
};
use itertools::Itertools;

pub fn init_meta_database(path: impl AsRef<Path>) -> BichonResult<Rc<Database<'static>>> {
    let database = Builder::new()
        .set_cache_size(134217728)
        .create(&META_MODELS, path)
        .map_err(|e| {
            raise_error!(
                format!("Failed to open database: {:?}", e),
                ErrorCode::InternalError
            )
        })?;

    Ok(Rc::new(database))
}

pub fn find_admin(database: &Rc<Database<'static>>) -> BichonResult<Option<UserModel>> {
    let r_transaction = database
        .r_transaction()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    let entity: Option<UserModel> = r_transaction
        .get()
        .primary(DEFAULT_ADMIN_USER_ID)
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    return Ok(entity);
}

pub fn update_admin_password(
    database: &Rc<Database<'static>>,
    password: String,
    encrypt_key: &str,
) -> BichonResult<()> {
    let rw_transaction = database
        .rw_transaction()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    let entity: UserModel = rw_transaction
        .get()
        .primary(DEFAULT_ADMIN_USER_ID)
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
        .ok_or_else(|| raise_error!("admin is not found".into(), ErrorCode::InternalError))?;

    let mut updated = entity.clone();
    updated.password = Some(
        internal_encrypt_string(encrypt_key, &password)
            .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?,
    );

    rw_transaction
        .update(entity, updated)
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    rw_transaction
        .commit()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    return Ok(());
}

pub fn reset_webui_token(database: &Rc<Database<'static>>) -> BichonResult<()> {
    let rw_transaction = database
        .rw_transaction()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;

    let tokens: Vec<AccessTokenModel> = rw_transaction
        .scan()
        .secondary(AccessTokenModelKey::user_id)
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
        .start_with(DEFAULT_ADMIN_USER_ID)
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
        .try_collect()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;

    let webui_token = tokens
        .into_iter()
        .find(|t| t.token_type == TokenType::WebUI);

    let new_token = AccessTokenModel::new_webui_token(DEFAULT_ADMIN_USER_ID);
    match webui_token {
        Some(current) => {
            rw_transaction
                .remove(current)
                .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;

            rw_transaction
                .insert(new_token)
                .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
        }
        None => {
            rw_transaction
                .insert(new_token)
                .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
        }
    }

    rw_transaction
        .commit()
        .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
    Ok(())
}
