#!/usr/bin/env python3
#--------------------------------------------------------------------------------------------------------
# Name: Linux Lite - Lite Firewall Config
# Architecture: amd64
# Author: Jerry Bezencon
# Website: https://www.linuxliteos.com
# Language: Python/GTK4
# Licence: GPLv2
#--------------------------------------------------------------------------------------------------------

import gi
gi.require_version('Gtk', '4.0')
gi.require_version("Pango", "1.0")
gi.require_version("PangoCairo", "1.0")
from gi.repository import Gtk, Gdk, GLib, Gio, Pango, PangoCairo

import gettext as _gt, locale as _loc
TEXTDOMAIN = "lite-firewallconfig"
try:
    _loc.setlocale(_loc.LC_ALL, "")
except _loc.Error:
    pass
_gt.bindtextdomain(TEXTDOMAIN, "/usr/share/locale")
_gt.textdomain(TEXTDOMAIN)
_ = _gt.translation(TEXTDOMAIN, "/usr/share/locale", fallback=True).gettext

import subprocess
import threading


# ===========================================================================
# CANONICAL Linux Lite house-style dialog — copy this block VERBATIM into each
# GTK4 Lite app (just below imports). Reference: lite-autologin.
#
# REQUIRES these imports in the host app:
#   gi.require_version("Pango", "1.0"); gi.require_version("PangoCairo", "1.0")
#   from gi.repository import Gtk, Gdk, GLib, Pango, PangoCairo
# and a gettext `_` callable (every Lite GTK4 app already has one).
#
# USAGE:
#   LiteDialog.result(parent, "success", _("Title"), _("Subtitle"))
#   LiteDialog.result(parent, "error",   _("Title"), _("Subtitle"))
#   LiteDialog.confirm(parent, _("Remove X?"), _("This cannot be undone."),
#                      confirm_label=_("Remove"), destructive=True,
#                      on_confirm=do_it)
# RULES (do not change): glyph colour is PangoCairo (never a coloured GtkLabel);
# NO dim text anywhere; mode-aware palette.
# ===========================================================================

class LiteDialog(Gtk.Window):
    _GLYPH = {"success": "✓", "error": "✕", "warning": "⚠",
              "question": "?", "info": "i"}
    _css_loaded = False

    def __init__(self, parent, kind="info", title="", subtitle="", buttons=None):
        super().__init__(modal=True, resizable=False)
        self.set_title(parent.get_title() if parent is not None else "")
        if parent is not None:
            self.set_transient_for(parent)
        self.set_default_size(440, -1)
        LiteDialog._ensure_css()

        self.set_titlebar(Gtk.HeaderBar())

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12,
                      margin_top=28, margin_bottom=24, margin_start=32, margin_end=32)
        self.set_child(box)

        # Glyph drawn with PangoCairo in the kind colour (a DrawingArea, not a
        # GtkLabel): GtkLabel drops markup/CSS foreground under the LL theme's
        # provider priority, but Cairo painting can't be overridden by CSS.
        box.append(self._make_glyph(kind))

        t = Gtk.Label(label=title)
        t.add_css_class("lite-dlg-title")
        t.set_halign(Gtk.Align.CENTER)
        t.set_justify(Gtk.Justification.CENTER)
        t.set_wrap(True)
        box.append(t)

        if subtitle:
            s = Gtk.Label(label=subtitle)
            s.set_halign(Gtk.Align.CENTER)
            s.set_justify(Gtk.Justification.CENTER)
            s.set_wrap(True)
            box.append(s)

        actions = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12,
                          margin_top=10)
        actions.set_halign(Gtk.Align.CENTER)
        actions.add_css_class("lite-dlg-actions")
        box.append(actions)

        focus_btn = None
        for spec in (buttons or [{"label": _("Close"), "primary": True}]):
            b = Gtk.Button(label=spec.get("label", _("OK")))
            b.add_css_class("lite-dlg-btn")
            if spec.get("primary"):
                b.add_css_class("suggested-action")
            if spec.get("destructive"):
                b.add_css_class("destructive-action")
            b.connect("clicked", self._on_click, spec.get("callback"))
            actions.append(b)
            if spec.get("primary"):
                focus_btn = b
        if focus_btn is not None:
            focus_btn.grab_focus()

    def _on_click(self, _btn, callback):
        self.close()
        if callback is not None:
            callback()

    @staticmethod
    def _is_dark():
        s = Gtk.Settings.get_default()
        if s is None:
            return False
        try:
            if s.get_property("gtk-application-prefer-dark-theme"):
                return True
            return "dark" in (s.get_property("gtk-theme-name") or "").lower()
        except Exception:
            return False

    @classmethod
    def _palette(cls):
        if cls._is_dark():
            return {"success": "#57e389", "error": "#ff7b72", "warning": "#f9c84e",
                    "question": "#78aeed", "info": "#78aeed"}
        return {"success": "#26a269", "error": "#e01b24", "warning": "#e8870a",
                "question": "#3584e4", "info": "#3584e4"}

    @classmethod
    def _make_glyph(cls, kind):
        char = cls._GLYPH.get(kind, "")
        hexc = cls._palette().get(kind, "#000000")
        r = int(hexc[1:3], 16) / 255.0
        g = int(hexc[3:5], 16) / 255.0
        b = int(hexc[5:7], 16) / 255.0
        area = Gtk.DrawingArea()
        area.set_content_width(72)
        area.set_content_height(60)
        area.set_halign(Gtk.Align.CENTER)

        def _draw(_area, cr, w, h):
            layout = PangoCairo.create_layout(cr)
            layout.set_markup('<span weight="bold" size="40000">%s</span>' % char)
            tw, th = layout.get_pixel_size()
            cr.set_source_rgb(r, g, b)
            cr.move_to((w - tw) / 2.0, (h - th) / 2.0)
            PangoCairo.show_layout(cr, layout)

        area.set_draw_func(_draw)
        return area

    @classmethod
    def _ensure_css(cls):
        if cls._css_loaded:
            return
        provider = Gtk.CssProvider()
        provider.load_from_data(b"""
        .lite-dlg-title { font-size: 19px; font-weight: bold; }
        .lite-dlg-actions button {
            border-radius: 18px;
            padding-top: 7px; padding-bottom: 7px;
            padding-left: 22px; padding-right: 22px;
            min-width: 86px;
        }
        """)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(), provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        cls._css_loaded = True

    @classmethod
    def result(cls, parent, kind, title, subtitle="", button=None, on_close=None):
        cls(parent, kind=kind, title=title, subtitle=subtitle,
            buttons=[{"label": button or _("Close"), "primary": True,
                      "callback": on_close}]).present()

    @classmethod
    def confirm(cls, parent, title, subtitle="", confirm_label=None,
                cancel_label=None, kind="question", destructive=False,
                on_confirm=None, on_cancel=None):
        cls(parent, kind=kind, title=title, subtitle=subtitle, buttons=[
            {"label": cancel_label or _("Cancel"), "callback": on_cancel},
            {"label": confirm_label or _("OK"),
             "primary": not destructive, "destructive": destructive,
             "callback": on_confirm},
        ]).present()

