
#include "service_status.h"
#include "service_model.h"

#include <stdio.h>
#include <string.h>
#include <json-glib/json-glib.h>
#include <gtk/gtk.h>

/* Helper: run systemctl and append labels to a GtkListBox. Returns TRUE on
 * success, FALSE on failure. This approach creates labels and does not keep
 * pointers to temporary buffers, avoiding lifetime issues.
 */
static gboolean load_services_into_store(GListStore *store)
{
    const char *cmd = "systemctl list-units --type=service --all --no-pager --output=json";
    FILE *fp = popen(cmd, "r");
    if (!fp) {
        g_warning("load_services_into_listbox: failed to run systemctl");
        return FALSE;
    }

    GString *out = g_string_new(NULL);
    char buf[4096];
    while (fgets(buf, sizeof(buf), fp)) {
        g_string_append(out, buf);
    }
    pclose(fp);

    JsonParser *parser = json_parser_new();
    GError *error = NULL;
    if (!json_parser_load_from_data(parser, out->str, -1, &error)) {
        g_warning("load_services_into_listbox: json parse error: %s", error ? error->message : "?");
    g_clear_error(&error);
    if (G_IS_OBJECT(parser)) g_object_unref(parser);
    else g_warning("service_status: parser is not a GObject in error path");
        g_string_free(out, TRUE);
        return FALSE;
    }

    JsonNode *root = json_parser_get_root(parser);
    if (JSON_NODE_HOLDS_ARRAY(root)) {
        JsonArray *arr = json_node_get_array(root);
        guint i, n = json_array_get_length(arr);
        for (i = 0; i < n; ++i) {
            JsonNode *elem = json_array_get_element(arr, i);
            if (!JSON_NODE_HOLDS_OBJECT(elem)) continue;
            JsonObject *obj = json_node_get_object(elem);

            const gchar *name = NULL;
            const gchar *state = NULL;
            const gchar *active = NULL;
            const gchar *sub = NULL;
            const gchar *description = NULL;

            if (json_object_has_member(obj, "unit"))
                name = json_object_get_string_member(obj, "unit");
                
            if (json_object_has_member(obj, "load"))
                state = json_object_get_string_member(obj, "load");
                
            if (json_object_has_member(obj, "active"))
                active = json_object_get_string_member(obj, "active");
                
            if (json_object_has_member(obj, "sub"))
                sub = json_object_get_string_member(obj, "sub");
                
            if (json_object_has_member(obj, "description"))
                description = json_object_get_string_member(obj, "description");
                

            if (!name) name = "(unknown)";
            if (!state) state = "(unknown)";
            if (!active) active = "(unknown)";
            if (!sub) sub = "(unknown)";
            if (!description) description = "(unknown)";

            gchar *safe_name = NULL;
            gchar *safe_state = NULL;
            gchar *safe_active = NULL;
            gchar *safe_sub = NULL;
            gchar *safe_description = NULL;

            if (!g_utf8_validate(name, -1, NULL))
                safe_name = g_utf8_make_valid(name, -1);
            else
                safe_name = g_strdup(name);

            if (!g_utf8_validate(state, -1, NULL))
                safe_state = g_utf8_make_valid(state, -1);
            else
                safe_state = g_strdup(state);

            if (!g_utf8_validate(active, -1, NULL))
                safe_active = g_utf8_make_valid(active, -1);
            else
                safe_active = g_strdup(active);

            if (!g_utf8_validate(sub, -1, NULL))
                safe_sub = g_utf8_make_valid(sub, -1);
            else
                safe_sub = g_strdup(sub);

            if (!g_utf8_validate(description, -1, NULL))
                safe_description = g_utf8_make_valid(description, -1);
            else
                safe_description = g_strdup(description);

        Service *s = service_new(safe_name, safe_state, safe_active, safe_sub, safe_description);
        g_debug("load_services_into_store: appending service %p type=%s",
            s, G_IS_OBJECT(s) ? G_OBJECT_TYPE_NAME(s) : "(not a GObject)");
        g_list_store_append(store, G_OBJECT(s));
        if (G_IS_OBJECT(s)) g_object_unref(s);
        else g_warning("service_status: service object is not a GObject (append)");

            g_free(safe_name);
            g_free(safe_state);
            g_free(safe_active);
            g_free(safe_sub);
            g_free(safe_description);
        }
    }

    if (G_IS_OBJECT(parser)) g_object_unref(parser);
    else g_warning("service_status: parser is not a GObject at cleanup");
    g_string_free(out, TRUE);
    return TRUE;
}

