import json
import os
from datetime import datetime

from qgis.PyQt.QtCore import QDir, Qt, qVersion
from qgis.PyQt.QtWidgets import (
    QDialog,
    QFileDialog,
    QInputDialog,
    QLabel,
    QMessageBox,
    QTreeWidgetItem,
)
from qgis.PyQt.uic import loadUi
from qgis.core import QgsApplication, QgsTask
from qgis.utils import Qgis, QgsMessageLog, iface

from .Function import read_version
from .db.Check_tab import CheckTab
from ..ui.custom_control import _qt_is_checked

QT_VERSION = [int(v) for v in qVersion().split(".")][0]


class ClassDlgExport(QDialog):
    """
    Class allow to export schema
    """

    def __init__(self, mgis):
        QDialog.__init__(self)
        self.mgis = mgis
        self.mdb = self.mgis.mdb
        self.iface = iface
        self.ui = loadUi(os.path.join(self.mgis.masplugPath, "ui/ui_export.ui"), self)
        self.cond_com = None
        self.parent = {}
        self.child = {}
        self.list_ver = []
        self.listeRuns = []
        self.listeScen = {}
        self.init_gui()

    def init_gui(self):
        """
        initialisation GUI
        """
        if QT_VERSION > 5:
            qt_tris = Qt.ItemFlag.ItemIsAutoTristate
            qt_item_check = Qt.ItemFlag.ItemIsUserCheckable
            qt_ucheck = Qt.CheckState.Unchecked

        else:
            qt_tris = Qt.ItemIsAutoTristate
            qt_item_check = Qt.ItemIsUserCheckable
            qt_ucheck = Qt.Unchecked

        liste_col = self.mdb.list_columns("runs")
        self.cond_com = "comments" in liste_col
        self.b_cancel.clicked.connect(self.annule)
        if not self.mdb.check_schema_into_db():
            QMessageBox.warning(self, "Warning", "\t No data in  database")
            self.b_export.hide()
            return
        dico = self.mdb.select("runs", "", "date")

        if self.cond_com:
            for run, scen, date, comments in zip(
                dico["run"], dico["scenario"], dico["date"], dico["comments"]
            ):
                if run not in self.listeRuns:
                    self.listeRuns.append(run)
                    self.listeScen[run] = []
                self.listeScen[run].append((scen, date, comments))
        else:
            for run, scen, date in zip(dico["run"], dico["scenario"], dico["date"]):
                if run not in self.listeRuns:
                    self.listeRuns.append(run)
                    self.listeScen[run] = []
                self.listeScen[run].append((scen, date))

        if len(self.listeRuns) > 0:
            self.parent = {}
            self.child = {}

            for run in self.listeRuns:
                self.parent[run] = QTreeWidgetItem(self.tw_runs)
                self.parent[run].setText(0, run)
                item_flag = self.parent[run]

                item_flag.setFlags(item_flag.flags() | qt_tris | qt_item_check)

                lbl = QLabel("")
                self.tw_runs.setItemWidget(self.parent[run], 2, lbl)

                self.child[run] = {}
                maxi = datetime(1900, 1, 1, 0, 0)
                if self.cond_com:
                    for scen, date, comments in self.listeScen[run]:
                        self.child[run][scen] = QTreeWidgetItem(self.parent[run])
                        item_flag = self.child[run][scen]
                        item_flag.setFlags(item_flag.flags() | qt_item_check)
                        item_flag.setCheckState(0, qt_ucheck)

                        self.child[run][scen].setText(0, scen)

                        lbl = QLabel("{:%d/%m/%Y %H:%M}".format(date))
                        self.tw_runs.setItemWidget(self.child[run][scen], 1, lbl)

                        maxi = max(maxi, date)
                        lbl = QLabel(comments)
                        self.tw_runs.setItemWidget(self.child[run][scen], 2, lbl)
                else:
                    for scen, date in self.listeScen[run]:
                        self.child[run][scen] = QTreeWidgetItem(self.parent[run])
                        item_flag = self.child[run][scen]
                        item_flag.setFlags(item_flag.flags() | qt_item_check)
                        item_flag.setCheckState(0, qt_ucheck)
                        self.child[run][scen].setText(0, scen)

                        lbl = QLabel("{:%d/%m/%Y %H:%M}".format(date))
                        self.tw_runs.setItemWidget(self.child[run][scen], 1, lbl)

                        maxi = max(maxi, date)

                lbl = QLabel("{:%d/%m/%Y %H:%M}".format(maxi))
                self.tw_runs.setItemWidget(self.parent[run], 1, lbl)

        self.b_export.clicked.connect(self.export)

        self.cb_all_exp_res.stateChanged.connect(self.state_changed)

    def state_changed(self):
        """

        :return:
        """
        if self.cb_all_exp_res.isChecked():
            self.tw_runs.setEnabled(False)
        else:
            self.tw_runs.setEnabled(True)

    def export(self):
        """export results selected"""
        selection = {}
        if self.cb_all_exp_res.isChecked():
            for run in self.listeRuns:
                selection[run] = []
                for tple in self.listeScen[run]:
                    selection[run].append("'{}'".format(tple[0]))
        else:
            for run in self.listeRuns:
                if _qt_is_checked(self.parent[run], check_level="any"):
                    selection[run] = []
                    if self.cond_com:
                        for scen, date, comments in self.listeScen[run]:
                            if _qt_is_checked(self.child[run][scen], check_level="partial_or_full"):
                                selection[run].append("'{}'".format(scen))
                    else:
                        for scen, date in self.listeScen[run]:
                            if _qt_is_checked(self.child[run][scen], check_level="partial_or_full"):
                                selection[run].append("'{}'".format(scen))

        file = self.choix_file()
        if file != "":
            self.mgis.add_info("**** Run Export ****")
            # self.mdb.export_model(selection,file)
            plug_ver = read_version(self.mgis.masplugPath)
            self.mgis.task_exp = QgsTask.fromFunction(
                "Export Schema",
                self.task_export,
                on_finished=self.completed,
                tup=(selection, file, plug_ver),
            )
            self.mgis.task_exp.taskCompleted.connect(self.del_task_exp)
            self.mgis.task_exp.taskTerminated.connect(self.del_task_exp)
            QgsApplication.taskManager().addTask(self.mgis.task_exp)

            self.accept()
        else:
            return

    def del_task_exp(self):
        """
        Clean tastk_mas variable
        :return:
        """
        del self.mgis.task_exp
        self.mgis.task_exp = None

    def completed(self, exception):
        """this is called when run is finished. Exception is not None if run
        raises an exception. Result is the return value of run."""
        message_category = "My tasks from a function"
        self.mdb.ignor_schema = list()
        if exception is None:
            QgsMessageLog.logMessage("task completed", message_category, Qgis.Info)
        else:
            QgsMessageLog.logMessage(
                "Exception: {}".format(exception), message_category, Qgis.Critical
            )
            raise exception

    def task_export(self, task, tup=None):
        if tup:
            selection, file, plug_ver = tup
        else:
            self.mgis.add_info("no transmitted variable")
            return
        self.mdb.export_model(selection, file, plug_ver)
        return

    def annule(self):
        """ "Cancel"""
        self.close()

    def choix_file(self):
        """
        Database Export function
        :param self:
        :return:
        """
        # choix du fichier d'exportatoin

        file_name_path, _ = QFileDialog.getSaveFileName(
            self, "saveFile", os.path.join(QDir.homePath(), self.mdb.SCHEMA), filter="JSON (*.json)"
        )
        return file_name_path


