/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2025 Canonical Ltd */

#include <gio/gio.h>
#include <libnotify/notify.h>

#include "unattended-upgrade.h"
#include "update-notifier.h"
#include "trayappletui.h"

#define ACTION_POSTPONE "POSTPONE"
#define ACTION_OK "OK"

typedef struct {
    NotifyNotification *postpone_notification;
    GDBusProxy *proxy;
} UnattendedUpgradePrivate;

static void
on_postpone_reply (GDBusProxy *proxy, GAsyncResult *res, UnattendedUpgradePrivate *priv)
{
    g_autoptr(GError) error = NULL;
    g_autoptr(GVariant) ret = g_dbus_proxy_call_finish (proxy, res, &error);
    if (!ret) {
        g_warning ("Failed to Postpone unattended-upgrade: %s", error->message);
        return;
    }

    notify_notification_close (priv->postpone_notification, NULL);
}

static void
on_postpone_action (NotifyNotification *self, char *action, TrayApplet *tray_applet)
{
    UnattendedUpgradePrivate *priv = tray_applet->user_data;
    g_return_if_fail (g_strcmp0 (action, ACTION_POSTPONE) == 0);

    g_dbus_proxy_call (priv->proxy, "Postpone", NULL,
        G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
        -1, NULL, (GAsyncReadyCallback) on_postpone_reply, priv);
}

static void
on_ok_action (NotifyNotification *self, char *action, TrayApplet *tray_applet)
{
    g_return_if_fail (g_strcmp0 (action, ACTION_OK) == 0);
    notify_notification_close (self, NULL);
}

static void
on_unattended_upgrade_pending_signal (GDBusProxy *proxy,
                                      gchar *sender_name,
                                      gchar *signal_name,
                                      GVariant *parameters,
                                      TrayApplet *tray_applet)
{
    UnattendedUpgradePrivate *priv = tray_applet->user_data;

    if (g_str_equal (signal_name, "AboutToStart")) {
        unsigned timeout = 0;
        unsigned remaining_days = 0;
        g_autofree gchar *desc = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GVariant) info = NULL;

        g_variant_get (parameters, "(@a{sv})", &info);
        g_return_if_fail (info != NULL);

        g_auto(GVariantDict) dict = G_VARIANT_DICT_INIT (info);
        g_variant_dict_lookup (&dict, "postpone-timeout-time", "u", &timeout);
        g_variant_dict_lookup (&dict, "postpone-remaining-days", "u", &remaining_days);

        if (timeout == 0 && remaining_days == 0) {
            // bad parameters, but we should still be able to postpone
            desc = g_strdup(_("You may postpone the updates to a later time."));
        } else {
            desc = g_strdup_printf(_("You may postpone the updates for %u more days."), remaining_days);
        }

        notify_notification_update (priv->postpone_notification,
            _("System updates will install soon"), desc, NULL);

        if (!notify_notification_show (priv->postpone_notification, &error))
            g_warning ("Failed to show notification for postponing unattended-upgrade: %s", error->message);
    } else if (g_str_equal (signal_name, "Started")) {
        notify_notification_close (priv->postpone_notification, NULL);
        tray_applet_ui_set_visible (tray_applet, TRUE);
    } else if (g_str_equal (signal_name, "Finished")) {
        notify_notification_close (priv->postpone_notification, NULL);
        tray_applet_ui_set_visible (tray_applet, FALSE);
    } else if (g_str_equal (signal_name, "Canceled")) {
        notify_notification_close (priv->postpone_notification, NULL);
        tray_applet_ui_set_visible (tray_applet, FALSE);
    }
}

void
on_dbus_proxy_ready (GObject *, GAsyncResult *res, TrayApplet *tray_applet)
{
    UnattendedUpgradePrivate *priv = tray_applet->user_data;
    g_autoptr(GError) error = NULL;
    g_autoptr(GDBusProxy) proxy = g_dbus_proxy_new_for_bus_finish (res, &error);

    if (!proxy) {
        g_critical ("Failed to get a DBus proxy for com.ubuntu.UnattendedUpgrade: %s", error->message);
        return;
    }

    const char *info_text = _("System updates are being applied in the background");
    tray_applet_ui_ensure (tray_applet);
    tray_applet_ui_set_menu (tray_applet, gtk_menu_new ());
    tray_applet_ui_set_icon (tray_applet, "system-software-update-symbolic", info_text);
    tray_applet_ui_set_tooltip_text (tray_applet, info_text);

    NotifyNotification *notification = notify_notification_new ("", NULL, NULL);
    notify_notification_add_action (notification, ACTION_OK, _("OK"),
        (NotifyActionCallback) on_ok_action, tray_applet, NULL);
    notify_notification_add_action (notification, ACTION_POSTPONE, _("Postpone"),
        (NotifyActionCallback) on_postpone_action, tray_applet, NULL);
    // Bypass 'do not disturb' mode
    notify_notification_set_urgency (notification, NOTIFY_URGENCY_CRITICAL);
    // Persistent
    notify_notification_set_hint (notification, "transient", g_variant_new_boolean (FALSE));
    // Do not automatically dismiss on click
    notify_notification_set_hint (notification, "resident", g_variant_new_boolean (TRUE));
    // Link to the update-manager application
    notify_notification_set_hint (notification, "desktop-entry", g_variant_new_string ("update-manager"));

    priv->postpone_notification = g_steal_pointer (&notification);
    priv->proxy = g_steal_pointer (&proxy);
    g_signal_connect (priv->proxy, "g-signal", G_CALLBACK (on_unattended_upgrade_pending_signal), tray_applet);
}

void
unattended_upgrade_tray_icon_init(TrayApplet *tray_applet)
{
    UnattendedUpgradePrivate *priv = g_new0 (UnattendedUpgradePrivate, 1);
    tray_applet->user_data = g_steal_pointer (&priv);

    GDBusProxyFlags flags =
        G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
        G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;

    g_dbus_proxy_new_for_bus (
        G_BUS_TYPE_SYSTEM, flags, NULL,
        "com.ubuntu.UnattendedUpgrade",
        "/com/ubuntu/UnattendedUpgrade/Pending",
        "com.ubuntu.UnattendedUpgrade.Pending",
        NULL, (GAsyncReadyCallback) on_dbus_proxy_ready, tray_applet);
}

void
unattended_upgrade_tray_icon_dispose(TrayApplet *tray_applet)
{
    UnattendedUpgradePrivate *priv = tray_applet->user_data;
    if (priv->proxy)
        g_signal_handlers_disconnect_by_data (priv->proxy, tray_applet);
    g_clear_object (&priv->proxy);
    g_clear_object (&priv->postpone_notification);
    g_free (priv);
    tray_applet->user_data = NULL;
}
