#!/usr/bin/env python3

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# Author: Stefano Capitani <stefano@manjarodotorg>

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, GLib, Gio, Adw

import sys
import os
import subprocess
import gettext
import pathlib
import threading

# Translation

TEXTDOMAIN    = 'manjaro-pacnew-checker'
TEXTDOMAINDIR = '/usr/share/locale'

gettext.bindtextdomain(TEXTDOMAIN, TEXTDOMAINDIR)
gettext.textdomain(TEXTDOMAIN)
TRANSL = gettext.gettext

# Constants

ICON     = 'pacnew-checker'
TITLE    = 'Pacnew Checker'
TITLE1   = 'Management of Pacnew/Pacsave files'
UI_FILE  = pathlib.Path(__file__).parent / 'ui' / 'main.ui'
WIKI_URL  = 'https://wiki.manjaro.org/index.php/System_Maintenance/en#Pacnew_and_Pacsave_files'
FORUM_URL = 'https://forum.manjaro.org/'
HELPER = '/usr/lib/pacnew-checker/pacnew-helper'


def wiki_link():
    """Open the Manjaro wiki page about Pacnew/Pacsave files."""
    Gio.AppInfo.launch_default_for_uri(WIKI_URL, None)


def forum_link():
    """Open the Manjaro forum in the default browser."""
    Gio.AppInfo.launch_default_for_uri(FORUM_URL, None)


def pkexec_helper(*args):
    """Run the pacnew-helper via pkexec as root and return the exit code."""
    cmd = ['pkexec', HELPER] + list(args)
    print(f"[DEBUG] pkexec cmd: {cmd}", file=sys.stderr)
    try:
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            print(f"[ERROR] pkexec exit {result.returncode}", file=sys.stderr)
            if result.stdout:
                print(f"[ERROR] stdout: {result.stdout}", file=sys.stderr)
            if result.stderr:
                print(f"[ERROR] stderr: {result.stderr}", file=sys.stderr)
        return result.returncode
    except Exception as e:
        print(f"[ERROR] Exception running pkexec: {e}", file=sys.stderr)
        return -1



# Dialog: Disclaimer

def show_disclaimer(parent):
    """Show the GPL disclaimer dialog."""
    dialog = Gtk.Dialog(
        title=TITLE,
        transient_for=parent,
        modal=True,
    )
    dialog.set_default_size(750, -1)
    dialog.add_button(TRANSL('Ok'), Gtk.ResponseType.OK)

    text = TRANSL(
        "This program is free software, you can redistribute it and/or modify "
        "it under the terms of the GNU General Public License as published by "
        "the Free Software Foundation version 3 of the License.\n"
        "\n"
        "This program is distributed in the hope that it will be useful, but "
        "WITHOUT ANY WARRANTY, without even the implied warranty of "
        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU "
        "General Public License for more details.\n"
        "\n"
        "Author: Stefano Capitani\n"
        "stefano@manjaro.org"
    )

    label = Gtk.Label(label=text)
    label.set_wrap(True)
    label.set_xalign(0.0)
    label.set_margin_start(12)
    label.set_margin_end(12)
    label.set_margin_top(12)
    label.set_margin_bottom(12)

    dialog.get_content_area().append(label)
    dialog.connect('response', lambda d, _r: d.destroy())
    dialog.present()


# Dialog: per-file action choice

def show_file_choice_dialog(parent, basefile, suffix, on_done_callback):
    dialog = Gtk.Dialog(
        title=TITLE1,
        transient_for=parent,
        modal=True,
    )
    dialog.set_default_size(500, 260)

    content = dialog.get_content_area()
    content.set_spacing(6)
    content.set_margin_start(12)
    content.set_margin_end(12)
    content.set_margin_top(12)
    content.set_margin_bottom(6)

    header = Gtk.Label()
    header.set_use_markup(True)
    header.set_wrap(True)
    header.set_xalign(0.0)
    header.set_markup(
         TRANSL("What do you want to do with the file <span foreground='red'>$basefile</span>?\n")
        .replace('$basefile', GLib.markup_escape_text(basefile))
    )
    content.append(header)

    options = [
        ('merge',   TRANSL("View and merge the $suffix file").replace('$suffix', suffix)),
        ('keep',    TRANSL("Keep the original and remove the $suffix file").replace('$suffix', suffix)),
        ('replace', TRANSL("Replace the original with the $suffix file").replace('$suffix', suffix)),
        ('nothing', TRANSL("Do nothing")),
    ]

    radios = []
    group = None
    for value, label in options:
        btn = Gtk.CheckButton(label=label)
        if group is None:
            group = btn
        else:
            btn.set_group(group)
        radios.append((value, btn))
        content.append(btn)

    radios[0][1].set_active(True)

    dialog.add_button(TRANSL('Ok'), Gtk.ResponseType.OK)

    def on_response(dlg, response_id):
        choice = None
        if response_id == Gtk.ResponseType.OK:
            for value, btn in radios:
                if btn.get_active():
                    choice = value
                    break
        dlg.destroy()
        on_done_callback(choice)

    dialog.connect('response', on_response)
    dialog.present()