# Icon paths
ICON_ENABLED = "/usr/share/icons/Papirus/22x22/apps/security-high.svg"
ICON_DISABLED = "/usr/share/icons/Papirus/22x22/apps/security-low.svg"
ICON_INFO = "/usr/share/icons/Papirus/22x22/status/dialog-information.svg"
ICON_APP = "/usr/share/icons/Papirus/24x24/apps/lite-firewallconfig.png"


def run_helper(action):
    """Invoke the privileged lite-firewallconfig-helper via pkexec. The helper
    script has its own polkit policy (com.linuxliteos.lite-firewallconfig) so
    the auth dialog shows the LL shield icon and a clean message rather than
    the generic 'Authentication is needed to run /bin/sh' text.
    Returns (success, stderr)."""
    try:
        result = subprocess.run(
            ["pkexec", "/usr/bin/lite-firewallconfig-helper", action],
            capture_output=True, text=True, timeout=60
        )
        return result.returncode == 0, (result.stderr or "").strip()
    except subprocess.TimeoutExpired:
        return False, _("pkexec timed out")
    except Exception as e:
        return False, str(e)


class ProgressDialog(Gtk.Window):
    """An indeterminate-progress dialog shown while a pkexec call runs."""

    def __init__(self, parent, title, label):
        super().__init__(title=title)
        self.set_transient_for(parent)
        self.set_modal(True)
        self.set_default_size(400, 100)
        self.set_resizable(False)

        self.success = False
        self.error_detail = ""
        self._pulse_source = None

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_top(20)
        box.set_margin_bottom(20)
        box.set_margin_start(20)
        box.set_margin_end(20)

        self.status_label = Gtk.Label(label=label)
        self.status_label.set_xalign(0)
        box.append(self.status_label)

        self.progress_bar = Gtk.ProgressBar()
        box.append(self.progress_bar)

        self.set_child(box)

    def run_action(self, action, on_complete):
        """Invoke pkexec /usr/bin/lite-firewallconfig-helper <action>, pulsing
        the bar while it runs."""
        self._pulse_source = GLib.timeout_add(100, self._pulse)

        def worker():
            self.success, self.error_detail = run_helper(action)
            GLib.idle_add(self.on_finished, on_complete)

        thread = threading.Thread(target=worker, daemon=True)
        thread.start()

    def _pulse(self):
        self.progress_bar.pulse()
        return True

    def on_finished(self, callback):
        if self._pulse_source is not None:
            GLib.source_remove(self._pulse_source)
            self._pulse_source = None
        self.close()
        if callback:
            callback(self.success, self.error_detail)
        return False


