import os
import platform
import shutil
from logging import getLogger
from typing import Optional

from PyQt5.QtCore import (
    Qt,
    pyqtSlot,
    pyqtSignal,
)
from PyQt5.QtWidgets import (
    QWidget,
    QMessageBox,
)

from rare.models.install import SelectiveDownloadsModel, MoveGameModel
from rare.components.dialogs.selective_dialog import SelectiveDialog
from rare.models.game import RareGame
from rare.shared import RareCore
from rare.shared.workers import VerifyWorker, MoveWorker
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
from rare.utils.misc import format_size, icon
from rare.widgets.image_widget import ImageWidget, ImageSize
from rare.widgets.side_tab import SideTabContents
from rare.components.dialogs.move_dialog import MoveDialog, is_game_dir

logger = getLogger("GameInfo")


class GameInfo(QWidget, SideTabContents):
    # str: app_name
    import_clicked = pyqtSignal(str)

    def __init__(self, parent=None):
        super(GameInfo, self).__init__(parent=parent)
        self.ui = Ui_GameInfo()
        self.ui.setupUi(self)
        # lk: set object names for CSS properties
        self.ui.install_button.setObjectName("InstallButton")
        self.ui.modify_button.setObjectName("InstallButton")
        self.ui.uninstall_button.setObjectName("UninstallButton")

        self.ui.install_button.setIcon(icon("ri.install-line"))
        self.ui.import_button.setIcon(icon("mdi.application-import"))

        self.ui.modify_button.setIcon(icon("fa.gear"))
        self.ui.verify_button.setIcon(icon("fa.check"))
        self.ui.repair_button.setIcon(icon("fa.wrench"))
        self.ui.move_button.setIcon(icon("mdi.folder-move-outline"))
        self.ui.uninstall_button.setIcon(icon("ri.uninstall-line"))

        self.rcore = RareCore.instance()
        self.core = RareCore.instance().core()
        self.args = RareCore.instance().args()
        # self.image_manager = RareCore.instance().image_manager()

        self.rgame: Optional[RareGame] = None

        self.image = ImageWidget(self)
        self.image.setFixedSize(ImageSize.Display)
        self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignTop)

        self.ui.install_button.clicked.connect(self.__on_install)
        self.ui.import_button.clicked.connect(self.__on_import)
        self.ui.modify_button.clicked.connect(self.__on_modify)
        self.ui.verify_button.clicked.connect(self.__on_verify)
        self.ui.repair_button.clicked.connect(self.__on_repair)
        self.ui.move_button.clicked.connect(self.__on_move)
        self.ui.uninstall_button.clicked.connect(self.__on_uninstall)

        self.steam_grade_ratings = {
            "platinum": self.tr("Platinum"),
            "gold": self.tr("Gold"),
            "silver": self.tr("Silver"),
            "bronze": self.tr("Bronze"),
            "borked": self.tr("Borked"),
            "fail": self.tr("Failed to get rating"),
            "pending": self.tr("Loading..."),
            "na": self.tr("Not applicable"),
        }

        # lk: hide unfinished things
        self.ui.tags_group.setVisible(False)
        self.ui.requirements_group.setVisible(False)

    @pyqtSlot()
    def __on_install(self):
        if self.rgame.is_non_asset:
            self.rgame.launch()
        else:
            self.rgame.install()

    @pyqtSlot()
    def __on_import(self):
        self.import_clicked.emit(self.rgame.app_name)

    @pyqtSlot()
    def __on_uninstall(self):
        """ This method is to be called from the button only """
        self.rgame.uninstall()

    @pyqtSlot()
    def __on_modify(self):
        """ This method is to be called from the button only """
        self.rgame.modify()

    @pyqtSlot()
    def __on_repair(self):
        """ This method is to be called from the button only """
        repair_file = os.path.join(self.core.lgd.get_tmp_path(), f"{self.rgame.app_name}.repair")
        if not os.path.exists(repair_file):
            QMessageBox.warning(
                self,
                self.tr("Error - {}").format(self.rgame.app_title),
                self.tr(
                    "Repair file does not exist or game does not need a repair. Please verify game first"
                ),
            )
            return
        self.repair_game(self.rgame)

    def repair_game(self, rgame: RareGame):
        rgame.update_game()
        ans = False
        if rgame.has_update:
            ans = QMessageBox.question(
                self,
                self.tr("Repair and update? - {}").format(self.rgame.app_title),
                self.tr(
                    "There is an update for <b>{}</b> from <b>{}</b> to <b>{}</b>. "
                    "Do you want to update the game while repairing it?"
                ).format(rgame.app_title, rgame.version, rgame.remote_version),
            ) == QMessageBox.Yes
        rgame.repair(repair_and_update=ans)

    @pyqtSlot(RareGame, str)
    def __on_worker_error(self, rgame: RareGame, message: str):
        QMessageBox.warning(
            self,
            self.tr("Error - {}").format(rgame.app_title),
            message
        )

    @pyqtSlot()
    def __on_verify(self):
        """ This method is to be called from the button only """
        if not os.path.exists(self.rgame.igame.install_path):
            logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.app_title} does not exist")
            QMessageBox.warning(
                self,
                self.tr("Error - {}").format(self.rgame.app_title),
                self.tr("Installation path for <b>{}</b> does not exist. Cannot continue.").format(self.rgame.app_title),
            )
            return
        if self.rgame.sdl_name is not None:
            selective_dialog = SelectiveDialog(
                self.rgame, parent=self
            )
            selective_dialog.result_ready.connect(self.verify_game)
            selective_dialog.open()
        else:
            self.verify_game(self.rgame)

    @pyqtSlot(RareGame, SelectiveDownloadsModel)
    def verify_game(self, rgame: RareGame, sdl_model: SelectiveDownloadsModel = None):
        if sdl_model is not None:
            if not sdl_model.accepted or sdl_model.install_tag is None:
                return
            self.core.lgd.config.set(rgame.app_name, "install_tags", ','.join(sdl_model.install_tag))
            self.core.lgd.save_config()
        worker = VerifyWorker(self.core, self.args, rgame)
        worker.signals.progress.connect(self.__on_verify_progress)
        worker.signals.result.connect(self.__on_verify_result)
        worker.signals.error.connect(self.__on_worker_error)
        self.rcore.enqueue_worker(rgame, worker)

    @pyqtSlot(RareGame, int, int, float, float)
    def __on_verify_progress(self, rgame: RareGame, num, total, percentage, speed):
        # lk: the check is NOT REQUIRED because signals are disconnected but protect against it anyway
        if rgame is not self.rgame:
            return
        self.ui.verify_progress.setValue(num * 100 // total)

    @pyqtSlot(RareGame, bool, int, int)
    def __on_verify_result(self, rgame: RareGame, success, failed, missing):
        self.ui.repair_button.setDisabled(success)
        if success:
            QMessageBox.information(
                self,
                self.tr("Summary - {}").format(rgame.app_title),
                self.tr("<b>{}</b> has been verified successfully. "
                        "No missing or corrupt files found").format(rgame.app_title),
            )
        else:
            ans = QMessageBox.question(
                self,
                self.tr("Summary - {}").format(rgame.app_title),
                self.tr(
                    "<b>{}</b> failed verification, <b>{}</b> file(s) corrupted, <b>{}</b> file(s) are missing. "
                    "Do you want to repair them?"
                ).format(rgame.app_title, failed, missing),
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.Yes,
            )
            if ans == QMessageBox.Yes:
                self.repair_game(rgame)

    @pyqtSlot()
    def __on_move(self):
        """ This method is to be called from the button only """
        move_dialog = MoveDialog(self.rgame, parent=self)
        move_dialog.result_ready.connect(self.move_game)
        move_dialog.open()

    def move_game(self, rgame: RareGame, model: MoveGameModel):
        if not model.accepted:
            return

        new_install_path = os.path.join(model.target_path, os.path.basename(self.rgame.install_path))
        dir_exists = False
        if os.path.isdir(new_install_path):
            dir_exists = is_game_dir(self.rgame.install_path, new_install_path)

        if not dir_exists:
            for item in os.listdir(model.target_path):
                if os.path.basename(self.rgame.install_path) in os.path.basename(item):
                    ans = QMessageBox.question(
                        self,
                        self.tr("Move game? - {}").format(self.rgame.app_title),
                        self.tr(
                            "Destination <b>{}</b> already exists. "
                            "Are you sure you want to overwrite it?"
                        ).format(new_install_path),
                        QMessageBox.Yes | QMessageBox.No,
                        QMessageBox.Yes,
                    )

                    if ans == QMessageBox.Yes:
                        if os.path.isdir(new_install_path):
                            shutil.rmtree(new_install_path)
                        else:
                            os.remove(new_install_path)
                    else:
                        return

        worker = MoveWorker(
            self.core, rgame=rgame, dst_path=model.target_path, dst_exists=dir_exists
        )
        worker.signals.progress.connect(self.__on_move_progress)
        worker.signals.result.connect(self.__on_move_result)
        worker.signals.error.connect(self.__on_worker_error)
        self.rcore.enqueue_worker(self.rgame, worker)

    @pyqtSlot(RareGame, int, object, object)
    def __on_move_progress(self, rgame: RareGame, progress: int, total_size: int, copied_size: int):
        # lk: the check is NOT REQUIRED because signals are disconnected but protect against it anyway
        if rgame is not self.rgame:
            return
        self.ui.move_progress.setValue(progress)

    @pyqtSlot(RareGame, str)
    def __on_move_result(self, rgame: RareGame, dst_path: str):
        QMessageBox.information(
            self,
            self.tr("Summary - {}").format(rgame.app_title),
            self.tr("<b>{}</b> successfully moved to <b>{}<b>.").format(rgame.app_title, dst_path),
        )

    @pyqtSlot()
    def __update_widget(self):
        """ React to state updates from RareGame """
        # self.image.setPixmap(self.image_manager.get_pixmap(self.rgame.app_name, True))
        self.image.setPixmap(self.rgame.pixmap)

        self.ui.lbl_version.setDisabled(self.rgame.is_non_asset)
        self.ui.version.setDisabled(self.rgame.is_non_asset)
        self.ui.version.setText(
            self.rgame.version if not self.rgame.is_non_asset else "N/A"
        )

        self.ui.lbl_install_size.setEnabled(bool(self.rgame.install_size))
        self.ui.install_size.setEnabled(bool(self.rgame.install_size))
        self.ui.install_size.setText(
            format_size(self.rgame.install_size) if self.rgame.install_size else "N/A"
        )

        self.ui.lbl_install_path.setEnabled(bool(self.rgame.install_path))
        self.ui.install_path.setEnabled(bool(self.rgame.install_path))
        self.ui.install_path.setText(
            self.rgame.install_path if self.rgame.install_path else "N/A"
        )

        self.ui.platform.setText(
            self.rgame.igame.platform
            if self.rgame.is_installed and not self.rgame.is_non_asset
            else self.rgame.default_platform
        )

        self.ui.lbl_grade.setDisabled(
            self.rgame.is_unreal or platform.system() == "Windows"
        )
        self.ui.grade.setDisabled(
            self.rgame.is_unreal or platform.system() == "Windows"
        )
        self.ui.grade.setText(self.steam_grade_ratings[self.rgame.steam_grade()])

        self.ui.install_button.setEnabled(
            (not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle
        )

        self.ui.import_button.setEnabled(
            (not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle
        )

        self.ui.modify_button.setEnabled(
            self.rgame.is_installed
            and (not self.rgame.is_non_asset)
            and self.rgame.is_idle
            and self.rgame.sdl_name is not None
        )

        self.ui.verify_button.setEnabled(
            self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
        )
        self.ui.verify_progress.setValue(self.rgame.progress if self.rgame.state == RareGame.State.VERIFYING else 0)
        if self.rgame.state == RareGame.State.VERIFYING:
            self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
        else:
            self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)

        self.ui.repair_button.setEnabled(
            self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
            and self.rgame.needs_repair
            and not self.args.offline
        )

        self.ui.move_button.setEnabled(
            self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
        )
        self.ui.move_progress.setValue(self.rgame.progress if self.rgame.state == RareGame.State.MOVING else 0)
        if self.rgame.state == RareGame.State.MOVING:
            self.ui.move_stack.setCurrentWidget(self.ui.move_progress_page)
        else:
            self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)

        self.ui.uninstall_button.setEnabled(
            self.rgame.is_installed and (not self.rgame.is_non_asset) and self.rgame.is_idle
        )

        if self.rgame.is_installed and not self.rgame.is_non_asset:
            self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page)
        else:
            self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)

    @pyqtSlot(RareGame)
    def update_game(self, rgame: RareGame):
        if self.rgame is not None:
            if (worker := self.rgame.worker()) is not None:
                if isinstance(worker, VerifyWorker):
                    try:
                        worker.signals.progress.disconnect(self.__on_verify_progress)
                    except TypeError as e:
                        logger.warning(f"{self.rgame.app_name} verify worker: {e}")
                if isinstance(worker, MoveWorker):
                    try:
                        worker.signals.progress.disconnect(self.__on_move_progress)
                    except TypeError as e:
                        logger.warning(f"{self.rgame.app_name} move worker: {e}")
            self.rgame.signals.widget.update.disconnect(self.__update_widget)

        self.rgame = None

        rgame.signals.widget.update.connect(self.__update_widget)
        if (worker := rgame.worker()) is not None:
            if isinstance(worker, VerifyWorker):
                worker.signals.progress.connect(self.__on_verify_progress)
            if isinstance(worker, MoveWorker):
                worker.signals.progress.connect(self.__on_move_progress)

        self.set_title.emit(rgame.app_title)
        self.ui.app_name.setText(rgame.app_name)
        self.ui.dev.setText(rgame.developer)

        if rgame.is_non_asset:
            self.ui.install_button.setText(self.tr("Link/Launch"))
            self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page)
        else:
            self.ui.install_button.setText(self.tr("Install"))

        self.rgame = rgame
        self.__update_widget()