# Dialog: confirm removal after merge

def show_confirm_remove_dialog(parent, suffix, on_done_callback):
    dialog = Gtk.Dialog(
        title=TITLE1,
        transient_for=parent,
        modal=True,
    )

    content = dialog.get_content_area()
    content.set_margin_start(12)
    content.set_margin_end(12)
    content.set_margin_top(12)
    content.set_margin_bottom(6)

    label = Gtk.Label()
    label.set_use_markup(True)
    label.set_wrap(True)
    label.set_markup(
        TRANSL("Now we will <span foreground='red'>remove</span> the $suffix file")
        .replace('$suffix', GLib.markup_escape_text(suffix))
    )
    content.append(label)

    dialog.add_button(TRANSL('Cancel'), Gtk.ResponseType.CANCEL)
    dialog.add_button(TRANSL('Ok'),     Gtk.ResponseType.OK)

    def on_response(dlg, response_id):
        confirmed = (response_id == Gtk.ResponseType.OK)
        dlg.destroy()
        on_done_callback(confirmed)

    dialog.connect('response', on_response)
    dialog.present()


def _show_error_dialog(parent, message):
    """Display a simple error message dialog."""
    dialog = Gtk.MessageDialog(
        transient_for=parent,
        flags=0,
        message_type=Gtk.MessageType.ERROR,
        buttons=Gtk.ButtonsType.OK,
        text=message,
    )
    dialog.set_modal(True)
    dialog.connect('response', lambda d, r: d.destroy())
    dialog.present()


# merge_function

def merge_function(parent, file, suffix, on_done_callback):
    diffprog = os.environ.get('DIFFPROG')
    orig = file[:file.rfind('.' + suffix)]

    def run_merge_process():
        ret = 0
        if diffprog:
            try:
                subprocess.run(
                    [diffprog, f'admin://{orig}', f'admin://{file}'],
                    check=True
                )
            except (FileNotFoundError, subprocess.CalledProcessError):
                ret = pkexec_helper(
                    'merge', file, '',
                    str(os.getuid()),
                    os.environ.get('DISPLAY', ''),
                    os.environ.get('DBUS_SESSION_BUS_ADDRESS', ''),
                    os.environ.get('XAUTHORITY', ''),
                )
        else:
            ret = pkexec_helper(
                'merge', file, '',
                str(os.getuid()),
                os.environ.get('DISPLAY', ''),
                os.environ.get('DBUS_SESSION_BUS_ADDRESS', ''),
                os.environ.get('XAUTHORITY', ''),
            )
        GLib.idle_add(after_merge_in_main_thread, ret)

    def after_merge_in_main_thread(ret):
        if ret != 0:
            _show_error_dialog(parent, TRANSL("Merge failed (helper returned error)."))
            on_done_callback()
        else:
            def on_remove_confirmed(confirmed):
                if confirmed:
                    pkexec_helper('remove', file)
                on_done_callback()

            show_confirm_remove_dialog(parent, suffix, on_remove_confirmed)

        return False
    threading.Thread(target=run_merge_process, daemon=True).start()


# Main application window

