/*
 * Copyright (c) KylinSoft Co., Ltd. 2024. All rights reserved.
 *
 * kaiming is licensed under the GPL v2.0+.
 *
 * See the LICENSE file for more details.
 */
/**
 * @brief 开明穿透系统代理
 */

#include "common/KMCommonUtils.h"
#include "dbus/kmsessionproxy.h"

// D-Bus 名称和路径定义
#define KAIMING_DBUS_SERVICE "org.kaiming.proxy"
#define KAIMING_DBUS_PATH "/org/kaiming/proxy"
#define KAIMING_DBUS_INTERFACE "org.kaiming.proxy.session"

struct StartBinaryTask
{
    ProxySession *conn;
    int pid;
    std::string command;
    std::string pwd;
};

static gboolean on_handle_xdg_open(ProxySession *object, GDBusMethodInvocation *invocation, const gchar *path, const gchar *pwd, const gchar *appId, const gchar *appVersion, gpointer user_data)
{
    KMInfo(std::string("xdg-open path:") + path + " pwd:" + pwd);

    GError *error = nullptr;
    g_autofree gchar *pLocalPath = nullptr;

    if (pwd != nullptr)
    {
        if (chdir(pwd) != 0)
        {
            KMInfo(std::string("chdir failed: ") + strerror(errno));
        }
    }

    // 处理 http/https，直接用 xdg-open
    if (g_str_has_prefix(path, "http://") || g_str_has_prefix(path, "https://"))
    {
        g_autofree gchar *command = g_strdup_printf("xdg-open \"%s\"", path);
        if (!g_spawn_command_line_async(command, &error))
        {
            gchar *error_message = g_strdup_printf("Failed to open URL using xdg-open: %s", error->message ? error->message : "Unknown error");
            g_dbus_method_invocation_return_dbus_error(invocation, "org.kaiming.proxy.Error.Failed", error_message);
            KMError(error_message);
            g_free(error_message);
            g_error_free(error);
            return false;
        }

        proxy_session_complete_xdg_open(object, invocation);
        kmlogger.info("Opened URL via xdg-open: %s", path);
        return true;
    }
    else if (g_str_has_prefix(path, "file://"))
    {
        // 转换 file:// URI
        pLocalPath = g_filename_from_uri(path, nullptr, nullptr);
        if (pLocalPath == nullptr)
        {
            g_dbus_method_invocation_return_dbus_error(invocation, "org.kaiming.proxy.Error.Failed", "Invalid file:// URI.");
            KMError("Invalid file:// URI");
            return false;
        }

        std::string_view localPathView(pLocalPath);
        if (localPathView.size() > 1 && *(pLocalPath + 1) == '/')
        {
            pLocalPath++;
        }
    }
    else
    {
        pLocalPath = g_strdup(path);
    }

    // 只有 file:// 或无前缀路径，且路径不存在时，尝试拼装完整路径
    if (!fs::exists(std::string(pLocalPath)))
    {
        std::string layerPath = "/opt/kaiming/layers/stable";
        std::string arch = getSingleSubdirectory(layerPath);
        if (!arch.empty())
        {
            // 拼接完整路径（注意加斜杠分隔）
            std::string fullPath = layerPath + "/" + arch + "/app/" + appId + "/binary/" + appVersion + "/files" + std::string(pLocalPath);

            if (fs::exists(fullPath))
            {
                g_clear_pointer(&pLocalPath, g_free);
                pLocalPath = g_strdup(fullPath.c_str());
            }
        }
    }

    // 用最终路径执行 xdg-open
    g_autofree gchar *command = g_strdup_printf("xdg-open \"%s\"", pLocalPath);
    if (!g_spawn_command_line_async(command, &error))
    {
        gchar *error_message = g_strdup_printf("Failed to open file/path using xdg-open: %s", error->message ? error->message : "Unknown error");
        g_dbus_method_invocation_return_dbus_error(invocation, "org.kaiming.proxy.Error.Failed", error_message);
        KMError(error_message);
        g_free(error_message);
        g_error_free(error);
        return false;
    }

    proxy_session_complete_xdg_open(object, invocation);
    kmlogger.info("Opened file/path via xdg-open: %s", pLocalPath);
    return true;
}

static void taskReply(void *taskData, const char *reply)
{
    kmlogger.info("reply: %s", reply);
    auto *data = static_cast<StartBinaryTask *>(taskData);
    proxy_session_emit_binary_reply(data->conn, data->pid, reply);
}

static void onStartBinaryAsync(GTask *task, gpointer, gpointer taskData, GCancellable *)
{
    auto *data = static_cast<StartBinaryTask *>(taskData);
    kmlogger.info("[DBus] session pid: %d, command: %s", data->pid, data->command.c_str());
    runGtaskCommand(task, data->command, data->pwd, taskReply, taskData);
    taskReply(taskData, "start binary done");
}

static void onStartBinaryDone(GObject *, GAsyncResult *res, gpointer userData)
{
    GError *error = nullptr;
    GTask *task = G_TASK(res);

    GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION(g_task_get_source_object(task));
    gchar *result = static_cast<gchar *>(g_task_propagate_pointer(task, &error));

    if (error)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
    }
    else
    {
        proxy_session_complete_start_binary_as_user(PROXY_SESSION(userData), invocation, result);
        g_free(result);
    }
}

static gboolean onHandleStartBinaryAsUser(ProxySession *object, GDBusMethodInvocation *invocation, gint pid, const gchar *binary, const gchar *pwd, gpointer userData)
{
    auto *data = new StartBinaryTask{ object, pid, binary, pwd };
    GTask *task = g_task_new(invocation, nullptr, onStartBinaryDone, userData);
    g_task_set_task_data(task, data, [](gpointer d) {
        delete static_cast<StartBinaryTask *>(d);
    });
    g_task_run_in_thread(task, onStartBinaryAsync);
    g_object_unref(task);
    return true;
}

// 当总线获取成功时的回调
static void on_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
{
    GError *error = nullptr;
    auto *service = proxy_session_skeleton_new();
    g_signal_connect(service, "handle-xdg-open", G_CALLBACK(on_handle_xdg_open), nullptr);
    g_signal_connect(service, "handle-start-binary-as-user", G_CALLBACK(onHandleStartBinaryAsUser), nullptr);
    g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(service), connection, KAIMING_DBUS_PATH, &error);
    if (error)
    {
        KMError("Failed to export kaiming session proxy interface .");
        g_clear_error(&error);
        g_object_unref(service);
    }
}

int main(int argc, char **argv)
{
    auto ownerId = g_bus_own_name(G_BUS_TYPE_SESSION, KAIMING_DBUS_SERVICE, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, nullptr, nullptr, nullptr, nullptr);
    if (ownerId == 0)
    {
        KMError("Failed to acquire system dbus name .");
        return EXIT_FAILURE;
    }

    auto *loop = g_main_loop_new(nullptr, FALSE);
    if (!loop)
    {
        KMError("Failed to create GMainLoop .");
        g_bus_unown_name(ownerId);
        return EXIT_FAILURE;
    }

    g_main_loop_run(loop);
    g_bus_unown_name(ownerId);
    g_main_loop_unref(loop);
    return 0;
}