class FirewallConfigApp(Gtk.Application):
    """Main FirewallD configuration application."""

    def __init__(self):
        super().__init__(application_id="com.linuxlite.firewallconfig",
                         flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.window = None

    def do_activate(self):
        """Create and show the main window."""
        if self.window is None:
            self.window = Gtk.ApplicationWindow(application=self)
            self.window.set_title(_("Firewall"))
            self.window.set_default_size(280, 200)
            self.window.set_resizable(False)

            # Try to set window icon
            try:
                self.window.set_icon_name("lite-firewallconfig")
            except:
                pass

            # Header bar (plain Gtk so the window follows the Linux-Lite theme)
            header = Gtk.HeaderBar()
            self.window.set_titlebar(header)

            # Content box
            content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
            content_box.set_margin_top(20)
            content_box.set_margin_bottom(20)
            content_box.set_margin_start(20)
            content_box.set_margin_end(20)
            content_box.set_halign(Gtk.Align.CENTER)
            content_box.set_valign(Gtk.Align.CENTER)
            content_box.set_vexpand(True)

            # Enable Firewall button
            enable_btn = self.create_button(_("Enable Firewall"), ICON_ENABLED,
                                            self.on_enable_clicked)
            content_box.append(enable_btn)

            # Disable Firewall button
            disable_btn = self.create_button(_("Disable Firewall"), ICON_DISABLED,
                                             self.on_disable_clicked)
            content_box.append(disable_btn)

            # Show Status button
            status_btn = self.create_button(_("Show Status"), ICON_INFO,
                                            self.on_status_clicked)
            content_box.append(status_btn)

            self.window.set_child(content_box)

        self.window.present()
    
    def create_button(self, label, icon_path, callback):
        """Create a button with an icon."""
        button = Gtk.Button()
        button.set_size_request(200, 40)
        
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.set_halign(Gtk.Align.CENTER)
        
        # Try to load icon from file, fall back to symbolic icon
        try:
            icon = Gtk.Image.new_from_file(icon_path)
        except:
            icon = Gtk.Image.new_from_icon_name("dialog-information-symbolic")
        icon.set_pixel_size(22)
        
        label_widget = Gtk.Label(label=label)

        box.append(icon)
        box.append(label_widget)
        button.set_child(box)
        button.connect("clicked", callback)
        
        return button
    
    def on_enable_clicked(self, button):
        """Enable the firewall via the pkexec'd helper."""
        dialog = ProgressDialog(self.window, _("Firewall — Enable"), _("Enabling Firewall..."))
        dialog.present()
        dialog.run_action("enable", self._on_enable_done)

    def _on_enable_done(self, success, error_detail):
        if success:
            self.show_message(_("Firewall Enabled"),
                              _("The Firewall has been enabled successfully."),
                              kind="success")
        else:
            body = _("Failed to enable the firewall.")
            if error_detail:
                body += f"\n\n{error_detail}"
            self.show_message(_("Error"), body, kind="error")

    def on_disable_clicked(self, button):
        """Disable the firewall via the pkexec'd helper."""
        dialog = ProgressDialog(self.window, _("Firewall — Disable"), _("Disabling Firewall..."))
        dialog.present()
        dialog.run_action("disable", self._on_disable_done)

    def _on_disable_done(self, success, error_detail):
        if success:
            self.show_message(_("Firewall Disabled"),
                              _("The Firewall has been disabled successfully."),
                              kind="success")
        else:
            body = _("Failed to disable the firewall.")
            if error_detail:
                body += f"\n\n{error_detail}"
            self.show_message(_("Error"), body, kind="error")
    
    def on_status_clicked(self, button):
        """Show the current firewall status."""
        try:
            result = subprocess.run(
                ["systemctl", "is-active", "firewalld"],
                capture_output=True,
                text=True
            )
            is_active = result.stdout.strip() == "active"
        except Exception:
            is_active = False
        
        if is_active:
            status_text = _("ENABLED")
        else:
            status_text = _("DISABLED")

        # Simple text status -> house-style result dialog. The glyph carries the
        # semantic colour (green ✓ when enabled, red ✕ when disabled).
        LiteDialog.result(
            self.window,
            "success" if is_active else "error",
            _("Firewall Status"),
            _("The Firewall is currently: {status}").format(status=status_text),
        )

    def show_message(self, title, message, kind="info"):
        """Show a simple house-style result dialog."""
        LiteDialog.result(self.window, kind, title, message)


def main():
    app = FirewallConfigApp()
    return app.run(None)


if __name__ == "__main__":
    main()
