#!/usr/bin/env python3
"""
Favorites tab for SolydXK System

Author: Arjen Balfoort
Date: 2026-04-23
"""

import gettext
import logging
import threading
from favorites import FavoriteApps, AppStreamPackageInfo
from utils import (
    show_dialog,
    DialogIcon,
    DebSoftwareApp,
    LogLevel,
    LogLevel_To_DialogIcon
)

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


_ = gettext.translation('solydxk-system', fallback=True).gettext


class TabFavorites:
    """
    Favorites tab for SolydXK System
    """
    def __init__(self, app: DebSoftwareApp) -> None:
        self.app: DebSoftwareApp = app
        self.logging: logging = self.app.logging
        self.install_button: Gtk.Button = self.app.builder.get_object('btn_fav_install')
        self.favorites_apps: FavoriteApps = FavoriteApps(self.app.logging)
        self.favorites: list[AppStreamPackageInfo] = self.favorites_apps.favorites
        self.client: PackageKitGlib.Client = PackageKitGlib.Client()
        self.selected_packages: list[dict] = []
        self.package_ids: list[str] = []
        self._installed: bool = False

        # Add Install button click handler
        self.install_button.connect("clicked", self.on_fav_install_clicked)

    def setup_favorites_tab(self, refresh_favorites: bool=False) -> None:
        """
        Populate the Favorites tab with a list of favorite applications.
        """
        # Reset the progress bar and enable the Install button
        self.app.progressbar.set_fraction(0)
        self.app.progressbar_label.set_text("")
        self.install_button.set_sensitive(True)

        if refresh_favorites:
            # Clear the list of favorite packages
            self.app.listbox_favorites.remove_all()

            # Update the list of favorite packages
            for favorite in self.favorites:
                for sel_pkg in self.selected_packages:
                    if favorite.package_id == sel_pkg.get("package_id"):
                        favorite.is_installed = True
        else:
            if self.app.listbox_favorites.get_row_at_index(0) is not None:
                return

        for pk_info in self.favorites:
            package_box = self.favorites_apps.get_gtk_box_summary(pk_info)
            self.app.listbox_favorites.append(package_box)
            # Create a gesture for the box
            click_gesture = Gtk.GestureClick()
            click_gesture.connect("pressed", self.on_summary_clicked, pk_info.package_id)
            package_box.add_controller(click_gesture)

    def on_summary_clicked(self, _gesture, _n_press, _x, _y, package_id) -> None:
        """ Handle the click on a favorite package. """
        # Iterate through all rows in the listbox
        for row in self.app.listbox_favorites:
            child = row.get_child()
            if isinstance(child, Gtk.Box) and isinstance(child.get_last_child(), Gtk.CheckButton):
                # Check if the checkbox is checked
                checkbox = child.get_last_child()
                if checkbox.package_id == package_id:
                    checkbox.set_active(not checkbox.get_active())
                    break

    def on_fav_install_clicked(self, _button: Gtk.Button) -> None:
        """Handle the Install button click."""
        # Disable immediately to prevent double-clicks
        self.install_button.set_sensitive(False)

        # Clear the list of selected packages
        self.selected_packages.clear()

        # Iterate through all rows in the listbox
        for row in self.app.listbox_favorites:
            child = row.get_child()
            if isinstance(child, Gtk.Box) and isinstance(child.get_last_child(), Gtk.CheckButton):
                # Check if the checkbox is checked
                checkbox = child.get_last_child()
                if checkbox.get_active() and checkbox.package_id and checkbox.package_name:
                    self.selected_packages.append(
                        {'package_name': checkbox.package_name, 'package_id': checkbox.package_id}
                    )

        if not self.selected_packages:
            show_dialog(_("Favorites"), _("No packages selected for installation."),
                        self.app.window, DialogIcon.WARNING)
            return

        # Run the heavy work in a background thread
        worker = threading.Thread(target=self._install_worker, daemon=True)
        worker.start()

    def search_package_ids(self) -> list[str]:
        """ Searches for package ids. """
        ids: list[str] = []
        for pkg in self.selected_packages:
            try:
                results = self.client.search_names(
                    PackageKitGlib.FilterEnum.NONE,
                    [pkg.get("package_name")],
                    None,
                    self._progress_callback,
                    None,
                )
            except GLib.Error as e:
                self.manage_message(LogLevel.ERROR, str(e), 0)
                continue

            for p in results.get_package_array():
                if p.get_name() == pkg.get("package_name"):
                    ids.append(p.get_id())
                    break

        return ids

    def manage_message(self,
                       level: LogLevel,
                       message: str,
                       percentage: int) -> None:
        """
        Schedule progress bar and log updates on the main thread.
        """
        def _update():
            # GTK updates must be on the main thread
            self.app.progressbar.set_fraction(percentage / 100)
            self.app.progressbar_label.set_text(message)

            if self.logging is not None and message:
                msg = message.replace('\n', ' ')
                if level in (LogLevel.INFO, LogLevel.SUCCESS, LogLevel.PROGRESS):
                    self.logging.info(msg)
                elif level in (LogLevel.WARNING, LogLevel.CANCELED):
                    self.logging.warning(msg)
                elif level == LogLevel.ERROR:
                    self.logging.error(msg)
                elif level == LogLevel.DEBUG:
                    self.logging.debug(msg)

            # Show dialog on success or canceled
            if level in (LogLevel.SUCCESS, LogLevel.CANCELED):
                show_dialog(_("Favorites"),
                            message,
                            self.app.window,
                            LogLevel_To_DialogIcon.get(level, DialogIcon.INFO))

            # Returning False would remove this idle source; True keeps it.
            return False

        GLib.idle_add(_update)

    def _install_worker(self) -> None:
        """
        Worker thread: search package IDs and install them.
        """
        try:
            self.package_ids = self.search_package_ids()

            if not self.package_ids:
                self.manage_message(LogLevel.WARNING,
                                    _("No matching packages found for selection."),
                                    0)
                return

            # Install packages
            self._install_packages()
        except Exception as e:
            # Catch-all to avoid killing the thread silently
            self.manage_message(LogLevel.ERROR, str(e), 0)
        finally:
            # Refresh the favorites tab
            GLib.idle_add(self.setup_favorites_tab, self._installed)

    def _install_packages(self) -> None:
        self._installed = False
        if not self.selected_packages:
            return

        try:
            msg = _("Installing") + " " + ', '.join(
                [pkg.get("package_name") for pkg in self.selected_packages]
            )
            self.manage_message(LogLevel.PROGRESS, msg, 0)
            results = self.client.install_packages(
                PackageKitGlib.TransactionFlagEnum.NONE,
                self.package_ids,
                self._new_cancellable(),
                self._progress_callback,
                None,
            )
            self._store_results(results)
        except GLib.Error as e:
            self._store_error(e)

    def _new_cancellable(self):
        return Gio.Cancellable()

    def _progress_callback(
        self,
        progress: PackageKitGlib.Progress,
        progress_type: PackageKitGlib.ProgressType,
        _user_data=None,
    ) -> None:
        if progress_type == PackageKitGlib.ProgressType.PERCENTAGE:
            pct = max(0, progress.get_percentage())
            if pct > 0:
                self.manage_message(LogLevel.PROGRESS, _("Installing"), pct)

    def _store_results(self, results) -> None:
        exit_status = results.get_exit_code()
        error = results.get_error_code()

        if exit_status == PackageKitGlib.ExitEnum.SUCCESS:
            self._installed = True
            pkgs = ', '.join([pkg.get("package_name") for pkg in self.selected_packages])
            self.manage_message(LogLevel.SUCCESS,
                               _("Finished installing") + f" {pkgs}",
                               100)
        elif error is not None:
            error_details = error.get_details()
            self.manage_message(LogLevel.ERROR, error_details, 0)

    def _store_error(self, error: GLib.Error) -> None:
        # Skip user not providing sudo password
        if error.code == 303:
            self.manage_message(LogLevel.CANCELED, _("Canceled by user"), 0)
            return
        self.manage_message(LogLevel.ERROR, str(error), 0)
