#!/usr/bin/env python3
"""
Manages Flatpak package actions.

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

import re
import logging
import subprocess
import gettext
import threading
import queue

from app_shared import (
    AppStreamPackageInfo,
    PackageAction,
    DebSoftwareApp,
    LogLevel
)

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


class FlatpakPackageHandler(threading.Thread):
    """
    Manages Flatpak package actions.
    """
    def __init__(self,
                 app: DebSoftwareApp,
                 action: PackageAction,
                 packages: list[AppStreamPackageInfo] = None,
                 result_queue: queue.Queue = None,
                 on_done=None) -> None:
        super().__init__(daemon=True)
        self.app: DebSoftwareApp = app
        self.logging: logging = app.logging
        self.result_queue: queue.Queue = result_queue
        self.packages: list[AppStreamPackageInfo] = packages
        self.action: PackageAction = action
        self.on_done: callable = on_done

        self.package_ids: list[str] = self.search_package_ids()

    # =========================================
    # Global methods
    # =========================================

    def queue_message(self,
                      level: LogLevel,
                      message: str,
                      percentage: int) -> None:
        """
        Add a message to the queue with its level (info/error).
        
        Args:
            level (LogLevel): The level of the message.
            message (str): The message to add to the queue.
            percentage (int): The progress percentage.
        """
        # Add the message to the queue
        if self.result_queue is not None:
            info_item = {'level': level,
                         'message': message,
                         'progress': percentage}
            self.result_queue.put(info_item)

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

    def search_package_ids(self) -> list[str]:
        """ Searches for package ids. """
        return [pkg.package_id for pkg in self.packages]

    def run(self) -> None:
        if self.action not in (
            PackageAction.INSTALL,
            PackageAction.REMOVE,
            PackageAction.UPDATE
        ):
            self.queue_message(LogLevel.ERROR, _("Unknown action."), 0)
            return

        if not self.package_ids:
            self.queue_message(LogLevel.ERROR, _("No matching packages were found."), 0)
            return

        total = len(self.package_ids)
        done = 0

        for pkg_id in self.package_ids:
            done += 1
            before_pct = int((done - 1) / total * 100)
            after_pct = int(done / total * 100)

            command = ["flatpak", "install", "-y", "--noninteractive", "flathub", pkg_id]
            if self.action == PackageAction.REMOVE:
                command = ["flatpak", "uninstall", "-y", "--noninteractive", pkg_id]
            elif self.action == PackageAction.UPDATE:
                command = ["flatpak", "update", "-y", "--noninteractive", pkg_id]

            msg = self._status_message() + f" {pkg_id}"
            self.queue_message(LogLevel.PROGRESS, msg, before_pct)

            # Run command with streaming output
            ok = self._run_flatpak_command(command, pkg_id)
            if not ok:
                continue

            # Success message
            msg = _("Finished") + f" {msg.lower()}"
            self.queue_message(LogLevel.SUCCESS, msg, after_pct)

        if self.on_done:
            self.on_done()

    # =========================================
    # Private methods
    # =========================================

    def _run_flatpak_command(self, command: list[str], pkg_id: str) -> bool:

        with subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            bufsize=1,
            env={"LANG": "C", "LC_ALL": "C"}
        ) as process:

            status_text = self._status_message() + f" {pkg_id}..."

            # Read stderr line-by-line (Flatpak prints progress here)
            for line in process.stderr:
                line = line.strip()
                if not line:
                    continue

                pct = self._extract_percentage(line)
                self.queue_message(LogLevel.PROGRESS, status_text, pct)

                # Log raw Flatpak output for debugging
                self.logging.debug(f"flatpak: {line}")

            returncode = process.wait()

            if returncode != 0:
                self.queue_message(
                    LogLevel.ERROR,
                    _("Flatpak failed with exit code %s") % returncode,
                    0
                )
                return False

        return True

    def _extract_percentage(self, line: str) -> int:
        match = re.search(r"(\d+)%", line)
        return int(match.group(1)) if match else 0

    def _status_message(self) -> str:
        if self.action == PackageAction.INSTALL:
            return _("Installing")
        if self.action == PackageAction.REMOVE:
            return _("Removing")
        if self.action == PackageAction.UPDATE:
            return _("Updating")
        return _("Working")
