/*
 * 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/kmsystemproxy.h"

#define KAIMING_DBUS_SERVICE "org.kaiming.proxy"
#define KAIMING_DBUS_PATH "/org/kaiming/proxy"
#define KAIMING_DBUS_INTERFACE "org.kaiming.proxy.system"

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

struct TaskResult
{
    int ret;
    std::string msg;
};

struct TaskRunParams
{
    std::string command;
    std::string appid;
    std::string cid;
};

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

static void onStartBinaryAsync(GTask *task, gpointer, gpointer taskData, GCancellable *)
{
    auto *data = static_cast<StartBinaryTask *>(taskData);
    kmlogger.debug("[DBus] system 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_system_complete_start_binary_as_user(PROXY_SYSTEM(userData), invocation, result);
        g_free(result);
    }
}

static gboolean onHandleStartBinaryAsUser(ProxySystem *object, GDBusMethodInvocation *invocation, gint uid, const gchar *binary, const gchar *pwd, gpointer userData)
{
    auto *data = new StartBinaryTask{ object, uid, binary, pwd };
    auto *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 onCommandAsync(GTask *task, gpointer, gpointer taskData, GCancellable *)
{
    auto data = static_cast<TaskRunParams *>(taskData);

    int pipefd[2];
    if (pipe(pipefd) == -1)
    {
        g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "pipe failed");
        return;
    }
    pid_t pid = fork();
    if (pid == -1)
    {
        g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_FAILED, "fork failed");
        return;
    }

    if (pid == 0)
    {
        // 关闭父进程读端，重定向写端到 stdout/stderr
        close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        dup2(pipefd[1], STDERR_FILENO);
        close(pipefd[1]);

        std::string cmd;
        cmd.append(KAIMING_BOX).append(" ").append(data->command);
        const char *args[] = {"/bin/bash", "-c", cmd.c_str(), nullptr};
        execv("/bin/bash", const_cast<char *const *>(args));
        KMError("exec command failed !");
        exit(EXIT_FAILURE);
    }
    else
    {
        // 父进程
        close(pipefd[1]); // 关闭写端

        // 先读子进程输出
        std::string output;
        char buf[1024];
        ssize_t n;
        while ((n = read(pipefd[0], buf, sizeof(buf) - 1)) > 0)
        {
            buf[n] = '\0';
            output += buf;
        }
        close(pipefd[0]);

        int status = 0;
        waitpid(pid, &status, 0);
        int ret = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
        TaskResult *result = new TaskResult{ ret, output };
        g_task_return_pointer(task, result, [](gpointer d) {
            delete static_cast<TaskResult *>(d);
        });
    }
}

static void onCommandDone(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));
    TaskResult *result = static_cast<TaskResult *>(g_task_propagate_pointer(task, &error));
    if (error)
    {
        g_dbus_method_invocation_return_gerror(invocation, error);
        g_error_free(error);
    }
    else
    {
        proxy_system_complete_run(PROXY_SYSTEM(userData), invocation, result->ret, result->msg.c_str());
        delete result;
    }
}

static gboolean asyncTask(GDBusMethodInvocation *invocation, const gchar *command, const gchar *appid, const gchar *cid, gpointer userData)
{
    if (command == nullptr)
    {
        g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Command cannot be empty");
        return false;
    }

    auto *data = new TaskRunParams{ command, appid, cid };
    auto *task = g_task_new(invocation, nullptr, onCommandDone, userData);
    g_task_set_task_data(task, data, [](gpointer d) {
        delete static_cast<TaskRunParams *>(d);
    });
    g_task_run_in_thread(task, onCommandAsync);
    g_object_unref(task);
    return true;
}

static gboolean onHandleRun(ProxySystem *object, GDBusMethodInvocation *invocation, const gchar *command, const gchar *appid, const gchar *cid, gpointer userData)
{
    return asyncTask(invocation, command, appid, cid, userData);
}

static gboolean onHandleEnter(ProxySystem *object, GDBusMethodInvocation *invocation, const gchar *command, gpointer userData)
{
    return asyncTask(invocation, command, "", "", userData);
}

static gboolean onHandleKill(ProxySystem *object, GDBusMethodInvocation *invocation, const gchar *id, gpointer userData)
{
    int ret = 0;
    std::string msg;
    kmkill(ret, msg, id);
    proxy_system_complete_kill(PROXY_SYSTEM(userData), invocation, ret, msg.c_str());
    return true;
}

static gboolean onHandlePs(ProxySystem *object, GDBusMethodInvocation *invocation, gpointer userData)
{
    int ret = 0;
    std::string msg;
    ps(ret, msg);
    proxy_system_complete_ps(PROXY_SYSTEM(userData), invocation, ret, msg.c_str());
    return true;
}

static gboolean onHandleGetRunStatus(ProxySystem *object, GDBusMethodInvocation *invocation, const gchar *appId, const gchar *uid, gpointer userData)
{
    bool ret;
    std::string containerid;
    ret = getRunStatus(appId, uid, containerid);
    proxy_system_complete_get_run_status(PROXY_SYSTEM(userData), invocation, ret, containerid.c_str());
    return true;
}

static void onBusAcquired(GDBusConnection *connection, const gchar *, gpointer)
{
    GError *error = nullptr;
    auto *service = proxy_system_skeleton_new();
    g_signal_connect(service, "handle-start-binary-as-user", G_CALLBACK(onHandleStartBinaryAsUser), nullptr);
    g_signal_connect(service, "handle-run", G_CALLBACK(onHandleRun), nullptr);
    g_signal_connect(service, "handle-enter", G_CALLBACK(onHandleEnter), nullptr);
    g_signal_connect(service, "handle-ps", G_CALLBACK(onHandlePs), nullptr);
    g_signal_connect(service, "handle-kill", G_CALLBACK(onHandleKill), nullptr);
    g_signal_connect(service, "handle-get-run-status", G_CALLBACK(onHandleGetRunStatus), nullptr);

    g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(service), connection, KAIMING_DBUS_PATH, &error);
    if (error)
    {
        g_clear_error(&error);
        g_object_unref(service);
        KMError("Failed to export kaiming system proxy interface .");
    }
}

int main()
{
    auto ownerId = g_bus_own_name(G_BUS_TYPE_SYSTEM, KAIMING_DBUS_SERVICE, G_BUS_NAME_OWNER_FLAGS_NONE, onBusAcquired, 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;
}