class ClassDlgImport(QDialog):
    """
    Class allow to export schema
    """

    def __init__(self, mgis):
        QDialog.__init__(self)
        self.mgis = mgis
        self.mdb = self.mgis.mdb
        self.iface = iface
        self.ui = loadUi(os.path.join(self.mgis.masplugPath, "ui/ui_import.ui"), self)

        self.jsfile = None
        self.metadict = None
        self.checkdict = {
            "ver_plug": None,
            "updat_plug": False,
            "updat_db": False,
            "pgsql_version": None,
            "updat_pgsql": False,
            "schema_name": None,
            "exist_schema": False,
            "err": 0,
        }
        self.bool_ver_plug = True
        self.bool_ver_postgres = True
        self.bool_name_schema = True
        self.init_gui()

    def init_gui(self):
        """
        initialisation GUI
        """
        chkt = CheckTab(self, self.mdb)
        self.list_ver = chkt.list_hist_version
        # self.list_ver = [int(v.replace('.','')) for v in self.list_ver]
        self.checkdict["ver_plug"] = read_version(self.mgis.masplugPath)
        self.checkdict["pgsql_version"] = self.mdb.version_postgres()
        self.txt_file.textChanged[str].connect(self.change_file)
        self.btn_open_file.clicked.connect(self.get_file)
        self.btn_cancel.clicked.connect(self.annule)
        self.btn_valid.clicked.connect(self.import_db)

    def change_file(self):
        """
        check text
        :return:
        """
        txt = self.txt_file.text()
        self.metadict = None
        if os.path.isfile(txt):
            self.read_meta(txt)
            self.fill_gui()
        else:
            self.clean_gui()

    def get_file(self):
        """
        Get file name
        :return:
        """
        file_name_path, _ = QFileDialog.getOpenFileName(
            None,
            "File Selection",
            self.mgis.repProject,
            # QDir.homePath(),
            filter="JSON (*.json)",
        )
        self.mgis.up_rep_project(file_name_path)
        if file_name_path != "":
            self.txt_file.setText(file_name_path)

    def read_meta(self, filein):
        """
        Read meta data file
        and fill the GUI
        :param filein:
        :return:
        """
        with open(filein, "r") as file:
            self.metadict = json.load(file)

    def clean_gui(self):
        """
        clean gui
        :return:
        """
        self.txt_shem.setText("")
        self.txt_vplug.setText("")
        self.txt_vpgsql.setText("")

    def fill_gui(self):
        """
        fill_gui
        :return:
        """
        self.txt_shem.setText(self.metadict["schema_name"])
        self.txt_vplug.setText(self.metadict["plugin_version"])
        self.txt_vpgsql.setText(
            ".".join([str(v) for v in self.conv_ver_psql(self.metadict["pgsql_version"])])
        )

        if self.check_metadata():
            self.check_ver_plug()
            self.check_name_schema()
            self.check_ver_postgres()

        self.txt_vplug.setStyleSheet("color: green")
        self.txt_vpgsql.setStyleSheet("color: green")
        self.txt_shem.setStyleSheet("color: green")

        if self.checkdict["updat_plug"]:
            self.txt_vplug.setStyleSheet("color: red")
        elif self.checkdict["updat_db"]:
            self.txt_vplug.setStyleSheet("color: orange")
        if self.checkdict["updat_pgsql"]:
            self.txt_vpgsql.setStyleSheet("color: red")
        if self.checkdict["exist_schema"]:
            self.txt_shem.setStyleSheet("color: orange")
        if self.checkdict["err"] > 0:
            txt = "There is an error in the metadata file (.json)\n"
            if self.checkdict["err"] == 1:
                txt += "The plugin version doesn't exist."
                self.txt_vplug.setStyleSheet("color: red")

            QMessageBox.critical(None, "Error", txt)

    def check_metadata(self):
        """
        check metadata file
        :return:
        """
        if self.metadict is None:
            self.checkdict["err"] = 2
            return False
        else:
            self.checkdict["err"] = 0
            return True

    def check_ver_plug(self):
        """
        Fill  checkdict
        :return: True if import is possible if not False
        """
        self.checkdict["updat_db"] = False
        self.checkdict["updat_plug"] = False
        vplug = [int(v) for v in self.metadict["plugin_version"].split(".")]
        maxv = self.list_ver[-1]
        maxv = [int(v) for v in maxv.split(".")]
        if maxv == vplug:
            return True
        elif self.compa_ver(vplug, maxv):
            self.checkdict["updat_plug"] = True
            return False
        # check in list
        for val in self.list_ver:
            elem = [int(v) for v in val.split(".")]
            if elem == vplug:
                self.checkdict["updat_db"] = True
                return True
        self.checkdict["err"] = 1
        return False

    def check_ver_postgres(self):
        """
        check version of postgres
        :return:
        """
        self.checkdict["updat_pgsql"] = False
        vpsql = self.conv_ver_psql(self.metadict["pgsql_version"])

        if self.checkdict["pgsql_version"]:
            vdb = self.conv_ver_psql(self.checkdict["pgsql_version"])
        else:
            vdb = None
            self.mgis.add_info("WARNING: Check the database connection")
        if not self.compa_ver(vdb, vpsql):
            self.checkdict["updat_pgsql"] = True

    def conv_ver_psql(self, ver):
        """
        Convert psql version in list
        :param ver: format of the psql version
        :return: the version in list
        """
        strlst = []
        wow = []
        newver = [c for c in ver]
        newver.reverse()
        cpt = 1
        for i, val in enumerate(newver):
            wow.append(val)
            if cpt == 2:
                cpt = 1
                wow.reverse()
                wow = int("".join(wow))
                strlst.append(wow)
                wow = []
            else:
                cpt += 1
        if wow:
            wow.reverse()
            wow = int("".join(wow))
            strlst.append(wow)
        strlst.reverse()
        return strlst

    def compa_ver(self, vdb, vmeta):
        """
        Comparaison of version
        :param vdb: version of database
        :param vmeta:  version of importation
        :return: True if  vdb >= vmeta
        """
        cond = False
        if vdb is None or vmeta is None:
            return cond
        if vdb[0] > vmeta[0]:
            cond = True
        else:
            if vdb[1] > vmeta[1] and vdb[0] == vmeta[0]:
                cond = True
            else:
                if vdb[2] >= vmeta[2] and vdb[0] == vmeta[0] and vdb[1] == vmeta[1]:
                    cond = True
        return cond

    def check_name_schema(self):
        """check if name exists yet"""
        self.checkdict["exist_schema"] = False
        lst_schema = self.mdb.list_schema()
        if self.metadict["schema_name"] in lst_schema:
            self.checkdict["exist_schema"] = True

    def import_db(self):
        """
        import of database
        :return:
        """
        txt = self.txt_file.text()
        if txt != "" and os.path.isfile(txt):
            self.jsfile = txt
        else:
            QMessageBox.warning(None, "Warning", "Please enter a valid filename?")
            return

        dir = os.path.dirname(self.jsfile)
        file_name = os.path.splitext(os.path.basename(self.jsfile))[0]
        psqlfile = os.path.join(dir, file_name + ".psql")
        if not os.path.isfile(psqlfile):
            QMessageBox.critical(None, "Error", "Not found {}".format(psqlfile))
            return

            # Impossible to import
        bool_import = True
        if self.checkdict["err"] > 0:
            bool_import = False
            # import impossible
            txt = ""
            if self.checkdict["err"] == 2:
                txt = "Metafile isn't right."
            QMessageBox.critical(None, "Error", "Import isn't possible.\n {}".format(txt))
            return
        elif self.checkdict["updat_plug"]:
            bool_import = False
            QMessageBox.critical(
                None, "Error", "Import isn't possible, because the Plugin version is superior."
            )
            return
        elif self.checkdict["updat_pgsql"]:
            # import impossible
            bool_import = False
            QMessageBox.critical(
                None, "Error", "Import isn't possible, because the Postgres version is superior."
            )
            return

        # change name
        if self.checkdict["exist_schema"]:
            bool_import = False
            while not bool_import:
                if QT_VERSION > 5:
                    y_bt = QMessageBox.StandardButton.Yes
                    n_bt = QMessageBox.StandardButton.No
                    cancel_bt = QMessageBox.StandardButton.Cancel
                else:
                    y_bt = QMessageBox.Yes
                    n_bt = QMessageBox.No
                    cancel_bt = QMessageBox.Cancel
                rep = QMessageBox.question(
                    self,
                    "MessageBox",
                    "The {} schema already exists.\n "
                    "Do you want overwrite the schema ?".format(self.metadict["schema_name"]),
                    y_bt | n_bt | cancel_bt,
                    cancel_bt,
                )

                if rep == y_bt:
                    bool_import = True
                elif rep == n_bt:
                    newname, ok = QInputDialog.getText(
                        self, "new Model", "Change the name of new model:"
                    )
                    if ok and newname != "":
                        self.metadict["schema_name"] = newname
                        self.check_name_schema()
                        if not self.checkdict["exist_schema"]:
                            bool_import = True
                        else:
                            txt = f"the name {newname} exists yet"
                            QMessageBox.critical(
                                None, "Error", "Import isn't possible.\n {}".format(txt)
                            )
                            return
                    else:
                        txt = "the name is empty."
                        QMessageBox.critical(
                            None, "Error", "Import isn't possible.\n {}".format(txt)
                        )
                        return
                else:
                    QMessageBox.warning(None, "Warning", "Import is canceled.\n {}".format(txt))
                    return

        if bool_import:
            self.metadict["jsfile"] = self.jsfile
            self.metadict["psqlfile"] = psqlfile
            self.mdb.remove_group_layer("Mas_{}".format(self.metadict["schema_name"]))
            # self.mdb.import_model(self.metadict)
            self.mgis.task_imp = QgsTask.fromFunction(
                "Import Schema", self.task_import, on_finished=self.completed, tup=None
            )
            self.mgis.task_imp.taskCompleted.connect(self.del_task_imp)
            self.mgis.task_imp.taskTerminated.connect(self.del_task_imp)
            QgsApplication.taskManager().addTask(self.mgis.task_imp)
            self.accept()

        return

    def del_task_imp(self):
        """
        Clean tastk_mas variable
        :return:
        """
        del self.mgis.task_imp
        self.mgis.task_imp = None

    def completed(self, exception):
        """this is called when run is finished. Exception is not None if run
        raises an exception. Result is the return value of run."""
        message_category = "My tasks from a function"
        self.mdb.ignor_schema = list()
        if exception is None:
            QgsMessageLog.logMessage("task completed", message_category, Qgis.Info)
        else:
            QgsMessageLog.logMessage(
                "Exception: {}".format(exception), message_category, Qgis.Critical
            )
            raise exception

    def task_import(self, task, tup=None):
        self.mdb.import_model(self.metadict)
        return

    def annule(self):
        """ "Cancel"""
        self.close()


