import time
from io import StringIO
from threading import Lock, Thread
from typing import List

from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, Qt, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QWidget, QSizePolicy

from bauh import __app_name__
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.model import PackageUpdate
from bauh.view.qt import qt_utils
from bauh.view.qt.about import AboutDialog
from bauh.view.qt.settings import SettingsWindow
from bauh.view.qt.view_utils import load_resource_icon
from bauh.view.qt.window import ManageWindow
from bauh.view.util import util
from bauh.view.util.translation import I18n


class UpdateCheck(QThread):

    signal = pyqtSignal(list)

    def __init__(self, manager: SoftwareManager, check_interval: int, parent=None):
        super(UpdateCheck, self).__init__(parent)
        self.check_interval = check_interval
        self.manager = manager

    def run(self):

        while True:
            updates = self.manager.list_updates()
            self.signal.emit(updates)
            time.sleep(self.check_interval)


class TrayIcon(QSystemTrayIcon):

    def __init__(self, i18n: I18n, manager: SoftwareManager, manage_window: ManageWindow, config: dict, screen_size: QSize):
        super(TrayIcon, self).__init__()
        self.i18n = i18n
        self.manager = manager
        self.screen_size = screen_size

        if config['ui']['tray']['default_icon']:
            self.icon_default = QIcon(config['ui']['tray']['default_icon'])
        else:
            self.icon_default = QIcon.fromTheme('bauh_tray_default')

        if self.icon_default.isNull():
            self.icon_default = load_resource_icon('img/logo.svg', 24)

        if config['ui']['tray']['updates_icon']:
            self.icon_updates = QIcon(config['ui']['tray']['updates_icon'])
        else:
            self.icon_updates = QIcon.fromTheme('bauh_tray_updates')

        if self.icon_updates.isNull():
            self.icon_updates = load_resource_icon('img/logo_update.svg', 24)

        self.setIcon(self.icon_default)

        self.menu = QMenu()

        self.action_manage = self.menu.addAction(self.i18n['tray.action.manage'])
        self.action_manage.triggered.connect(self.show_manage_window)

        self.action_settings = self.menu.addAction(self.i18n['settings'].capitalize())
        self.action_settings.triggered.connect(self.show_settings_window)

        self.action_about = self.menu.addAction(self.i18n['tray.action.about'])
        self.action_about.triggered.connect(self.show_about)

        self.action_exit = self.menu.addAction(self.i18n['tray.action.exit'])
        self.action_exit.triggered.connect(lambda: QCoreApplication.exit())

        self.setContextMenu(self.menu)

        self.manage_window = None
        self.dialog_about = None
        self.settings_window = None

        self.check_thread = UpdateCheck(check_interval=int(config['updates']['check_interval']), manager=self.manager)
        self.check_thread.signal.connect(self.notify_updates)
        self.check_thread.start()

        self.last_updates = set()
        self.update_notification = bool(config['system']['notifications'])
        self.lock_notify = Lock()

        self.activated.connect(self.handle_click)
        self.set_default_tooltip()

        self.manage_window = manage_window

    def set_default_tooltip(self):
        self.setToolTip('{} ({})'.format(self.i18n['manage_window.title'], __app_name__).lower())

    def handle_click(self, reason):
        if reason == self.Trigger:
            self.show_manage_window()

    def verify_updates(self, notify_user: bool = True):
        Thread(target=self._verify_updates, args=(notify_user,)).start()

    def _verify_updates(self, notify_user: bool):
        self.notify_updates(self.manager.list_updates(), notify_user=notify_user)

    def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True):

        self.lock_notify.acquire()

        try:
            if len(updates) > 0:
                update_keys = {'{}:{}:{}'.format(up.type, up.id, up.version) for up in updates}

                new_icon = self.icon_updates

                if update_keys.difference(self.last_updates):
                    self.last_updates = update_keys
                    n_updates = len(updates)
                    ups_by_type = {}

                    for key in update_keys:
                        ptype = key.split(':')[0]
                        count = ups_by_type.get(ptype)
                        count = 1 if count is None else count + 1
                        ups_by_type[ptype] = count

                    msg = StringIO()
                    msg.write(self.i18n['notification.update{}'.format('' if n_updates == 1 else 's')].format(n_updates))

                    if len(ups_by_type) > 1:
                        for ptype, count in ups_by_type.items():
                            msg.write('\n  * {} ( {} )'.format(ptype.capitalize(), count))

                    msg.seek(0)
                    msg = msg.read()
                    self.setToolTip(msg)

                    if self.update_notification and notify_user:
                        util.notify_user(msg=msg)

            else:
                self.last_updates.clear()
                new_icon = self.icon_default
                self.set_default_tooltip()

            if self.icon().cacheKey() != new_icon.cacheKey():  # changes the icon if needed
                self.setIcon(new_icon)

        finally:
            self.lock_notify.release()

    def show_manage_window(self):
        if self.manage_window.isMinimized():
            self.manage_window.setWindowState(Qt.WindowNoState)
        elif not self.manage_window.isVisible():
            self.manage_window.refresh_apps()
            self.manage_window.show()

    def show_settings_window(self):
        if self.settings_window:
            self.settings_window.handle_display()
        else:
            self.settings_window = SettingsWindow(manager=self.manager,
                                                  i18n=self.i18n,
                                                  screen_size=self.screen_size,
                                                  tray=self,
                                                  window=self.manage_window)
            self.settings_window.setMinimumWidth(int(self.screen_size.width() / 4))
            self.settings_window.adjustSize()
            qt_utils.centralize(self.settings_window)
            self.settings_window.show()

    def show_about(self):
        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.i18n)

        if self.dialog_about.isHidden():
            self.dialog_about.show()
