import pathlib

from PyQt5 import QtGui
from PyQt5.QtCore import Qt, pyqtSignal, QSortFilterProxyModel, QEvent
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, QWidget, QMainWindow, QPushButton, QMenu, \
    QAction, QActionGroup, QSplitter, QScrollArea, QDialog
from album.api import Album

from album.gui.actions_list_model import ActionsListModel
from album.gui.dialog.update_dialog import UpdateDialog
from album.gui.widgets.solution_view import SolutionView
from album.gui.widgets.util import create_btn


class CollectionWindow(QMainWindow):
    # events
    return_pressed = pyqtSignal(QKeyEvent)
    esc_pressed = pyqtSignal(QKeyEvent)
    show_solution = pyqtSignal(str)
    uninstall_solution = pyqtSignal(str)
    install_solution = pyqtSignal(str)
    remove_catalog = pyqtSignal(dict)
    add_new_catalog = pyqtSignal(QWidget)
    update_catalog_dialog = pyqtSignal(bool)
    update_catalogs = pyqtSignal(list)
    update_all_catalogs = pyqtSignal()
    collection_changed = pyqtSignal()

    update_running = pyqtSignal(bool)
    upgrade_running = pyqtSignal(bool)
    available_updates_changed = pyqtSignal(bool)

    class Action:
        def __init__(self, signal, args=None, solution=None):
            self.signal = signal
            self.args = args
            self.solution = solution

    def __init__(self, album_instance: Album, parent=None):
        super(CollectionWindow, self).__init__(parent)
        self.selected_solution = None
        self.album_instance: Album = album_instance
        self.all_catalogs = []
        self.deletable_catalogs = []
        self.list_widget = None
        self.setWindowTitle("Album")
        self.update_catalog_dialog.connect(self._choose_update_catalogs)

        self.search_model = ActionsListModel()
        # self.search_proxy_model = ActionFilterProxyModel()
        self.update_model()

        widget = QWidget(self)
        self.setMinimumWidth(800)
        self.setMinimumHeight(400)
        self.setWindowIcon(QtGui.QIcon(str(pathlib.Path(__file__).parent / 'resources' / 'album-xs.png')))
        self.layout = QVBoxLayout(widget)

        self.layout.setContentsMargins(0, 10, 0, 0)
        self.layout.setSpacing(0)

        self._create_list_widget()
        self._create_search_line()

        top_layout = QSplitter()
        top_layout.addWidget(self._create_center())
        top_layout.addWidget(self._create_right())
        self.layout.addWidget(self.search_line)
        self.layout.addWidget(top_layout, 1)
        self.layout.addLayout(self._create_status_layout())

        self.setCentralWidget(widget)

    def _create_center(self):
        res = QWidget()
        layout = QVBoxLayout(res)
        res.setAutoFillBackground(True)
        # res.setPalette(self.pal)
        res.setContentsMargins(2, 0, 2, 0)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.list_widget)
        return res

    def _create_search_line(self):
        self.search_line = QLineEdit(self)
        self.search_line.setStyleSheet(
            "QLineEdit { border-radius: 0px; border-top: 1px solid #bbbbbb; border-bottom: 1px solid #bbbbbb;padding: 3px;}")
        self.search_line.installEventFilter(self)
        self.proxy.setFilterCaseSensitivity(False)
        self.search_line.textChanged.connect(self.proxy.setFilterFixedString)
        self.proxy.rowsInserted.connect(self.list_widget.select_first_item)
        self.proxy.rowsRemoved.connect(self.list_widget.select_first_item)
        self.proxy.rowsMoved.connect(self.list_widget.select_first_item)

    def _create_list_widget(self):
        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setSourceModel(self.search_model)
        self.list_widget = SolutionView(self)
        self.list_widget.selectionModel().selectionChanged.connect(self._update_focus_solution)

    def _update_focus_solution(self):
        item = self._get_currently_selected_item()
        if item:
            solution = item.action.solution
            self.selected_solution_version.setText("Version " + solution["setup"]["version"])
            if "title" in solution["setup"] and solution["setup"]["title"]:
                self.selected_solution_title.setText(solution["setup"]["title"])
            else:
                self.selected_solution_title.setText(item.solution_coordinates)
            if "description" in solution["setup"] and solution["setup"]["description"]:
                self.selected_solution_description.setText(solution["setup"]["description"])
            if "cite" in solution["setup"] and solution["setup"]["cite"]:
                citations = ""
                for citation in solution["setup"]["cite"]:
                    citations += citation["text"] + "\n"
                    if "url" in citation:
                        citations += citation["url"] + "\n"
                self.selected_solution_cite.setText("Credits: %s" % citations if citations else "")
            if "solution_creators" in solution["setup"]:
                creators = ""
                for creator in solution["setup"]["solution_creators"]:
                    creators += creator + "\n"
                self.selected_solution_creator.setText("Created by %s" % creators if creators else "")
            else:
                self.selected_solution_cite.setText("")
            self.run_btn.setVisible(True)
        else:
            self.selected_solution_title.setText("")
            self.selected_solution_version.setText("")
            self.selected_solution_description.setText("")
            self.selected_solution_cite.setText("")
            self.run_btn.setVisible(False)

    def _create_status_layout(self):
        status_layout = QHBoxLayout()
        catalog_status = self._create_status_with_action(
            "catalogs: %s" % len(self.album_instance.get_catalogs_as_dict()["catalogs"]), self._get_catalog_actions())

        solutions_status = self._create_status(
            "solutions: %s" % self._get_solution_count(self.album_instance.get_index_as_dict()))
        installed_status = self._create_status(
            "installed: %s" % self._get_installed_solution_count(self.album_instance.get_index_as_dict()))
        # running_status = self._create_status("running: 0")
        # updates_status = self._create_status("updates: 0")
        self.collection_changed.connect(lambda: catalog_status.setText(
            "catalogs: %s" % len(self.album_instance.get_catalogs_as_dict()["catalogs"])))
        self.collection_changed.connect(lambda: self._set_actions(catalog_status, self._get_catalog_actions()))
        self.collection_changed.connect(lambda: solutions_status.setText(
            "solutions: %s" % self._get_solution_count(self.album_instance.get_index_as_dict())))
        self.collection_changed.connect(lambda: installed_status.setText(
            "installed: %s" % self._get_installed_solution_count(self.album_instance.get_index_as_dict())))
        status_layout.addWidget(catalog_status)
        status_layout.addWidget(solutions_status)
        status_layout.addWidget(installed_status)
        status_layout.addStretch()
        status_layout.setContentsMargins(3, 3, 3, 3)
        status_layout.setSpacing(10)
        # status_layout.addWidget(running_status)
        # status_layout.addWidget(updates_status)
        return status_layout

    def _get_catalog_actions(self):
        actions = QActionGroup(self)
        add_catalog = QAction("Add catalog")
        add_catalog.triggered.connect(lambda: self.add_new_catalog.emit(self))
        actions.addAction(add_catalog)
        update_catalog = QAction("Update catalog(s)")
        update_catalog.triggered.connect(self.update_catalog_dialog)
        actions.addAction(update_catalog)
        for catalog in self.deletable_catalogs:
            add_catalog = QAction("Remove catalog %s" % catalog["name"])
            add_catalog.triggered.connect(self.make_remove_catalog_action(catalog))
            actions.addAction(add_catalog)
        return actions.actions()

    def make_remove_catalog_action(self, catalog):
        def remove_catalog():
            self.remove_catalog.emit(catalog)
        return remove_catalog

    def _create_action_widget(self):
        action_widget = QWidget()
        action_layout = QHBoxLayout(action_widget)
        action_layout.setContentsMargins(0, 0, 0, 0)
        self.run_btn = create_btn(self, self.run_action, "Run", "Enter")
        self.run_btn.setMinimumWidth(100)
        self.run_btn.setAutoDefault(True)
        self.run_btn.setDefault(True)
        self.search_line.returnPressed.connect(self.run_btn.toggle)
        action_layout.addWidget(self.run_btn)
        self.return_pressed.connect(self.run_btn.keyPressEvent)
        self.esc_pressed.connect(self.close)
        action_widget.setContentsMargins(0, 2, 0, 0)
        return action_widget

    def _create_right(self):
        res_w = QWidget()
        res_w.setFixedWidth(270)
        res = QVBoxLayout(res_w)
        res.setContentsMargins(10, 10, 10, 0)

        self.selected_solution_title = QLabel("")
        self.selected_solution_version = QLabel("")
        self.selected_solution_title.setWordWrap(True)
        font = self.selected_solution_title.font()
        font.setBold(True)
        self.selected_solution_title.setFont(font)

        self.selected_solution_description = QLabel("No description provided.")
        self.selected_solution_description.setWordWrap(True)

        self.selected_solution_creator = QLabel("")
        self.selected_solution_creator.setWordWrap(True)

        self.selected_solution_cite = QLabel("")
        self.selected_solution_cite.setWordWrap(True)
        scroll = QScrollArea(res_w)
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll.setWidgetResizable(True)
        content_widget = QWidget(res_w)
        content_layout = QVBoxLayout(content_widget)
        content_layout.setContentsMargins(2, 2, 2, 2)
        content_layout.addWidget(self.selected_solution_description)
        content_layout.addWidget(self.selected_solution_creator)
        content_layout.addWidget(self.selected_solution_cite)
        content_layout.addStretch()
        scroll.setWidget(content_widget)
        res.addWidget(self.selected_solution_title)
        res.addWidget(self.selected_solution_version)
        res.addWidget(scroll)
        res.addWidget(self._create_action_widget())
        self._update_focus_solution()
        return res_w

    @staticmethod
    def _create_status(title):
        label = QLabel("%s" % title)
        return label

    def _create_status_with_action(self, title, actions):
        label = QPushButton("%s" % title, self)
        self._set_actions(label, actions)
        return label

    def _set_actions(self, button, actions):
        menu = QMenu(button)
        # label.setContextMenuPolicy(Qt.ActionsContextMenu)
        menu.addActions(actions)
        button.setMenu(menu)

    def eventFilter(self, source, event):
        if event.type() == QEvent.KeyPress:
            key = event.key()
            if key == Qt.Key_Return:
                self.return_pressed.emit(event)
            if key == Qt.Key_Escape:
                self.esc_pressed.emit(event)
            if source is self.search_line:
                key = event.key()
                if key == Qt.Key_Left or key == Qt.Key_Right or key == Qt.Key_Up or key == Qt.Key_Down:
                    self.list_widget.keyPressEvent(event)
        return super().eventFilter(source, event)

    def run_action(self):
        item = self._get_currently_selected_item()
        action = item.action
        if action.args:
            action.signal.emit(action.args)
        else:
            action.signal.emit()

    def _get_currently_selected_item(self):
        if len(self.list_widget.selectedIndexes()) > 0:
            source_index = self.proxy.mapToSource(self.list_widget.selectedIndexes()[0])
            item = self.search_model.item(source_index.row())
            return item
        return None

    def update_model(self):
        index = self.album_instance.get_index_as_dict()
        actions_list = []
        self.all_catalogs = []
        self.deletable_catalogs = []
        for catalog in index["catalogs"]:
            for solution in catalog["solutions"]:
                coordinates = "%s:%s:%s" % (
                    solution["setup"]["group"], solution["setup"]["name"], solution["setup"]["version"])
                full_coordinates = "%s:%s" % (catalog["name"], coordinates)
                title = solution["setup"]["title"]
                if not title:
                    title = coordinates
                item = ActionsListModel.ActionItem()
                item.setEditable(False)
                item.setCheckState(2 if solution["internal"]["installed"] else 0)
                item.setText("[%s] %s" % (catalog["name"], title))
                item.text = "[%s] %s" % (catalog["name"], title)
                item.action = CollectionWindow.Action(self.show_solution, full_coordinates, solution)
                item.solution_coordinates = full_coordinates
                actions_list.append(item)
        for catalog in index["catalogs"]:
            self.all_catalogs.append(catalog)
            if catalog["deletable"]:
                self.deletable_catalogs.append(catalog)

        actions_list.sort()
        self.search_model.removeRows(0, self.search_model.rowCount())
        for item in actions_list:
            self.search_model.appendRow(item)
        # self.search_model.set_list(actions_list)
        if self.list_widget:
            self.list_widget.select_first_item()
        self.collection_changed.emit()

    def _choose_update_catalogs(self, pressed):
        options = []
        for catalog in self.deletable_catalogs:
            options.append(catalog["name"])
        dialog = UpdateDialog(options, self.album_instance)
        dialog.upgradeDone.connect(self.collection_changed)
        dialog.exec_()

    def set_active(self):
        pass

    def set_not_active(self):
        pass

    @staticmethod
    def _get_solution_count(index_dict):
        count = 0
        for catalog in index_dict["catalogs"]:
            count += len(catalog["solutions"])
        return count

    @staticmethod
    def _get_installed_solution_count(index_dict):
        count = 0
        for catalog in index_dict["catalogs"]:
            count += len([solution for solution in catalog["solutions"] if solution["internal"]["installed"]])
        return count
