#!/usr/bin/env python3
"""
Main controller for Debian Software Manager.
"""

import argparse
import queue
import threading
import os
import subprocess
import shutil
import gettext
import apt

from apt_handler import AptPackageHandler
from flatpak_handler import FlatpakPackageHandler
from fwupd_handler import FwupdHandler
from main_model import MainModel
from main_view import MainView
from logging_config import logger, LOG_PATH
from settings_model import SettingsModel
from settings_view import SettingsView
from settings_controller import SettingsController
from app_shared import (
    PackageAction,
    PackageState,
    PackageType,
    LogLevel,
    show_dialog,
    DialogIcon,
    LogLevel_To_DialogIcon,
    open_file_in_default_app
)

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib, Gio  #pylint: disable=wrong-import-position


_ = gettext.translation('deb-software', fallback=True).gettext

class MainController(Gtk.Application):
    """Main application class: handles events, coordinates Model and View."""

    def __init__(self):
        super().__init__(
            application_id='org.solydxk.deb_software',
            flags=Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_COMMAND_LINE
        )
        self.app_name = "Debian Software Manager"
        self.model = None
        self.view = None
        self.selected_package = None
        self.selected_screenshot = 0
        self.saved_action = None

        self.parser_args = None
        self.pending_files = None

        self.settings_model = None
        self.settings_view = None
        self.settings_controller = None

        self.fwupd_client = None

    # --- Application Lifecycle ---
    def do_command_line(self, command_line, *args):  # pylint: disable=arguments-differ
        """Handle command line arguments."""
        all_args = command_line.get_arguments()[1:]  # Skip script name

        parser = argparse.ArgumentParser()
        parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode")
        parser.add_argument("-c", "--category", help="Show packages for a specific category")
        parser.add_argument("-u", "--update", action="store_true",
                        help="Show packages for updates. Short for '--category=updates'")
        parser_args, remaining_args = parser.parse_known_args(all_args)

        # Store for later use
        self.parser_args = parser_args
        self.pending_files = remaining_args

        self.activate()
        return 0

    def do_activate(self, *args, **kwargs):
        """Called when the application is activated."""
        self.model = MainModel(self)
        self.view = MainView(self, self.model)

        # Settings
        self.settings_model = SettingsModel(self.model)
        self.settings_view = SettingsView(self.settings_model)
        self.settings_controller = SettingsController(
            self,
            self.settings_model,  # Pass the Model (for data)
            self.settings_view,  # Pass the SettingsView
            self.view,  # Pass the View (for UI updates)
        )
        # Optionally store it in the model if needed elsewhere
        self.model.settings_controller = self.settings_controller

        # Pass command line args to the model AFTER it is initialized
        if hasattr(self, 'parser_args'):
            self.model.parser_args = self.parser_args
        if hasattr(self, 'pending_files'):
            self.model.pending_files = self.pending_files

        # Get the main window and show it
        self.view.window.set_application(self)
        self.view.window.present()

        # Start heavy work
        GLib.idle_add(self._start_background_thread, self._initialize_app_thread)

    def _start_background_thread(self, function):
        """Start a function in a background thread."""
        threading.Thread(target=function, daemon=True).start()
        return False

    def _initialize_app_thread(self):
        """Initialize the application in a worker thread."""
        GLib.idle_add(self._initialize_app_finish)

    def _initialize_app_finish(self):
        """Finish initialization on the main thread."""
        self.view.build_category_buttons()
        self.model.set_updates_label(self.view.updates_label)

        # Process any pending files from command line
        if self.model.pending_files:
            pk_info_list = self.model.downloads_handler.get_packages_info(self.model.pending_files)
            if not pk_info_list:
                self.model.pending_files = []
            else:
                self.model.software_manager.updates = pk_info_list
                self.model.software_manager.upgradable_count = len(pk_info_list)
                self.view.updates_label.set_text(_("Downloads"))
                self.view.update_all_label.set_text(_("Install all"))
                self.on_button_clicked(self.view.builder.get_object('btn_updates'), 'updates')

        return False

    # --- Event Handlers ---
    def on_button_clicked(self, button, category):
        """Show category apps."""
        logger.info("Show category: %s", category)
        self.view.sum_update.set_visible(False)
        self.view.save_settings.set_visible(False)
        self.view.sum_title.set_label(self.view.get_button_text(button))
        self.view.notebook.set_current_page(0)
        self.view.summary_listbox.remove_all()

        if category == 'favorites':
            for pk_info in self.model.favorites:
                self.view.add_app_summary(pk_info)
        elif category == 'installed':
            for pk_info in self.model.software_manager.get_installed_packages_summary(
                    components_filter=PackageType.APP
                ):
                self.view.add_app_summary(pk_info)
        elif category == 'updates':
            if self.model.software_manager.updates:
                for pk_info in self.model.software_manager.updates:
                    self.view.add_app_summary(pk_info)
                self.view.sum_update.set_visible(True)
            else:
                self.view.show_no_updates()
        elif category == 'addons':
            for pk_info in self.model.software_manager.addons:
                self.view.add_app_summary(pk_info)
        elif category == 'settings':
            self._show_settings()
        else:
            for pk_info in self.model.software_manager.filter_components(
                components_filter=PackageType.APP,
                category_filter=category
            ):
                self.view.add_app_summary(pk_info)

        # Reset scroll position
        vadjustment = self.view.summary_sw.get_vadjustment()
        vadjustment.set_value(0)
        button.grab_focus()

    def on_update_all_clicked(self, _button):
        """Update all packages."""
        apt_packages = []
        flatpak_packages = []
        fwupd_packages = []
        result_queue = queue.Queue()
        self.selected_package = None

        for package in self.model.software_manager.updates:
            if "flatpak" in package.origin:
                flatpak_packages.append(package)
            elif package.package_type == PackageType.RUNTIME:  # fwupd packages
                fwupd_packages.append(package)
            else:
                apt_packages.append(package)

        if apt_packages:
            thread = AptPackageHandler(
                script_dir=self.model.script_dir,
                action=PackageAction.SYSTEM_UPGRADE,
                packages=apt_packages,
                result_queue=result_queue
            )
            self._thread_starter(thread=thread, result_queue=result_queue)

        if flatpak_packages:
            thread = FlatpakPackageHandler(
                action=PackageAction.UPDATE,
                packages=flatpak_packages,
                result_queue=result_queue
            )
            self._thread_starter(thread=thread, result_queue=result_queue)

        if fwupd_packages:
            thread = FwupdHandler(
                action=PackageAction.UPDATE,
                packages=fwupd_packages,
                result_queue=result_queue
            )
            self._thread_starter(thread=thread, result_queue=result_queue)

    def on_search_activate(self, entry):
        """Show search results."""
        self.view.sum_update.set_visible(False)
        self.view.save_settings.set_visible(False)
        self.view.sum_title.set_label(_("Search results"))
        self.view.notebook.set_current_page(0)
        self.view.summary_listbox.remove_all()
        query = entry.get_text()
        results = self.model.software_manager.search_packages(query)
        logger.info("Show search results: %s - %d results", query, len(results))
        for result in results:
            self.view.add_app_summary(result)

    def on_clear_search(self, entry, icon_position):
        """Handle the icon-release event for the package search entry."""
        if icon_position == Gtk.EntryIconPosition.PRIMARY:
            self.on_search_activate(entry)
        else:
            entry.set_text('')

    def on_screenshot_clicked(self, _gesture, _n_press, _x, _y):
        """Show next screenshot."""
        if not self.selected_package or len(self.selected_package.screenshots) <= 1:
            return

        self.selected_screenshot += 1
        if self.selected_screenshot == len(self.selected_package.screenshots):
            self.selected_screenshot = 0

        img_url = self.selected_package.screenshots[self.selected_screenshot]
        logger.info("Show screenshot: %s", img_url)
        img = self.model.image_handler.load_image_from_url(self.model.cache_dir, img_url)
        if img is not None:
            self.view.app_screenshot.set_paintable(img)

    def on_summary_clicked(self, _gesture, _n_press, _x, _y, pk_info):
        """Show package details."""
        logger.info(
            "Show package details: %s with origin: %s", pk_info.package_id, pk_info.origin
        )
        if not pk_info.download_path:
            pk_info = self.model.get_package_details(pk_info.package_id, pk_info.origin)
        self.view.show_package_details(pk_info)

    def on_activate_link(self, _label, uri):
        """Open the URL in the default browser."""
        launcher = Gtk.UriLauncher(uri=uri)
        launcher.launch(self.view.window)
        return True

    def on_app_back_clicked(self, _button):
        """Go back to the summary page."""
        self.view.notebook.set_current_page(0)

    def on_app_install_clicked(self, _button):
        """Install the package."""
        self._single_package_action(PackageAction.INSTALL)

    def on_app_remove_clicked(self, _button):
        """Remove the package."""
        self._single_package_action(PackageAction.REMOVE)

    def on_app_update_clicked(self, _button):
        """Update the package."""
        self._single_package_action(PackageAction.UPDATE)

    def on_app_launch_clicked(self, _button):
        """Launch the package."""
        if not self.selected_package:
            return

        command = [self.selected_package.executable]
        origin = self.selected_package.origin
        if origin == 'flatpak':
            command = ['flatpak', 'run', self.selected_package.package_id]

        try:
            with subprocess.Popen(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            ) as process:
                _stdout, stderr = process.communicate()
                if process.returncode != 0:
                    logger.error(
                        "Command failed: %s\n"
                        "Exit code: %d\n"
                        "stderr: %s",
                        ' '.join(command),
                        process.returncode,
                        stderr.strip()
                    )
                else:
                    logger.info("Run command: %s.", ' '.join(command))
        except Exception as e:  # pylint: disable=broad-except
            logger.error("Failed to start command: %s: %s", ' '.join(command), e)

    def on_button_log_clicked(self, _button):
        """Open the log file in a text editor."""
        if open_file_in_default_app(LOG_PATH):
            logger.info("Open log file: %s", LOG_PATH)
        else:
            logger.warning("Failed to open log file: %s", LOG_PATH)
            show_dialog(
                "Debian Software Manager",
                _("Failed to open file: %s", LOG_PATH),
                self.view.window,
                DialogIcon.WARNING
            )

    def on_button_help_clicked(self, _button):
        """Open the help file in the default browser."""
        help_path = os.path.join(self.model.script_dir, "help.html")

        if open_file_in_default_app(help_path):
            logger.info("Open help file: %s", help_path)
        else:
            logger.warning("Failed to open help file: %s", help_path)
            show_dialog(
                "Debian Software Manager",
                _("Failed to open file: %s", help_path),
                self.view.window,
                DialogIcon.WARNING
            )

    def on_button_about_clicked(self, _button):
        """Show the about window."""
        try:
            version = apt.Cache()['deb-software'].versions[0].source_version
        except KeyError:
            version = ''

        about = Gtk.AboutDialog(
            transient_for=self.view.window,
            modal=True,
            program_name="Debian Software Manager",
            version=version,
            copyright=f"© 2026 {self.model.config.get('authors')[0]}",
            license_type=Gtk.License.GPL_3_0,
            website=self.model.config.get("home"),
            website_label="Website",
            authors=self.model.config.get("authors"),
            logo_icon_name=self.get_application_id()
        )
        about.present()

    def on_button_refresh_clicked(self, _button):
        """Refresh apt and appstream data. Clear the cache dir."""
        logger.info("Refresh the list of packages.")
        shutil.rmtree(self.model.cache_dir, ignore_errors=True)
        os.makedirs(self.model.cache_dir, exist_ok=True)

        result_queue = queue.Queue()
        thread = AptPackageHandler(
            script_dir=self.model.script_dir,
            action=PackageAction.REFRESH_CACHE,
            result_queue=result_queue
        )
        self._thread_starter(thread=thread, result_queue=result_queue)
        self.refresh_data()

    def on_save_settings_button_clicked(self, _button):
        """Save the settings."""
        self.view.save_settings.set_sensitive(False)
        self.model.settings_controller.save_all(
            self.view.repo_checkbuttons,
            getattr(self.view, 'interval_combo', None),
            self.view.checker_check
        )

    # --- Helper Methods ---
    def _show_settings(self):
        """Show settings."""
        self.view.sum_title.set_label(_("Settings"))

        self.view.repo_row, self.view.repo_checkbuttons = \
            self.settings_view.create_repositories_row()
        self.view.summary_listbox.append(self.view.repo_row)

        if shutil.which('flatpak') is not None:
            self.view.interval_row, self.view.interval_combo = \
                self.settings_view.create_interval_row()
            self.view.summary_listbox.append(self.view.interval_row)

        self.view.checker_row, self.view.checker_check = \
            self.settings_view.create_update_indicator_row()
        self.view.summary_listbox.append(self.view.checker_row)

        self.view.save_settings.set_visible(True)

    def _single_package_action(self, action: PackageAction):
        """Perform a single package action."""
        self.saved_action = action
        result_queue = queue.Queue()

        if action in (PackageAction.INSTALL, PackageAction.UPDATE):
            self.selected_package.is_updatable = False
        elif PackageAction.REMOVE in action:
            self.selected_package = None

        if action not in (PackageAction.INSTALL, PackageAction.REMOVE, PackageAction.UPDATE):
            logger.error("Unknown action: %s", action)
            return

        device = getattr(self.selected_package, 'fwupd_device', None)

        if device:
            # Fwupd can only update
            thread = FwupdHandler(
                action=PackageAction.UPDATE,
                packages=[self.selected_package],
                result_queue=result_queue
            )
        elif "flatpak" in self.selected_package.origin:
            thread = FlatpakPackageHandler(
                action=action,
                packages=[self.selected_package],
                result_queue=result_queue
            )
        else:
            thread = AptPackageHandler(
                script_dir=self.model.script_dir,
                action=action,
                packages=[self.selected_package],
                result_queue=result_queue
            )

        self._thread_starter(thread=thread, result_queue=result_queue)

    def _thread_starter(self, thread, result_queue):
        """Start a thread and check its progress."""
        message = ''
        level = LogLevel.INFO
        state = PackageState.INSTALLED
        subject = _("Installing")

        if thread.action == PackageAction.REMOVE:
            state = PackageState.REMOVED
            subject = _("Removing")
        elif thread.action == PackageAction.UPDATE:
            state = PackageState.UPDATED
            subject = _("Updating")
        elif thread.action == PackageAction.REFRESH_CACHE:
            state = None
            subject = _("Updating cache")

        # Disable buttons while running
        self.view.sum_update.set_sensitive(False)
        self.view.app_install.set_sensitive(False)
        self.view.app_remove.set_sensitive(False)
        self.view.app_update.set_sensitive(False)

        thread.start()

        def update_progress():
            nonlocal message
            nonlocal level
            nonlocal state

            if not result_queue.empty():
                dict_info = result_queue.get()
                level = dict_info['level']
                message = dict_info['message']

                if level == LogLevel.ERROR:
                    show_dialog(subject, message, self.view.window, DialogIcon.ERROR)
                    self.view.set_progress(0, "")
                    self.view.sum_update.set_sensitive(True)
                    self.view.app_install.set_sensitive(True)
                    self.view.app_remove.set_sensitive(True)
                    self.view.app_update.set_sensitive(True)
                    return False

                self.view.set_progress(dict_info['progress'], message \
                    if level != LogLevel.SUCCESS else "")

            if not thread.is_alive() and result_queue.empty():
                if level != LogLevel.CANCELED:
                    # state = INSTALLED/REMOVED/UPDATED/None
                    GLib.idle_add(self.refresh_data, state)

                self.view.clear_progress()
                self.view.sum_update.set_sensitive(True)
                self.view.app_install.set_sensitive(True)
                self.view.app_remove.set_sensitive(True)
                self.view.app_update.set_sensitive(True)

                # Show dialog
                show_dialog(
                    subject,
                    message,
                    self.view.window,
                    LogLevel_To_DialogIcon.get(level, DialogIcon.INFO)
                )

                return False
            return True

        GLib.timeout_add(100, update_progress)

    def refresh_data(self, state: PackageState = None, show_updates: bool = True):
        """Refresh updates."""
        self.model.refresh_data()

        if state is not None:
            # Reload selected package AFTER refresh
            self._reload_selected_package(state)

        if show_updates:
            self.view.sum_update.set_visible(False)
            self.view.summary_listbox.remove_all()

            if self.model.software_manager.updates:
                for pk_info in self.model.software_manager.updates:
                    self.view.add_app_summary(pk_info)
                self.view.sum_update.set_visible(True)
            else:
                self.view.show_no_updates()
            self.view.sum_title.set_label(self.model.get_category_translation("updates"))

        self.model.set_updates_label(self.view.updates_label)

    def _reload_selected_package(self, state: PackageState = None):
        """Reload metadata for the currently selected package."""
        if not self.selected_package:
            return False

        pkgname = self.selected_package.package_id
        origin = self.selected_package.origin

        # Rebuild package object from backend
        new_pkg = self.model.get_package_details(pkgname, origin)

        logger.debug("Reloaded: installed=%s version=%s exec=%s",
            new_pkg.is_installed, new_pkg.version, new_pkg.executable)

        # Replace stale object
        self.selected_package = new_pkg

        # Update UI
        self.view.show_package_details(new_pkg)
        self.view.set_button_state(state)

        return False