class CloneTask(QgsTask):
    """Clone schema Class"""

    def __init__(self, description, mgis, dest):
        super().__init__(description, QgsTask.CanCancel)
        self.mdb = mgis.mdb
        self.src = self.mdb.SCHEMA
        self.dest = dest
        self.message_category = "cloneTask"
        self.exception = None
        self.err = False
        self.first = True

    def run(self):
        """Run function to Clone schema"""
        QgsMessageLog.logMessage(
            'Started task "{}"'.format("clone"), self.message_category, Qgis.Info
        )
        qry = "SELECT clone_schema(%s, %s, '')"
        err = self.mdb.run_query(qry, params=(self.src, self.dest))
        self.err = not err
        self.finished()
        return not err

    def finished(self, result=None):
        """
        display message when the task is finished
        :param result: boolean
        """
        QgsMessageLog.logMessage("Running", self.message_category, Qgis.Info)
        if not self.first:
            if self.err:
                QgsMessageLog.logMessage(
                    "CloneTask is completed", self.message_category, Qgis.Success
                )
            else:
                if self.exception is None:
                    QgsMessageLog.logMessage(
                        "CloneTask is in error", self.message_category, Qgis.Warning
                    )
                else:
                    QgsMessageLog.logMessage(
                        "CloneTask is in error Exception: {exception}".format(
                            exception=self.exception
                        ),
                        self.message_category,
                        Qgis.Critical,
                    )
                    raise self.exception
        self.first = False

    def cancel(self):
        """cancel function ( can not use)"""
        QgsMessageLog.logMessage(" CloneTask was canceled", self.message_category, Qgis.Info)
        super().cancel()
