"""Workers QThread para no congelar la GUI durante git/build/run.

Cada worker es un QRunnable que ejecuta una operación bloqueante (download o run) y
emite señales hacia la UI a través de un objeto de señales propio.
"""

from __future__ import annotations

from pathlib import Path

from PySide6.QtCore import QObject, QRunnable, Signal

from . import gitops, runner
from .config import Game
from .metadata import GameMeta


class _Signals(QObject):
    log = Signal(str)            # línea de log
    finished = Signal(object)    # payload según el worker (GameMeta o int exit code)
    error = Signal(str)          # mensaje de error
    result = Signal(str, bool)   # (game_id, has_update) — CheckUpdatesWorker
    progress = Signal(int, int)  # (done, total) — CheckUpdatesWorker


class DownloadWorker(QRunnable):
    """Clona o actualiza (forzado) y refresca la metadata de un juego."""

    def __init__(
        self,
        root: Path,
        game: Game,
        token: str = "",
        net: gitops.NetConfig = gitops.DEFAULT_NET,
    ) -> None:
        super().__init__()
        self.root = root
        self.game = game
        self.token = token
        self.net = net
        self.signals = _Signals()

    def run(self) -> None:  # noqa: D401 - API de QRunnable
        try:
            meta: GameMeta = gitops.download(
                self.root,
                self.game,
                log=self.signals.log.emit,
                token=self.token,
                net=self.net,
            )
        except Exception as exc:  # noqa: BLE001 - reportar a la UI
            self.signals.error.emit(str(exc))
            return
        self.signals.finished.emit(meta)


class RunWorker(QRunnable):
    """Compila (si procede) y ejecuta el juego."""

    def __init__(self, root: Path, game: Game) -> None:
        super().__init__()
        self.root = root
        self.game = game
        self.signals = _Signals()

    def run(self) -> None:  # noqa: D401 - API de QRunnable
        try:
            code = runner.run_game(self.root, self.game, log=self.signals.log.emit)
        except Exception as exc:  # noqa: BLE001 - reportar a la UI
            self.signals.error.emit(str(exc))
            return
        self.signals.finished.emit(code)


class CheckUpdatesWorker(QRunnable):
    """Comprueba actualizaciones pendientes de los juegos ya descargados.

    Emite `result(game_id, has_update)` por cada juego instalado y `finished` al final.
    """

    def __init__(
        self,
        root: Path,
        games: list[Game],
        token: str = "",
        net: gitops.NetConfig = gitops.DEFAULT_NET,
    ) -> None:
        super().__init__()
        self.root = root
        self.games = games
        self.token = token
        self.net = net
        self.signals = _Signals()

    def run(self) -> None:  # noqa: D401 - API de QRunnable
        try:
            # Total = juegos descargados (los no instalados no se comprueban). Emitimos
            # progress por cada juego *intentado*, éxito o error, para que la barra
            # llegue siempre al final aunque algún repo dé timeout.
            installed = [g for g in self.games if gitops.is_installed(self.root, g)]
            total = len(installed)
            self.signals.progress.emit(0, total)
            for done, game in enumerate(installed, start=1):
                try:
                    has_update = gitops.check_update(
                        self.root,
                        game,
                        log=self.signals.log.emit,
                        token=self.token,
                        net=self.net,
                    )
                except Exception as exc:  # noqa: BLE001 - no abortar el resto
                    self.signals.log.emit(f"check {game.id}: {exc}")
                else:
                    self.signals.result.emit(game.id, has_update)
                self.signals.progress.emit(done, total)
        except Exception as exc:  # noqa: BLE001 - reportar a la UI
            self.signals.error.emit(str(exc))
            return
        self.signals.finished.emit(None)
