import datetime
from logging import getLogger
from multiprocessing import Queue as MPQueue

from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \
    QListWidget, QHBoxLayout, QGroupBox

from rare.components.dialogs.install_dialog import InstallInfoDialog, InstallDialog
from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget
from rare.components.tabs.downloads.download_thread import DownloadThread
from rare.utils.models import InstallOptions
from custom_legendary.core import LegendaryCore
from custom_legendary.models.downloading import UIUpdate
from custom_legendary.models.game import Game, InstalledGame
from custom_legendary.utils.selective_dl import games

logger = getLogger("Download")


class DownloadTab(QWidget):
    finished = pyqtSignal()
    thread: QThread
    dl_queue = []

    def __init__(self, core: LegendaryCore, updates: list):
        super(DownloadTab, self).__init__()
        self.core = core
        self.layout = QVBoxLayout()
        self.active_game: Game = None

        self.installing_game = QLabel(self.tr("No active Download"))
        self.dl_speed = QLabel()
        self.cache_used = QLabel()
        self.downloaded = QLabel()
        self.time_left = QLabel()

        self.info_layout = QGridLayout()

        self.info_layout.addWidget(self.installing_game, 0, 0)
        self.info_layout.addWidget(self.dl_speed, 0, 1)
        self.info_layout.addWidget(self.cache_used, 1, 0)
        self.info_layout.addWidget(self.downloaded, 1, 1)
        self.info_layout.addWidget(self.time_left, 2, 0)
        self.layout.addLayout(self.info_layout)

        self.mini_layout = QHBoxLayout()
        self.prog_bar = QProgressBar()
        self.prog_bar.setMaximum(100)
        self.mini_layout.addWidget(self.prog_bar)

        self.kill_button = QPushButton(self.tr("Stop Download"))
        # self.mini_layout.addWidget(self.kill_button)
        self.kill_button.setDisabled(True)
        self.kill_button.clicked.connect(self.stop_download)

        self.layout.addLayout(self.mini_layout)

        self.queue_widget = DlQueueWidget()
        self.layout.addWidget(self.queue_widget)
        self.queue_widget.update_list.connect(self.update_dl_queue)

        self.updates = QGroupBox(self.tr("Updates"))
        self.updates.setObjectName("group")
        self.update_layout = QVBoxLayout()
        self.update_widgets = {}

        self.update_text = QLabel(self.tr("No updates available"))
        self.update_text.setVisible(len(updates) == 0)
        self.update_layout.addWidget(self.update_text)

        for igame in updates:
            widget = UpdateWidget(core, igame)
            self.update_widgets[igame.app_name] = widget
            self.update_layout.addWidget(widget)
            widget.update.connect(self.update_game)

        self.updates.setLayout(self.update_layout)
        self.layout.addWidget(self.updates)
        self.layout.addStretch(1)

        self.setLayout(self.layout)

    def update_dl_queue(self, dl_queue):
        self.dl_queue = dl_queue

    def stop_download(self):
        self.thread.kill = True

    def install_game(self, options: InstallOptions):

        status_queue = MPQueue()
        try:
            dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download(
                app_name=options.app_name,
                base_path=options.path,
                force=options.force,
                no_install=options.download_only,
                status_q=status_queue,
                # max_shm=,
                max_workers=options.max_workers,
                # game_folder=,
                # disable_patching=,
                # override_manifest=,
                # override_old_manifest=,
                # override_base_url=,
                # platform_override=,
                # file_prefix_filter=,
                # file_exclude_filter=,
                # file_install_tag=,
                # dl_optimizations=,
                # dl_timeout=,
                repair=options.repair,
                # repair_use_latest=,
                ignore_space_req=options.ignore_free_space,
                # disable_delta=,
                # override_delta_manifest=,
                # reset_sdl=,
                sdl_prompt=self.sdl_prompt)
        except Exception as e:
            QMessageBox.warning(self, self.tr("Error preparing download"),
                                str(e))
            return

        if not analysis.dl_size:
            QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists"))
            return

        # Information

        if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
            return

        if self.active_game is None:
            self.start_installation(dlm, game, status_queue, igame, repair_file, options, analysis)
        else:
            self.dl_queue.append((dlm, game, status_queue, igame, repair_file, options, analysis))
            self.queue_widget.update_queue(self.dl_queue)

    def start_installation(self, dlm, game, status_queue, igame, repair_file, options: InstallOptions, analysis):
        print("start installation", game.app_title)
        self.active_game = game
        self.thread = DownloadThread(dlm, self.core, status_queue, igame, options.repair, repair_file)
        self.thread.status.connect(self.status)
        self.thread.statistics.connect(self.statistics)
        self.thread.start()
        self.kill_button.setDisabled(False)
        self.installing_game.setText(self.tr("Installing Game: ") + self.active_game.app_title)

    def sdl_prompt(self, app_name: str = '', title: str = '') -> list:
        sdl = QDialog()
        sdl.setWindowTitle('Select Additional Downloads')

        layout = QVBoxLayout(sdl)
        sdl.setLayout(layout)

        pack_list = QListWidget()
        layout.addWidget(pack_list)

        done = QPushButton(text='Done')
        done.clicked.connect(sdl.accept)
        layout.addWidget(done)

        tags = ['']
        if '__required' in games[app_name]:
            tags.extend(games[app_name]['__required']['tags'])

        # add available additional downloads to list
        pack_list.addItems([tag + ': ' + info['name'] for tag, info in games[app_name].items() if tag != '__required'])

        # enable checkboxes
        for i in range(len(pack_list)):
            item = pack_list.item(i)
            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
            item.setCheckState(Qt.Unchecked)

        sdl.exec_()

        # read checkboxes states
        for i in range(len(pack_list)):
            item = pack_list.item(i)
            if item.checkState() == Qt.Checked:
                tag = item.text().split(':')[0]
                tags.extend(games[app_name][tag]['tags'])

        return tags

    def status(self, text):
        if text == "dl_finished":
            pass
        elif text == "finish":
            self.installing_game.setText(self.tr("Download finished. Reload library"))

            try:
                from notifypy import Notify
            except ModuleNotFoundError:
                logger.warning("No Notification Module found")
            else:
                notification = Notify()
                notification.title = self.tr("Installation finished")
                notification.message = self.tr("Finished Download of game {}").format(self.active_game.app_name)
                notification.send()
            # QMessageBox.information(self, "Info", "Download finished")
            logger.info("Download finished: " + self.active_game.app_title)

            if self.dl_queue:
                if self.dl_queue[0][1] == self.active_game.app_name:
                    self.dl_queue.pop(0)
                    self.queue_widget.update_queue(self.dl_queue)

            if self.active_game.app_name in self.update_widgets.keys():
                self.update_widgets[self.active_game.app_name].setVisible(False)
                self.update_widgets.pop(self.active_game.app_name)

            self.active_game = None

            for i in self.update_widgets.values():
                i.update_button.setDisabled(False)

            self.finished.emit()
            self.reset_infos()

            if len(self.dl_queue) != 0:
                self.start_installation(*self.dl_queue[0])
            else:
                self.queue_widget.update_queue(self.dl_queue)

        elif text == "error":
            QMessageBox.warning(self, "warn", "Download error")

        elif text == "stop":
            self.reset_infos()

    def reset_infos(self):
        self.kill_button.setDisabled(True)
        self.installing_game.setText(self.tr("Installing Game: No active download"))
        self.prog_bar.setValue(0)
        self.dl_speed.setText("")
        self.time_left.setText("")
        self.cache_used.setText("")
        self.downloaded.setText("")

    def statistics(self, ui_update: UIUpdate):
        self.prog_bar.setValue(ui_update.progress)
        self.dl_speed.setText(self.tr("Download speed") + f": {ui_update.download_speed / 1024 / 1024:.02f}MB/s")
        self.cache_used.setText(self.tr("Cache used") + f": {ui_update.cache_usage / 1024 / 1024:.02f}MB")
        self.downloaded.setText(self.tr("Downloaded") + f": {ui_update.total_downloaded / 1024 / 1024:.02f}MB")
        self.time_left.setText(self.tr("Time left: ") + self.get_time(ui_update.estimated_time_left))

    def get_time(self, seconds: int) -> str:
        return str(datetime.timedelta(seconds=seconds))

    def update_game(self, app_name: str):
        logger.info("Update " + app_name)
        infos = InstallDialog(app_name, self.core, True).get_information()
        if infos != 0:
            path, max_workers, force, ignore_free_space = infos
            self.install_game(InstallOptions(app_name=app_name, max_workers=max_workers, path=path,
                                             force=force, ignore_free_space=ignore_free_space))


class UpdateWidget(QWidget):
    update = pyqtSignal(str)

    def __init__(self, core: LegendaryCore, game: InstalledGame):
        super(UpdateWidget, self).__init__()
        self.core = core
        self.game = game

        self.layout = QVBoxLayout()
        self.title = QLabel(self.game.title)
        self.layout.addWidget(self.title)

        self.update_button = QPushButton(self.tr("Update Game"))
        self.update_button.clicked.connect(lambda: self.update.emit(game.app_name))
        self.layout.addWidget(self.update_button)

        self.setLayout(self.layout)