static GtkWidget *create_label_factory_widget(GObject *item, const gchar *property)
{
    /* Create a label and bind to the named property on the Service object */
    GtkWidget *label = gtk_label_new(NULL);
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    g_object_bind_property(item, property, label, "label", G_BINDING_DEFAULT);
    return label;
}

/* Forward declarations for factory handlers */
static void factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer user_data);
static void factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer user_data);
/* Forward decl for list store helper */
static void clear_list_store(GListStore *store);

/* Clear all items from a GListStore safely. */
static void clear_list_store(GListStore *store)
{
    if (!G_IS_LIST_STORE(store)) return;
    while (g_list_model_get_n_items(G_LIST_MODEL(store)) > 0) {
        guint n = g_list_model_get_n_items(G_LIST_MODEL(store));
        gpointer item = g_list_model_get_item(G_LIST_MODEL(store), n - 1);
        g_debug("clear_list_store: removing item %p type=%s", item,
                G_IS_OBJECT(item) ? G_OBJECT_TYPE_NAME(item) : "(not a GObject)");
        g_list_store_remove(store, n - 1);
    }
}

void service_status_populate(GtkBuilder *builder)
{
    GtkWidget *widget = GTK_WIDGET(gtk_builder_get_object(builder, "listview"));
    if (!widget) {
        g_warning("service_status_populate: listview not found in builder");
        return;
    }

    if (!GTK_IS_COLUMN_VIEW(widget)) {
        g_warning("service_status_populate: expected GtkColumnView for id 'listview'");
        return;
    }

    GtkColumnView *cv = GTK_COLUMN_VIEW(widget);

    /* Diagnostic: print current view and its existing model/columns info */
    gpointer existing_model = (gpointer)gtk_column_view_get_model(cv);
    if (G_IS_OBJECT(existing_model))
        g_debug("service_status_populate: columnview=%p existing_model=%p type=%s",
                cv, existing_model, G_OBJECT_TYPE_NAME(existing_model));
    else
        g_debug("service_status_populate: columnview=%p existing_model=%p (not a GObject)",
                cv, existing_model);

    /* Build a GListStore of Service objects */
    /* If we already stored a service_store on the builder, update it in-place
     * to avoid replacing stored pointers (which would trigger the destroy
     * notify and may lead to unref-on-freed-pointer races). */
    gpointer stored_store = g_object_get_data(G_OBJECT(builder), "service_store");
    if (stored_store && G_IS_LIST_STORE(stored_store)) {
        g_debug("service_status_populate: updating stored_store=%p", stored_store);
        clear_list_store(G_LIST_STORE(stored_store));
        if (!load_services_into_store(G_LIST_STORE(stored_store))) {
            g_warning("service_status_populate: failed to reload stored store");
        }
        return;
    }
    GListStore *store = g_list_store_new(SERVICE_TYPE);
    if (!load_services_into_store(store)) {
        g_warning("service_status_populate: failed to load services into store");
    if (G_IS_OBJECT(store)) g_object_unref(store);
    else g_warning("service_status: store is not a GObject when load failed");
        return;
    }

    /* Create a single selection wrapper for the list store and set it on the view */
    GtkSingleSelection *selection = gtk_single_selection_new(G_LIST_MODEL(store));
    gtk_column_view_set_model(cv, GTK_SELECTION_MODEL(selection));

    /* Create columns if not present */
    GListModel *cols = gtk_column_view_get_columns(cv);
    if (g_list_model_get_n_items(cols) == 0) {
        /* Helper factory handlers declared below */

        /* Name column */
        GtkColumnViewColumn *name_col = gtk_column_view_column_new("Name", NULL);
        gtk_column_view_column_set_resizable(name_col, TRUE);
        gtk_column_view_append_column(cv, name_col);
    GtkListItemFactory *name_factory = gtk_signal_list_item_factory_new();
    g_signal_connect(name_factory, "setup", G_CALLBACK(factory_setup), NULL);
    g_signal_connect(name_factory, "bind", G_CALLBACK(factory_bind), (gpointer)"name");
    gtk_column_view_column_set_factory(name_col, GTK_LIST_ITEM_FACTORY(name_factory));
    /* ColumnView/Column holds a reference to the factory; do not unref it here */

        /* State column */
        GtkColumnViewColumn *state_col = gtk_column_view_column_new("State", NULL);

        gtk_column_view_column_set_resizable(state_col, TRUE);
        gtk_column_view_append_column(cv, state_col);
    GtkListItemFactory *state_factory = gtk_signal_list_item_factory_new();
    g_signal_connect(state_factory, "setup", G_CALLBACK(factory_setup), NULL);
    g_signal_connect(state_factory, "bind", G_CALLBACK(factory_bind), (gpointer)"state");
    gtk_column_view_column_set_factory(state_col, GTK_LIST_ITEM_FACTORY(state_factory));
    /* keep factory alive for column view */

        /* Description column (placeholder) */
        GtkColumnViewColumn *desc_col = gtk_column_view_column_new("Description", NULL);
        gtk_column_view_column_set_resizable(desc_col, TRUE);
        gtk_column_view_append_column(cv, desc_col);
        GtkListItemFactory *desc_factory = gtk_signal_list_item_factory_new();
        g_signal_connect(desc_factory, "setup", G_CALLBACK(factory_setup), NULL);
        g_signal_connect(desc_factory, "bind", G_CALLBACK(factory_bind), (gpointer)"description");
        gtk_column_view_column_set_factory(desc_col, GTK_LIST_ITEM_FACTORY(desc_factory));
    /* keep factory alive for column view */
    }

    /* Keep the selection and store alive for the lifetime of the builder
     * by storing them on the builder object. They will be unreffed when the
     * builder is destroyed (via the destroy notify). This ensures the
     * objects remain valid while GtkColumnView and its item managers are
     * using them.
     */
    if (G_IS_OBJECT(selection))
        g_object_set_data_full(G_OBJECT(builder), "service_selection", selection, (GDestroyNotify)g_object_unref);
    else
        g_warning("service_status_populate: selection is not a GObject: %p", selection);

    if (G_IS_OBJECT(store))
        g_object_set_data_full(G_OBJECT(builder), "service_store", store, (GDestroyNotify)g_object_unref);
    else
        g_warning("service_status_populate: store is not a GObject: %p", store);
}

/* Factory handlers: create a label on setup and bind it to the property's value. */
static void factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer user_data)
{
    GtkWidget *label = gtk_label_new(NULL);
    gtk_widget_set_halign(label, GTK_ALIGN_START);
    gtk_widget_set_hexpand(label, TRUE);
    gtk_list_item_set_child(item, label);
}

static void factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer user_data)
{
    const gchar *prop = (const gchar *)user_data;
    GObject *obj = gtk_list_item_get_item(item);
    if (!G_IS_OBJECT(obj)) return;
    GtkWidget *label = GTK_WIDGET(gtk_list_item_get_child(item));
    if (!label) return;
    gchar *val = NULL;
    g_object_get(obj, prop, &val, NULL);
    if (val) {
        gtk_label_set_text(GTK_LABEL(label), val);
        g_free(val);
    } else {
        gtk_label_set_text(GTK_LABEL(label), "");
    }
}