class PacnewCheckerApp(Adw.Application):
    """GTK4 application — replaces the entire pacnew-checker.sh bash script."""

    def __init__(self):
        super().__init__(
            application_id='org.manjaro.PacnewChecker',
            flags=Gio.ApplicationFlags.FLAGS_NONE,
        )
        self._conflict_files = []

    def do_activate(self):
        """Build and present the main window."""
        print("[DEBUG] do_activate called", file=sys.stderr)

        if not os.path.exists('/tmp/pacnew-check.file'):
            print("[DEBUG] /tmp/pacnew-check.file not found, quitting", file=sys.stderr)
            self.quit()
            return

        try:
            result = subprocess.run(
                ['pacdiff', '-o'],
                capture_output=True, text=True
            )
            self._conflict_files = result.stdout.split()
        except FileNotFoundError:
            self._conflict_files = []

        if not self._conflict_files:
            print("[DEBUG] No conflict files found, quitting", file=sys.stderr)
            self.quit()
            return

        print(f"[DEBUG] Conflict files: {self._conflict_files}", file=sys.stderr)
        print(f"[DEBUG] Using helper: {HELPER}", file=sys.stderr)

        builder = Gtk.Builder()
        builder.add_from_file(str(UI_FILE))

        self._window = builder.get_object('main_window')
        self._window.set_application(self)
        self._window.set_title(TITLE)
        self._window.set_icon_name(ICON)

        label_header = builder.get_object('label_header')
        label_header.set_markup(
            TRANSL("<span foreground='red'><b>New Pacnew/Pacsave files found:</b></span>\n\n"
              "Keep in mind:\n"
              "You must be aware of your choices. If you are unsure please inquire "
              "using our social channels such as our support forum.")
        )

        listbox = builder.get_object('listbox_files')
        for f in self._conflict_files:
            row = Gtk.Label(label=f)
            row.set_xalign(0.0)
            row.set_margin_start(6)
            row.set_margin_end(6)
            row.set_margin_top(3)
            row.set_margin_bottom(3)
            listbox.append(row)

        builder.get_object('button_disclaimer').connect(
            'clicked', lambda _b: show_disclaimer(self._window)
        )
        builder.get_object('button_wiki').connect(
            'clicked', lambda _b: wiki_link()
        )
        builder.get_object('button_forum').connect(
            'clicked', lambda _b: forum_link()
        )
        builder.get_object('button_ok').connect(
            'clicked', self._on_ok_clicked
        )

        print("[DEBUG] Presenting main window", file=sys.stderr)
        self._window.present()

    def _on_ok_clicked(self, _button):
        self._window.set_visible(False)
        self._process_files_loop(list(self._conflict_files))

    def _process_files_loop(self, files):
        if not files:
            self._show_done_dialog()
            return

        file      = files[0]
        remaining = files[1:]
        suffix    = file.rsplit('.', 1)[-1] if '.' in file else ''
        basefile  = os.path.basename(file)

        def on_choice(choice):
            if choice == 'merge':
                merge_function(
                    self._window, file, suffix,
                    on_done_callback=lambda: self._process_files_loop(remaining)
                )
                return

            elif choice == 'keep':
                pkexec_helper('remove', file)

            elif choice == 'replace':
                orig = file[:file.rfind('.' + suffix)]
                pkexec_helper('replace', file, orig)

            self._process_files_loop(remaining)

        show_file_choice_dialog(self._window, basefile, suffix, on_choice)

    def _show_done_dialog(self):
        dialog = Gtk.Dialog(
            title=TITLE,
            transient_for=self._window,
            modal=True,
        )
        dialog.set_default_size(400, -1)

        content = dialog.get_content_area()
        content.set_margin_start(12)
        content.set_margin_end(12)
        content.set_margin_top(12)
        content.set_margin_bottom(6)

        label = Gtk.Label(label=TRANSL("Management of Pacnew/Pacsave complete"))
        label.set_margin_top(8)
        label.set_margin_bottom(8)
        content.append(label)

        dialog.add_button(TRANSL('Done'), Gtk.ResponseType.OK)

        def on_response(dlg, _response_id):
            dlg.destroy()
            # Cleanup flag file
            pathlib.Path('/tmp/pacnew-check.file').unlink(missing_ok=True)
            self.quit()

        dialog.connect('response', on_response)
        dialog.present()


def main():
    global HELPER

    if '-d' in sys.argv:
        import logging
        logging.basicConfig(level=logging.DEBUG)
        sys.argv.remove('-d')
        print("[DEBUG] Debug mode enabled", file=sys.stderr)
        print(f"[DEBUG] Helper path: {HELPER}", file=sys.stderr)

    app = PacnewCheckerApp()
    sys.exit(app.run(sys.argv))


if __name__ == '__main__':
    main()