from collections import deque
from enum import IntEnum
from logging import getLogger
from typing import Deque, Optional

from legendary.models.game import Game, InstalledGame
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtWidgets import (
    QGroupBox,
    QLabel,
    QSizePolicy,
    QVBoxLayout,
    QWidget,
)

from rare.components.tabs.downloads.widgets import QueueWidget, UpdateWidget
from rare.lgndr.core import LegendaryCore
from rare.models.install import InstallOptionsModel, InstallQueueItemModel
from rare.shared.image_manager import ImageManager
from rare.utils.misc import widget_object_name

logger = getLogger("QueueGroup")


class UpdateGroup(QGroupBox):
    update_count = Signal(int)
    enqueue = Signal(InstallOptionsModel)

    def __init__(self, imgmgr: ImageManager, parent=None):
        super(UpdateGroup, self).__init__(parent=parent)
        self.setObjectName(type(self).__name__)
        self.setTitle(self.tr("Updates"))
        self.__text = QLabel(self.tr("No updates available"))
        self.__text.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

        # lk: For findChildren to work, the update's layout has to be in a widget
        self.__container = QWidget(self)
        self.__container.setVisible(False)
        container_layout = QVBoxLayout(self.__container)
        container_layout.setContentsMargins(0, 0, 0, 0)

        layout = QVBoxLayout(self)
        layout.addWidget(self.__text)
        layout.addWidget(self.__container)

        self.imgmgr = imgmgr

    def __find_widget(self, app_name: str) -> Optional[UpdateWidget]:
        return self.__container.findChild(UpdateWidget, name=widget_object_name(UpdateWidget, app_name))

    def count(self) -> int:
        return len(self.__container.findChildren(UpdateWidget, options=Qt.FindChildOption.FindDirectChildrenOnly))

    def contains(self, app_name: str) -> bool:
        return self.__find_widget(app_name) is not None

    def __update_group(self):
        count = self.count()
        self.__text.setVisible(not count)
        self.__container.setVisible(bool(count))
        self.update_count.emit(count)

    def append(self, game: Game, igame: InstalledGame):
        self.__text.setVisible(False)
        self.__container.setVisible(True)
        widget: UpdateWidget = self.__find_widget(game.app_name)
        if widget is not None:
            self.__container.layout().removeWidget(widget)
            widget.deleteLater()
        widget = UpdateWidget(self.imgmgr, game, igame, parent=self.__container)
        widget.destroyed.connect(self.__update_group)
        widget.enqueue.connect(self.enqueue)
        self.__container.layout().addWidget(widget)

    def remove(self, app_name: str):
        widget: UpdateWidget = self.__find_widget(app_name)
        self.__container.layout().removeWidget(widget)
        widget.deleteLater()

    def set_widget_enabled(self, app_name: str, enabled: bool):
        widget: UpdateWidget = self.__find_widget(app_name)
        widget.set_enabled(enabled)

    def get_widget_version(self, app_name: str) -> str:
        widget: UpdateWidget = self.__find_widget(app_name)
        return widget.version()


