/* Copyright (c) 2026 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "common.h"
#include "transfer/spdm_dbus.h"
#include <dbus/dbus.h>
#include <glib.h>

static DBusConnection* g_dbus_conn = NULL;

static int32_t init_dbus_env(void)
{
#define DBUS_SESSION_BUS_ADDRESS "DBUS_SESSION_BUS_ADDRESS"
#define DBUS_SESSION_BUS_PID     "DBUS_SESSION_BUS_PID"
#define DBUS_ENV_FILE_PATH       "/dev/shm/dbus/.dbus"
#define ADDRESS_IDX              1
#define PID_IDX                  2
    // DBUS环境变量已加载直接返回
    if (g_getenv(DBUS_SESSION_BUS_ADDRESS) != NULL && g_getenv(DBUS_SESSION_BUS_PID) != NULL) {
        return RET_OK;
    }

    gchar*  env_content = NULL;
    gsize   file_len    = 0;
    GError* err         = NULL;
    if (g_file_get_contents(DBUS_ENV_FILE_PATH, &env_content, &file_len, &err) == FALSE) {
        debug_log(DLOG_ERROR, "[spdm] get dbus env failed, error is %s", err->message);
        g_error_free(err);
        return RET_ERR;
    }

    GMatchInfo* match_info = NULL;
    GRegex*     regex = g_regex_new("DBUS_SESSION_BUS_ADDRESS=(.+?)\nDBUS_SESSION_BUS_PID=(\\d+)\n", G_REGEX_DEFAULT,
                                    G_REGEX_MATCH_DEFAULT, NULL);
    g_regex_match(regex, env_content, G_REGEX_MATCH_DEFAULT, &match_info);
    if (g_match_info_matches(match_info) == FALSE) {
        g_match_info_free(match_info);
        g_regex_unref(regex);
        g_free(env_content);
        return RET_ERR;
    }

    gchar* address = g_match_info_fetch(match_info, ADDRESS_IDX);
    gchar* pid     = g_match_info_fetch(match_info, PID_IDX);
    g_setenv(DBUS_SESSION_BUS_ADDRESS, address, 1);
    g_setenv(DBUS_SESSION_BUS_PID, pid, 1);
    g_free(address);
    g_free(pid);
    g_match_info_free(match_info);
    g_regex_unref(regex);
    g_free(env_content);
    return RET_OK;
}

static int32_t init_dbus_connection(void)
{
    DBusError error = {0};

    if (g_dbus_conn != NULL) {
        return RET_OK;
    }
    gint32 ret = init_dbus_env();
    if (ret != RET_OK) {
        return ret;
    }

    dbus_error_init(&error);
    g_dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &error);
    if (g_dbus_conn == NULL || dbus_error_is_set(&error)) {
        debug_log(DLOG_ERROR, "[spdm] Open dbus session failed, Error name:%s, Error message:%s.", error.name,
                  error.message);
        dbus_error_free(&error);
        return RET_ERR;
    }

    dbus_connection_set_exit_on_disconnect(g_dbus_conn, FALSE);

    return RET_OK;
}

static void append_user_context(DBusMessage* call, const gchar* user_name, const gchar* ip, const gchar* interface)
{
    typedef struct {
        const char* key;
        const char* value;
    } DBUS_STRINF_MAP;
    DBUS_STRINF_MAP map[] = {{"Interface", interface}, {"UserName", user_name}, {"ClientAddr", ip}};

    gsize i, n = sizeof(map) / sizeof(map[0]);

    DBusMessageIter iter;
    DBusMessageIter dict_iter;
    DBusMessageIter entry_iter;
    dbus_message_iter_init_append(call, &iter);
    dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
                                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
                                         DBUS_TYPE_STRING_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
                                     &dict_iter);

    for (i = 0; i < n; ++i) {
        dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
        dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &map[i].key);
        dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &map[i].value);
        dbus_message_iter_close_container(&dict_iter, &entry_iter);
    }

    dbus_message_iter_close_container(&iter, &dict_iter);
}

static int32_t dbus_call_transmit(const string& object_path, const uint8_t* request, size_t request_size,
                                  uint8_t* response, size_t* response_size)
{
    DBusMessage* call = dbus_message_new_method_call(COMPUTE_SERVICE, object_path.c_str(), HARDWARE_SPDM_INTERFACE,
                                                     SPDM_TRANSMIT_FUNCTION);
    if (call == NULL) {
        debug_log(DLOG_ERROR, "[spdm] Make new method call %s failed.", SPDM_TRANSMIT_FUNCTION);
        return RET_ERR;
    }

    append_user_context(call, "SPDM", "N/A", "Busctl");

    // 追加参数uayu
    uint32_t timeout_ms = DEFAULT_TIMEOUT_MS;
    if (dbus_message_append_args(call, DBUS_TYPE_UINT32, &request_size, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &request,
                                 request_size, DBUS_TYPE_UINT32, &timeout_ms, DBUS_TYPE_INVALID) == false) {
        dbus_message_unref(call);
        debug_log(DLOG_ERROR, "[spdm] Append message failed.");
        return RET_ERR;
    }

    // 执行调用
    uint8_t      retry_times = 0;
    DBusMessage* reply;
    DBusError    error = {0};
    dbus_error_init(&error);
    int32_t ret = RET_OK;
    while (retry_times < MAX_RETRY_TIMES) {
        retry_times++;
        reply = dbus_connection_send_with_reply_and_block(g_dbus_conn, call, DEFAULT_TIMEOUT_MS, &error);
        if (reply && !dbus_error_is_set(&error)) {
            ret = RET_OK;
            break;
        }
        debug_log(DLOG_ERROR, "[spdm] Dbus call failed: %s - %s", error.name, error.message);
        if (strcmp(error.name, TIMEOUT_ERROR_MSG) == 0) {
            dbus_error_free(&error);
            ret = RET_ERR_TIMEOUT;
            sleep(RETRY_WAIT_TIME);
            continue;
        } else if (strcmp(error.name, NOT_SUPPORT_ERROR_MSG) == 0) {
            dbus_error_free(&error);
            ret = RET_ERR_INVALID_CMD;
            break;
        }
        dbus_error_free(&error);
        ret = RET_ERR;
        break;
    }
    dbus_message_unref(call);
    if (ret != RET_OK) {
        return ret;
    }

    // 解析结果
    DBusMessageIter reply_iter;
    if (!dbus_message_iter_init(reply, &reply_iter)) {
        debug_log(DLOG_ERROR, "[spdm] reply has no arguments");
        dbus_message_unref(reply);
        return RET_ERR;
    }

    if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) {
        debug_log(DLOG_ERROR, "[spdm] reply type mismatch, expected array, got %d",
                  dbus_message_iter_get_arg_type(&reply_iter));
        dbus_message_unref(reply);
        return RET_ERR;
    }

    DBusMessageIter reply_array_iter;
    dbus_message_iter_recurse(&reply_iter, &reply_array_iter);
    size_t rsp_len = 0;
    while (dbus_message_iter_get_arg_type(&reply_array_iter) == DBUS_TYPE_BYTE) {
        uint8_t byte;
        dbus_message_iter_get_basic(&reply_array_iter, &byte);
        if (rsp_len < *response_size) {
            response[rsp_len] = byte;
        }
        rsp_len++;
        dbus_message_iter_next(&reply_array_iter);
    }
    dbus_message_unref(reply);

    if (rsp_len > *response_size) {
        debug_log(DLOG_ERROR, "[spdm] response buffer too small: %zu > %zu", rsp_len, *response_size);
        *response_size = 0;
        return RET_ERR;
    }

    *response_size = rsp_len;
    return RET_OK;
}

int32_t dbus_sync_send_recv(const string& object_path, const uint8_t* request, size_t request_size, uint8_t* response,
                            size_t* response_size)
{
    gint32 ret = init_dbus_connection();
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "[spdm] Init DBus failed, ret: %d", ret);
        return ret;
    }

    return dbus_call_transmit(object_path, request, request_size, response, response_size);
}