// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#define _GNU_SOURCE
#include "dconfig.h"

#include "log.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* DConfig D-Bus constants */
#define DCONFIG_SERVICE_NAME "org.desktopspec.ConfigManager"
#define DCONFIG_INTERFACE_NAME "org.desktopspec.ConfigManager"
#define DCONFIG_MANAGER_INTERFACE "org.desktopspec.ConfigManager.Manager"
#define DCONFIG_OBJECT_PATH "/"

/* DConfig D-Bus method names */
#define DCONFIG_METHOD_ACQUIRE_MANAGER_V2 "acquireManagerV2"
#define DCONFIG_METHOD_VALUE "value"
#define DCONFIG_METHOD_SET_VALUE "setValue"
#define DCONFIG_METHOD_RELEASE "release"

/* Helper function to read a boolean value from dconfig */
static int read_bool_value(sd_bus *bus, const char *object_path, const char *key, bool *out_value)
{
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus_message *reply = NULL;
    int r;

    r = sd_bus_message_new_method_call(bus,
                                       &m,
                                       DCONFIG_SERVICE_NAME,
                                       object_path,
                                       DCONFIG_MANAGER_INTERFACE,
                                       DCONFIG_METHOD_VALUE);
    if (r < 0) {
        log_warning("Failed to create method call for %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_append(m, "s", key);
    if (r < 0) {
        log_warning("Failed to append key %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_call(bus, m, 0, &error, &reply);
    if (r < 0) {
        log_warning("Failed to call value method for %s: %s",
                    key,
                    error.message ? error.message : strerror(-r));
        goto cleanup;
    }

    // Read variant<bool>
    int val;
    r = sd_bus_message_enter_container(reply, 'v', "b");
    if (r < 0) {
        log_warning("Failed to enter variant container for %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_read(reply, "b", &val);
    if (r < 0) {
        log_warning("Failed to read bool value for %s: %s", key, strerror(-r));
        sd_bus_message_exit_container(reply);
        goto cleanup;
    }

    *out_value = (bool)val;
    sd_bus_message_exit_container(reply);

    log_info("Loaded %s=%s from dconfig", key, *out_value ? "true" : "false");
    r = 0;

cleanup:
    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);
    return r;
}

/* Helper function to read string array from dconfig */
static int read_string_array(
        sd_bus *bus, const char *object_path, const char *key, char ***out_array, size_t *out_count)
{
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus_message *reply = NULL;
    int r;
    char **dirs = NULL;
    size_t count = 0;

    r = sd_bus_message_new_method_call(bus,
                                       &m,
                                       DCONFIG_SERVICE_NAME,
                                       object_path,
                                       DCONFIG_MANAGER_INTERFACE,
                                       DCONFIG_METHOD_VALUE);
    if (r < 0) {
        log_warning("Failed to create method call for %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_append(m, "s", key);
    if (r < 0) {
        log_warning("Failed to append key %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_call(bus, m, 0, &error, &reply);
    if (r < 0) {
        log_warning("Failed to call value method for %s: %s",
                    key,
                    error.message ? error.message : strerror(-r));
        goto cleanup;
    }

    // Read variant<array of variant<string>>
    // Example reply: <[<"/usr">, <"/persistent/ostree">, <"/sysroot/ostree">,
    // <"/ostree">]>
    r = sd_bus_message_enter_container(reply, 'v', "av");
    if (r < 0) {
        log_warning("Failed to enter variant container for %s: %s", key, strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_enter_container(reply, 'a', "v");
    if (r < 0) {
        log_warning("Failed to enter array container for %s: %s", key, strerror(-r));
        sd_bus_message_exit_container(reply);
        goto cleanup;
    }

    // Allocate array for max 16 elements
    const size_t max_elements = 16;
    dirs = calloc(max_elements + 1, sizeof(char *));
    if (!dirs) {
        log_warning("Failed to allocate memory for %s array", key);
        sd_bus_message_exit_container(reply);
        sd_bus_message_exit_container(reply);
        r = -ENOMEM;
        goto cleanup;
    }

    // Read elements directly (single pass)
    count = 0;
    while (count < max_elements) {
        r = sd_bus_message_enter_container(reply, 'v', "s");
        if (r == 0) {
            // No more elements
            break;
        }
        if (r < 0) {
            log_warning("Failed to enter variant for %s element %zu: %s", key, count, strerror(-r));
            for (size_t j = 0; j < count; j++) {
                free(dirs[j]);
            }
            free(dirs);
            dirs = NULL;
            sd_bus_message_exit_container(reply);
            sd_bus_message_exit_container(reply);
            goto cleanup;
        }

        const char *str;
        r = sd_bus_message_read(reply, "s", &str);
        if (r < 0) {
            log_warning("Failed to read string for %s element %zu: %s", key, count, strerror(-r));
            for (size_t j = 0; j < count; j++) {
                free(dirs[j]);
            }
            free(dirs);
            dirs = NULL;
            sd_bus_message_exit_container(reply);
            sd_bus_message_exit_container(reply);
            sd_bus_message_exit_container(reply);
            goto cleanup;
        }

        dirs[count] = strdup(str);
        if (!dirs[count]) {
            log_warning("Failed to duplicate string for %s element %zu", key, count);
            for (size_t j = 0; j < count; j++) {
                free(dirs[j]);
            }
            free(dirs);
            dirs = NULL;
            r = -ENOMEM;
            sd_bus_message_exit_container(reply);
            sd_bus_message_exit_container(reply);
            sd_bus_message_exit_container(reply);
            goto cleanup;
        }

        count++;
        sd_bus_message_exit_container(reply);
    }

    // Check if there are more elements (warning if limit reached)
    r = sd_bus_message_enter_container(reply, 'v', "s");
    if (r > 0) {
        log_warning("%s has more than %zu elements, only first %zu will be used",
                    key,
                    max_elements,
                    max_elements);
        sd_bus_message_exit_container(reply);
    }

    sd_bus_message_exit_container(reply);
    sd_bus_message_exit_container(reply);

    if (count == 0) {
        log_warning("%s array is empty", key);
        free(dirs);
        dirs = NULL;
        r = -1;
        goto cleanup;
    }

    *out_array = dirs;
    *out_count = count;

    log_info("Loaded %s with %zu elements from dconfig", key, count);
    r = 0;

cleanup:
    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);
    return r;
}

/* Helper function to acquire dconfig manager */
static int acquire_dconfig_manager(sd_bus *bus, char **out_path)
{
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus_message *reply = NULL;
    int r;
    const char *object_path = NULL;
    char *path_copy = NULL;

    if (!bus || !out_path) {
        return -EINVAL;
    }

    r = sd_bus_message_new_method_call(bus,
                                       &m,
                                       DCONFIG_SERVICE_NAME,
                                       DCONFIG_OBJECT_PATH,
                                       DCONFIG_INTERFACE_NAME,
                                       DCONFIG_METHOD_ACQUIRE_MANAGER_V2);
    if (r < 0) {
        log_warning("Failed to create acquireManagerV2 call: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_append(m,
                              "usss",
                              (uint32_t)DCONFIG_UID,
                              DCONFIG_APP_ID,
                              DCONFIG_RESOURCE_ID,
                              "");
    if (r < 0) {
        log_warning("Failed to append parameters: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_call(bus, m, 0, &error, &reply);
    if (r < 0) {
        log_warning("Failed to acquire dconfig manager: %s",
                    error.message ? error.message : strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_read(reply, "o", &object_path);
    if (r < 0) {
        log_warning("Failed to read object path: %s", strerror(-r));
        goto cleanup;
    }

    path_copy = strdup(object_path);
    if (!path_copy) {
        log_warning("Failed to allocate memory for object path");
        r = -ENOMEM;
        goto cleanup;
    }

    *out_path = path_copy;
    r = 0;

cleanup:
    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);
    return r;
}

/* Helper function to release dconfig manager */
static int release_dconfig_manager(sd_bus *bus, const char *object_path)
{
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus_message *reply = NULL;
    int r;

    if (!bus || !object_path) {
        return -EINVAL;
    }

    r = sd_bus_message_new_method_call(bus,
                                       &m,
                                       DCONFIG_SERVICE_NAME,
                                       object_path,
                                       DCONFIG_MANAGER_INTERFACE,
                                       DCONFIG_METHOD_RELEASE);
    if (r < 0) {
        log_warning("Failed to create release call: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_call(bus, m, 0, &error, &reply);
    if (r < 0) {
        log_warning("Failed to release dconfig manager: %s",
                    error.message ? error.message : strerror(-r));
        goto cleanup;
    }

    r = 0;

cleanup:
    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);
    return r;
}

int dconfig_load_all_config(sd_bus *bus,
                            bool *enabled,
                            char ***watch_dirs,
                            size_t *watch_dirs_count)
{
    int r;
    char *path_copy = NULL;

    bool tmp_enabled = true;
    char **tmp_watch_dirs = NULL;
    size_t tmp_count = 0;

    if (!bus || !enabled || !watch_dirs || !watch_dirs_count) {
        return -EINVAL;
    }

    log_info("Loading configuration from dconfig...");

    // 1. Acquire manager
    r = acquire_dconfig_manager(bus, &path_copy);
    if (r < 0) {
        goto cleanup;
    }

    log_info("Acquired dconfig manager at %s", path_copy);

    // 2. Read enabled
    if (read_bool_value(bus, path_copy, "enabled", &tmp_enabled) < 0) {
        log_info("Failed to read enabled, using default value: %s", tmp_enabled ? "true" : "false");
    } else {
        log_info("Read enabled from dconfig: %s", tmp_enabled ? "true" : "false");
    }

    // 3. Read watchDirs
    if (read_string_array(bus, path_copy, "watchDirs", &tmp_watch_dirs, &tmp_count) < 0) {
        log_info("Failed to read watchDirs from dconfig, using default values");
        // Allocate default watchDirs
        tmp_count = 4;
        tmp_watch_dirs = calloc(tmp_count + 1, sizeof(char *));
        if (tmp_watch_dirs) {
            tmp_watch_dirs[0] = strdup("/usr");
            tmp_watch_dirs[1] = strdup("/persistent/ostree");
            tmp_watch_dirs[2] = strdup("/sysroot/ostree");
            tmp_watch_dirs[3] = strdup("/ostree");
            log_info("Using default watchDirs: [/usr, /persistent/ostree, /sysroot/ostree, "
                     "/ostree]");
        } else {
            tmp_count = 0;
        }
    } else {
        // Print loaded watchDirs
        log_info("Read watchDirs from dconfig (%zu directories): ", tmp_count);
        for (size_t i = 0; i < tmp_count; i++) {
            log_info("%s%s", tmp_watch_dirs[i], i < tmp_count - 1 ? ", " : "");
        }
    }

    // 4. Release manager
    r = release_dconfig_manager(bus, path_copy);
    if (r < 0) {
        log_warning("Failed to release dconfig manager: %s", strerror(-r));
        // Continue anyway, as the main operation succeeded
    }

    // Success - update output parameters
    *enabled = tmp_enabled;
    *watch_dirs = tmp_watch_dirs;
    *watch_dirs_count = tmp_count;

    log_info("Configuration loaded: enabled=%s, len(watchDirs)=%zu",
             *enabled ? "true" : "false",
             *watch_dirs_count);

    r = 0;

cleanup:
    free(path_copy);
    return r;
}

int dconfig_sync_bool(sd_bus *bus, const char *key, bool value)
{
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus_message *reply = NULL;
    int r;
    char *path_copy = NULL;

    if (!bus || !key) {
        return -EINVAL;
    }

    log_info("Syncing %s=%s to dconfig...", key, value ? "true" : "false");

    // 1. Acquire manager
    r = acquire_dconfig_manager(bus, &path_copy);
    if (r < 0) {
        goto cleanup;
    }

    // 2. Set value
    r = sd_bus_message_new_method_call(bus,
                                       &m,
                                       DCONFIG_SERVICE_NAME,
                                       path_copy,
                                       DCONFIG_MANAGER_INTERFACE,
                                       DCONFIG_METHOD_SET_VALUE);
    if (r < 0) {
        log_warning("Failed to create setValue call: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_append(m, "s", key);
    if (r < 0) {
        log_warning("Failed to append key: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_open_container(m, 'v', "b");
    if (r < 0) {
        log_warning("Failed to open variant container: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_append(m, "b", value);
    if (r < 0) {
        log_warning("Failed to append value: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_message_close_container(m);
    if (r < 0) {
        log_warning("Failed to close variant container: %s", strerror(-r));
        goto cleanup;
    }

    r = sd_bus_call(bus, m, 0, &error, &reply);
    if (r < 0) {
        log_warning("Failed to set value: %s", error.message ? error.message : strerror(-r));
        goto cleanup;
    }

    log_info("Successfully synced %s=%s to dconfig", key, value ? "true" : "false");

    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    m = NULL;
    reply = NULL;

    // 3. Release manager
    r = release_dconfig_manager(bus, path_copy);
    if (r < 0) {
        log_warning("Failed to release dconfig manager: %s", strerror(-r));
        // Continue anyway, as the main operation succeeded
    }

    r = 0;

cleanup:
    free(path_copy);
    sd_bus_message_unref(m);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);

    return r;
}

void dconfig_free_string_array(char **array, size_t count)
{
    if (!array) {
        return;
    }

    for (size_t i = 0; i < count; i++) {
        free(array[i]);
    }
    free(array);
}