class QueueGroup(QGroupBox):
    update_count = Signal(int)
    removed = Signal(str)
    force = Signal(InstallQueueItemModel)

    def __init__(self, core: LegendaryCore, imgmgr: ImageManager, parent=None):
        super(QueueGroup, self).__init__(parent=parent)
        self.setObjectName(type(self).__name__)
        self.setTitle(self.tr("Queue"))
        self.__text = QLabel(self.tr("No downloads in queue"), self)
        self.__text.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

        # lk: For findChildren to work, the queue's layout has to be in a widget
        self.__container = QWidget(self)
        self.__container.setVisible(False)
        container_layout = QVBoxLayout(self.__container)
        container_layout.setContentsMargins(0, 0, 0, 0)

        layout = QVBoxLayout(self)
        layout.addWidget(self.__text)
        layout.addWidget(self.__container)

        self.core = core
        self.imgmgr = imgmgr
        self.__queue: Deque[str] = deque()

    def __find_widget(self, app_name: str) -> Optional[QueueWidget]:
        return self.__container.findChild(QueueWidget, name=widget_object_name(QueueWidget, app_name))

    def count(self) -> int:
        return len(self.__queue)

    def contains(self, app_name: str) -> bool:
        if app_name in self.__queue:
            return self.__find_widget(app_name) is not None
        else:
            return False

    def __update_group(self):
        count = self.count()
        self.__text.setVisible(not count)
        self.__container.setVisible(bool(count))
        self.update_count.emit(count)

    def __create_widget(self, item: InstallQueueItemModel, old_igame: InstalledGame) -> QueueWidget:
        widget: QueueWidget = QueueWidget(self.core, self.imgmgr, item, old_igame, parent=self.__container)
        widget.toggle_arrows(self.__queue.index(item.options.app_name), len(self.__queue))
        widget.destroyed.connect(self.__update_group)
        widget.destroyed.connect(self.__update_arrows)
        widget.remove.connect(self.remove)
        widget.force.connect(self.__on_force)
        widget.move_up.connect(self.__on_move_up)
        widget.move_down.connect(self.__on_move_down)
        return widget

    def push_front(self, item: InstallQueueItemModel, old_igame: InstalledGame):
        self.__text.setVisible(False)
        self.__container.setVisible(True)
        self.__queue.appendleft(item.options.app_name)
        widget = self.__create_widget(item, old_igame)
        self.__container.layout().insertWidget(0, widget)
        if self.count() > 1:
            app_name = self.__queue[1]
            other: QueueWidget = self.__find_widget(app_name)
            other.toggle_arrows(1, len(self.__queue))

    def push_back(self, item: InstallQueueItemModel, old_igame: InstalledGame):
        self.__text.setVisible(False)
        self.__container.setVisible(True)
        self.__queue.append(item.download.game.app_name)
        widget = self.__create_widget(item, old_igame)
        self.__container.layout().addWidget(widget)
        if self.count() > 1:
            app_name = self.__queue[-2]
            other: QueueWidget = self.__find_widget(app_name)
            other.toggle_arrows(len(self.__queue) - 2, len(self.__queue))

    def pop_front(self) -> InstallQueueItemModel:
        app_name = self.__queue.popleft()
        widget: QueueWidget = self.__find_widget(app_name)
        item = widget.item
        widget.deleteLater()
        return item

    def __update_arrows(self):
        """
        Check the first, second, last and second to last widgets in the list
        and update their arrows
        :return: None
        """
        for idx in [0, 1]:
            if self.count() > idx:
                app_name = self.__queue[idx]
                widget: QueueWidget = self.__find_widget(app_name)
                widget.toggle_arrows(idx, len(self.__queue))
        for idx in [1, 2]:
            if self.count() > idx:
                app_name = self.__queue[-idx]
                widget: QueueWidget = self.__find_widget(app_name)
                widget.toggle_arrows(len(self.__queue) - idx, len(self.__queue))

    def __remove(self, app_name: str):
        self.__queue.remove(app_name)
        widget: QueueWidget = self.__find_widget(app_name)
        self.__container.layout().removeWidget(widget)
        widget.deleteLater()

    @Slot(str)
    def remove(self, app_name: str):
        self.__remove(app_name)
        self.removed.emit(app_name)

    @Slot(InstallQueueItemModel)
    def __on_force(self, item: InstallQueueItemModel):
        self.__remove(item.options.app_name)
        self.force.emit(item)

    class MoveDirection(IntEnum):
        UP = -1
        DOWN = 1

    def __move(self, app_name: str, direction: MoveDirection):
        """
        Moved the widget for `app_name` up or down in the queue and the container
        :param app_name: The app_name associated with the widget
        :param direction: -1 to move up, +1 to move down
        :return: None
        """
        index = self.__queue.index(app_name)
        self.__queue.remove(app_name)
        self.__queue.insert(index + int(direction), app_name)
        widget: QueueWidget = self.__find_widget(app_name)
        self.__container.layout().insertWidget(index + int(direction), widget)
        self.__update_arrows()

    @Slot(str)
    def __on_move_up(self, app_name: str):
        self.__move(app_name, QueueGroup.MoveDirection.UP)

    @Slot(str)
    def __on_move_down(self, app_name: str):
        self.__move(app_name, QueueGroup.MoveDirection.DOWN)
