import os
import shutil
import tempfile
from functools import partial

import machineid
import requests
from qgis.core import QgsSettings, QgsZipUtils
from qgis.PyQt.Qt import QUrl
from qgis.PyQt.QtCore import QThread, pyqtSignal, pyqtSlot
from qgis.PyQt.QtGui import QDesktopServices
from qgis.PyQt.QtWidgets import (
    QAbstractItemView,
    QCheckBox,
    QHeaderView,
    QLabel,
    QLineEdit,
    QPushButton,
    QStackedLayout,
    QTableWidget,
    QTableWidgetItem,
)

from openlog.__about__ import (
    DIR_PLUGIN_ROOT,
    __integrated_extensions__,
    __title__,
    __version__,
)

PREMIUM_API = "https://account.apeiron.technology"
EXTENSION_PATH = os.path.join(DIR_PLUGIN_ROOT, "extensions")
APEIRON_URL = "https://apeiron.technology/openlog/"


class ExtensionsHandler:
    def __init__(self, settings_widget) -> None:
        """
        Class for handling premium logging and extensions installations.
        Args:
            - import settings_widget : Widget containing premium UI (ConfigOptionsPage)
        """
        self.settings_widget = settings_widget
        self.extension_grp = self.settings_widget.extension_grp
        self.extension_tbl: QTableWidget = self.settings_widget.extension_tbl
        self.extension_tbl.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.extension_tbl.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.extension_tbl.setSelectionMode(QAbstractItemView.ContiguousSelection)
        self.extension_tbl.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.description_label: QLabel = self.settings_widget.description_label
        self.login_btn: QPushButton = self.settings_widget.login_btn
        self.signup_btn: QPushButton = self.settings_widget.signup_btn
        self.account_btn: QPushButton = self.settings_widget.account_btn
        self.login_info: QLabel = self.settings_widget.login_info
        self.connection_status: QLabel = self.settings_widget.connection_status
        self.install_btn: QPushButton = self.settings_widget.install_btn
        self.uninstall_btn: QPushButton = self.settings_widget.uninstall_btn
        self.description_label: QLabel = self.settings_widget.description_label
        self.user_edit: QLineEdit = self.settings_widget.user_edit
        self.password_edit: QLineEdit = self.settings_widget.password_edit
        self.rmb_me: QCheckBox = self.settings_widget.rmb_me
        self.autologin: QCheckBox = self.settings_widget.autologin

        # table header
        self.extension_tbl.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.description_label.setVisible(False)

        # saved credentials
        self._get_credentials()

        # signals
        self.login_btn.pressed.connect(self._login_premium)
        self.signup_btn.pressed.connect(
            partial(QDesktopServices.openUrl, QUrl(APEIRON_URL))
        )
        self.account_btn.pressed.connect(
            partial(QDesktopServices.openUrl, QUrl(PREMIUM_API))
        )
        self.uninstall_btn.pressed.connect(self._uninstall_extension)
        self.install_btn.pressed.connect(self._install_extension)
        self.extension_tbl.itemSelectionChanged.connect(self._on_selection_changed)

        # installed extensions
        self.installed_extensions = []
        self._find_installed_extensions()

        # disable buttons
        self.install_btn.setEnabled(False)
        self.uninstall_btn.setEnabled(False)

        # available extensions
        self.available_extensions = []

        # credentials
        self.login: str = None
        self.pwd: str = None

        # connection status
        self.authenticated = False
        self.api_errors = ["invalid credentials", "unknown hardware", "expired account"]
        self.connection_status.setText("Status : Disconnected")

        # set stackedlayout to QTablewidget to overlay spinner
        self.stack_layout = QStackedLayout()
        self.extension_tbl.setLayout(self.stack_layout)

        self._requests_at_opening()

    def _requests_at_opening(self):
        """
        Method to be executed inside QThread.
        First extensions metadata are pulled from API, then login.
        """
        self._auto_login()
        self._fill_table()

    def _auto_login(self):
        """
        Method to log automatically.
        """
        if (
            self.autologin.isChecked()
            and self.user_edit is not None
            and self.password_edit is not None
        ):
            self._login_premium()

    def _get_credentials(self) -> None:
        """
        Set previously saved credentials.
        """
        if self.rmb_me.isChecked():
            settings = QgsSettings()
            settings.beginGroup(__title__)
            username = settings.value("openlog_username")
            password = settings.value("openlog_password")
            self.user_edit.setText(username)
            self.password_edit.setText(password)

    def _save_credentials(self) -> None:
        """
        Save current credentials.
        """
        if self.rmb_me.isChecked():
            settings = QgsSettings()
            settings.beginGroup(__title__)
            settings.setValue("openlog_username", self.user_edit.text())
            settings.setValue("openlog_password", self.password_edit.text())

    def _find_installed_extensions(self):
        """
        We scan extensions folder.
        """

        self.installed_extensions = []
        files = os.listdir(EXTENSION_PATH)
        files = [file for file in files if file.__contains__(".dist-info")]
        # find packages names
        package_names = [
            file.replace("openlog_qgis_plugin_", "").split("-")[0] for file in files
        ]
        # find versions
        versions = []
        for file_info in files:
            path_info = os.path.join(EXTENSION_PATH, file_info)
            with open(os.path.join(path_info, "METADATA"), "r") as f:
                text = f.readlines()
            text = [elt for elt in text if elt.split(":")[0] == "Version"][0]
            versions.append(text.replace("Version: ", "").replace("\n", ""))

        for name, version in zip(package_names, versions):
            self.installed_extensions.append({"name": name, "version": version})

    def _get_installed_extension(self, package_name: str) -> dict:
        """
        Return installed extension metadata.
        If not found, return None.
        """
        for ext in self.installed_extensions:
            if ext.get("name") == package_name:
                return ext

        return None

    def _get_package_description(self, package_name: str) -> str:

        for ext in self.available_extensions:
            if ext.get("name") == package_name:
                return ext.get("description")

    def _get_available_extensions(self):
        """
        Pull from API available extensions.
        """
        try:
            user_metadata = {"openlog_version": __version__, "email": self.login}
            response = requests.get(
                PREMIUM_API + "/extensions/", json=user_metadata, timeout=30
            )
            plugins = response.json()
        except Exception:
            plugins = []
        # remove already integrated extensions in Openlog
        plugins = [
            plugin
            for plugin in plugins
            if plugin["name"] not in __integrated_extensions__
        ]
        self.available_extensions = plugins

    def _fill_table(self):
        """
        Fill table widget with available extensions.
        """

        # clear table
        self.extension_tbl.clearSelection()
        self.extension_tbl.clearContents()
        while self.extension_tbl.rowCount() != 0:
            self.extension_tbl.removeRow(self.extension_tbl.rowCount() - 1)

        if len(self.available_extensions) == 0:
            self._get_available_extensions()

        # offline
        if len(self.available_extensions) == 0:
            self._fill_table_offline()

        for plugin in self.available_extensions:
            plugin_name = plugin.get("name")
            plugin_version = plugin.get("version")

            row = self.extension_tbl.rowCount()
            self.extension_tbl.insertRow(row)
            self.extension_tbl.setItem(row, 0, QTableWidgetItem(plugin_name))
            self.extension_tbl.setItem(row, 1, QTableWidgetItem(plugin_version))
            # add installed version if exist
            self._find_installed_extensions()
            installed_version = self._get_installed_extension(plugin_name)
            if installed_version is not None:
                self.extension_tbl.setItem(
                    row, 2, QTableWidgetItem(installed_version["version"])
                )
            else:
                self.extension_tbl.setItem(row, 2, QTableWidgetItem("Not installed"))

    def _fill_table_offline(self) -> None:
        """
        Fill table with only installed extensions.
        """
        self._find_installed_extensions()
        for plugin in self.installed_extensions:
            plugin_name = plugin.get("name")
            plugin_version = plugin.get("version")

            row = self.extension_tbl.rowCount()
            self.extension_tbl.insertRow(row)
            self.extension_tbl.setItem(row, 0, QTableWidgetItem(plugin_name))
            self.extension_tbl.setItem(row, 1, QTableWidgetItem("?"))
            self.extension_tbl.setItem(row, 2, QTableWidgetItem(plugin_version))

    def _login_premium(self):
        """
        Connect to openlog premium API and get available extensions.
        """
        login = self.user_edit.text()
        mdp = self.password_edit.text()
        self.login = login
        self.pwd = mdp
        mac = machineid.id()
        self._save_credentials()
        data = {"login": login, "mdp": mdp, "mac": mac}
        try:
            response = requests.get(PREMIUM_API + "/user_auth/", json=data, timeout=30)
            if response.status_code == 200:
                if response.text in self.api_errors:
                    self.authenticated = False
                    self.login_info.setStyleSheet("color: red;")
                    self.login_info.setText(response.text)
                    self.connection_status.setText("Status : Disconnected")
                else:
                    self.authenticated = True
                    self.connection_status.setText("Status : Connected")
                    self.login_info.setText("")
        except Exception:
            self.login_info.setStyleSheet("color: red;")
            self.login_info.setText(
                "Unable to connect to server. Please check your connection and disable any VPN."
            )
            self.connection_status.setText("Status : Disconnected")
            self.authenticated = False
            return

        # update button availability
        self._on_selection_changed()
        # fill table
        self._fill_table()

    def _on_selection_changed(self):
        """
        Slot for handling button states depending selected extension.
        """

        self.install_btn.setEnabled(False)
        self.uninstall_btn.setEnabled(False)
        selected_indexes = self.extension_tbl.selectedIndexes()
        if len(selected_indexes) > 0:
            self.install_btn.setEnabled(False)
            self.uninstall_btn.setEnabled(False)
            for index in selected_indexes:
                selected_row = index.row()
                name = self.extension_tbl.item(selected_row, 0).text()
                latest_version = self.extension_tbl.item(selected_row, 1).text()
                current_version = self.extension_tbl.item(selected_row, 2).text()
                if current_version != "Not installed":
                    self.uninstall_btn.setEnabled(True)
                if current_version != latest_version:
                    self.install_btn.setEnabled(True)

            # display description
            if len(selected_indexes) == 3:
                description = self._get_package_description(name)
                self.description_label.setVisible(True)
                self.description_label.setText(description)

        # buttons enable/disable
        if not self.authenticated:
            self.install_btn.setEnabled(False)

    def _remove_extension(self, name: str) -> None:
        """
        Remove extensions from folder.
        """
        folder_pattern = "openlog_qgis_plugin_" + name
        for fld in os.listdir(EXTENSION_PATH):
            if folder_pattern in fld:
                shutil.rmtree(os.path.join(EXTENSION_PATH, fld))

    def _uninstall_extension(self):
        """
        Uninstall selected extensions.
        """
        selected_indexes = self.extension_tbl.selectedIndexes()
        for index in selected_indexes:
            selected_row = index.row()
            extension = self.extension_tbl.item(selected_row, 0).text()
            self._remove_extension(extension)

        # refresh table
        self._fill_table()

    def _install_extension(self):
        """
        Install selected extension.
        """
        selected_indexes = self.extension_tbl.selectedIndexes()
        for index in selected_indexes:
            selected_row = index.row()
            extension = self.extension_tbl.item(selected_row, 0).text()
            latest_version = self.extension_tbl.item(selected_row, 1).text()
            current_version = self.extension_tbl.item(selected_row, 2).text()
            if latest_version == current_version:
                continue
            metadata = [
                ext
                for ext in self.available_extensions
                if ext["package_name"] == extension
            ][0]
            json = {
                "login": self.login,
                "mdp": self.pwd,
                "mac": machineid.id(),
                "id_repo": metadata["project_id"],
                "package_name": extension,
                "version": metadata["version"],
                "openlog_version": metadata["openlog_minimal_version"],
            }
            try:
                response = requests.get(
                    PREMIUM_API + "/get_package/", json=json, stream=True
                )
            except Exception:
                self.login_info.setStyleSheet("color: red;")
                self.login_info.setText(
                    "Unable to connect to server. Please try later."
                )
                return

            if response.status_code == 200:
                self._remove_extension(extension)
                # create temp folder
                folder = tempfile.mkdtemp()
                output = os.path.join(folder, "openlog_ex.zip")
                with open(output, "wb") as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            f.write(chunk)
                QgsZipUtils.unzip(zip=output, dir=EXTENSION_PATH)
            # refresh table
            self._fill_table()
