# -*- coding: utf-8 -*-
"""
/***************************************************************************
 PluginApiCredentialsDialog
                             -------------------
        begin                : 2021-12-20
        git sha              : $Format:%H$
        copyright            : (C) 2021 by Zuidt
        email                : richard@zuidt.nl
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import logging
import os
import time

from qgis.PyQt import QtWidgets, uic
from qgis.PyQt.QtCore import (
    QRegularExpression,
    QSettings,
)
from qgis.PyQt.QtGui import (
    QRegularExpressionValidator,
)
from qgis.PyQt.QtWidgets import (
    QDialog,
)

from . import LOGGER_NAME
from .ext.ndff.api.ndffapi import (
    NdffApi,
)
from .ext.ndff.connector.connector import (
    NdffConnector,
)
from .ext.ndff.exceptions import (
    NdffLibError,
)

log = logging.getLogger(LOGGER_NAME)

# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
    os.path.join(
        os.path.dirname(__file__), "ndffc_plugin_api_credentials_dialog_base.ui"
    )
)


class PluginApiCredentialsDialog(QtWidgets.QDialog, FORM_CLASS):
    """
    The PluginApiCredentialsDialog is a dialog which started as a dialog to handle the NDFF api credential (sets) of
    a user, but in time also is used to hold some other (advanced) settings, like SSL-validation, Modality of dialogs
    and Logging levels.
    The Dialog has 2 tabs now.

    Note that the credentials if given via the QGIS plugin are NOT(!) saved in the optional 'ndff_api.csv' settings
    file, but in the Qt/QSettings environment in current QGIS profile (ndffc_plugin/credentials) group.
    """

    def __init__(self, connector: NdffConnector, parent=None):
        """
        Constructor of the dialog
        """
        super(PluginApiCredentialsDialog, self).__init__(parent)
        log.debug("PluginApiCredentialsDialog contructor")
        self.setupUi(self)

        self.cmb_api_environment.addItem(
            "Acceptatie Omgeving: https://accapi.ndff.nl",
            userData=NdffApi.ACC_ENVIRONMENT,
        )
        self.cmb_api_environment.addItem(
            "PRODUCTIE Omgeving: https://api.ndff.nl", userData=NdffApi.PRD_ENVIRONMENT
        )
        self.cmb_api_environment.currentIndexChanged.connect(
            self.api_environment_changed
        )

        self.connector = connector

        # because we use this 'short title' as key for the set, we do NOT want special character in it.
        # that is also the reason we force it to have a length of 6 minimum and contain no spaces
        valid_title_regexp = QRegularExpression("[-\\w]{6,}")
        self.le_title.setValidator(QRegularExpressionValidator(valid_title_regexp))

        # fill credential settings combo with available settings sets (from QSettings)
        credential_settings = QSettings()
        credential_settings.beginGroup("ndffc_plugin/credentials")
        credential_sets = credential_settings.childGroups()
        credential_settings.endGroup()
        current = ""  # will be the one that is shown in the dialog
        # remove 'current' IF available
        if "current" in credential_sets:
            credential_sets.remove("current")
            current = "current"
        elif len(credential_sets) > 0:
            # IF we have other credential sets: pick the first one to show
            current = credential_sets[0]
        self.cb_credential_sets.addItems(credential_sets)

        # we are in QGIS, so taking /ndffc_plugin/credentials sets from QSettings,
        # and put all those in the cb_credential_sets
        # AND take the /ndffc_plugin/credentials/current set to put
        self.merge_api_settings_from_qsettings_into_connector(
            self.connector, set_name=current
        )
        self.fill_fields()

        self.btn_test_api.clicked.connect(self.test_api)
        self.cb_credential_sets.currentTextChanged.connect(self.credential_set_changed)

        # look if the level (in __init__.py is set to DEBUG/10, then set the checkbox to CHECKED
        # if level <= DEBUG/10, BUT > 0 set to checked, meaning: show logging
        self.cb_plugin_logs.setChecked(
            0 < logging.getLogger("ndff_connector_plugin").level <= 10
        )
        # https://www.pythonguis.com/tutorials/transmitting-extra-data-qt-signals/
        self.cb_plugin_logs.stateChanged.connect(
            lambda checked: self.show_logs(checked, "ndff_connector_plugin")
        )

        self.cb_api_logs.setChecked(
            0 < logging.getLogger("ndff_connector_plugin.ext.ndff.api").level <= 10
        )
        self.cb_api_logs.stateChanged.connect(
            lambda checked: self.show_logs(
                checked, "ndff_connector_plugin.ext.ndff.api"
            )
        )

        self.cb_connector_logs.setChecked(
            0
            < logging.getLogger("ndff_connector_plugin.ext.ndff.connector").level
            <= 10
        )
        self.cb_connector_logs.stateChanged.connect(
            lambda checked: self.show_logs(
                checked, "ndff_connector_plugin.ext.ndff.connector"
            )
        )

        # ssl_validation setting is either True, 1 or 2 (int or string):
        if QSettings().value("/ndffc_plugin/ssl_validation", True) in (
            True,
            1,
            "1",
            2,
            "2",
        ):
            self.cb_ssl_validation.setChecked(True)
        else:  # 0, False or something else?
            self.cb_ssl_validation.setChecked(False)
        self.cb_ssl_validation.stateChanged.connect(self.ssl_validation_changed)
        if QSettings().value("/ndffc_plugin/main_window_modality", False) in (
            True,
            1,
            "1",
            "true",
        ):
            self.cb_non_modal_dialog.setChecked(True)
        else:
            self.cb_non_modal_dialog.setChecked(False)
        self.cb_non_modal_dialog.stateChanged.connect(self.main_window_modality_changed)

    @staticmethod
    def show_logs(checked, source):
        """
        Fiddling with loggers here, to runtime change the logging levels
        """
        # Qt.Unchecked == 0 Qt.Checked == 2
        if checked:
            logging.getLogger(source).setLevel(logging.DEBUG)
        else:  # unchecked
            logging.getLogger(source).setLevel(logging.WARNING)

    def api_environment_changed(self, index):
        """
        Slot being called when the user changes the ACC/PRD environment combo.
        Note that this only shows something in the log.
        The state is actually kept in the combo item data (and tested used when the user either tests or accepts
        current values of the dialog).
        """
        log.debug(
            f"Environment changed to: {index} = {self.cmb_api_environment.itemData(index)}"
        )

    def ssl_validation_changed(self, checked):
        """
        Slot being called when the user changes the SSL validation checkbox.
        Note that this is SET in QSettings immediately.
        """
        log.debug(f"SSL validation changed to: {checked}")
        # key of ssl_validation will be saved in QSettings and picked up during
        QSettings().setValue("/ndffc_plugin/ssl_validation", checked)
        self.merge_api_settings_from_qsettings_into_connector(
            self.connector, set_name="current"
        )
        self.fill_fields()

    @staticmethod
    def main_window_modality_changed(checked):
        """
        Slot being called when the user changes the Window modality checkbox.
        Note that this is SET in QSettings immediately.
        """
        log.debug(f"Window modality changed to {checked}")
        if checked:
            QSettings().setValue("/ndffc_plugin/main_window_modality", True)
        else:
            QSettings().setValue("/ndffc_plugin/main_window_modality", False)

    def credential_set_changed(self, set_name):
        """
        User changed the credential set in the combobox.

        Make sure all fields are properly filled from state of current NdffConnector instance
        """
        self.merge_api_settings_from_qsettings_into_connector(self.connector, set_name)
        # fill inputs from state of current NdffConnector instance
        self.fill_fields()

    @staticmethod
    def merge_api_settings_from_qsettings_into_connector(
        connector: NdffConnector, set_name="current"
    ):
        """
        Take the settings from QSettings set and merge them with the settings in current NdffConnector instance.
        """
        # Below I thought to pick them up from QSettings, and IF they are present these
        title = QSettings().value(f"/ndffc_plugin/credentials/{set_name}/title", False)
        if title:
            connector.ndff_api_settings["title"] = (
                title.strip()
            )  # just to be sure user did not put spaces on either end
        api_environment = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/api_environment", False
        )
        if api_environment:
            connector.ndff_api_settings["api_environment"] = (
                api_environment.strip()
            )  # just to be sure user did not put spaces on either end
        user = QSettings().value(f"/ndffc_plugin/credentials/{set_name}/user", False)
        if user:
            connector.ndff_api_settings["user"] = (
                user.strip()
            )  # just to be sure user did not put spaces on either end
        password = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/password", False
        )
        if password:
            connector.ndff_api_settings["password"] = (
                password.strip()
            )  # just to be sure user did not put spaces on either end
        client_id = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/client_id", False
        )
        if client_id:
            connector.ndff_api_settings["client_id"] = (
                client_id.strip()
            )  # just to be sure user did not put spaces on either end
        client_secret = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/client_secret", False
        )
        if client_secret:
            connector.ndff_api_settings["client_secret"] = (
                client_secret.strip()
            )  # just to be sure user did not put spaces on either end
        domain = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/domain", False
        )
        if domain:
            connector.ndff_api_settings["domain"] = (
                domain.strip()
            )  # just to be sure user did not put spaces on either end
        domain_key = QSettings().value(
            f"/ndffc_plugin/credentials/{set_name}/domain_key", False
        )
        if domain_key:
            connector.ndff_api_settings["domain_key"] = (
                domain_key.strip()
            )  # just to be sure user did not put spaces on either end
        else:  # domain_key is optional, so: if not in config 'remove' it by setting it to None
            connector.ndff_api_settings["domain_key"] = None
        # it is possible to disable ssl validation of the http client
        validate_ssl = QSettings().value("/ndffc_plugin/ssl_validation", True)
        if validate_ssl:
            log.debug("DEFAULT SSL SETTINGS, OK")
            connector.ndff_api_settings["verify_ssl_certificate"] = True
        else:
            log.info(
                "!!!! WAARSCHUWING: DISABLED SSL SETTINGS, ALLEEN GEBRUIKEN INDIEN NODIG !!!!!"
            )
            connector.ndff_api_settings["verify_ssl_certificate"] = False

        if (
            api_environment
            and user
            and password
            and client_id
            and client_secret
            and domain
        ):
            return True
        else:
            # let's make sure NOTHING is actually set
            # Mmm then we are overriding eventually available settings from ndff settings files, so NOT do this:
            # connector.ndff_api_settings = {}
            return False

    def fill_fields(self):
        """
        Fill all credential fields/inputs in current dialog instance.

        Always fill the dialog inputs with what is available in the connector
        (probably from setting files if QSettings was empty)
        """
        if "title" in self.connector.ndff_api_settings:
            self.le_title.setText(self.connector.ndff_api_settings["title"].strip())
            self.cb_credential_sets.setCurrentText(
                self.connector.ndff_api_settings["title"].strip()
            )
        if "api_environment" in self.connector.ndff_api_settings:
            if (
                self.connector.ndff_api_settings["api_environment"]
                == NdffApi.PRD_ENVIRONMENT
            ):
                self.cmb_api_environment.setCurrentIndex(1)
            else:
                self.cmb_api_environment.setCurrentIndex(0)
        if "user" in self.connector.ndff_api_settings:
            self.le_api_username.setText(self.connector.ndff_api_settings["user"])
        if "password" in self.connector.ndff_api_settings:
            self.le_api_password.setText(self.connector.ndff_api_settings["password"])
        if "client_id" in self.connector.ndff_api_settings:
            self.le_api_client_id.setText(self.connector.ndff_api_settings["client_id"])
        if "client_secret" in self.connector.ndff_api_settings:
            self.le_api_client_secret.setText(
                self.connector.ndff_api_settings["client_secret"]
            )
        if "domain" in self.connector.ndff_api_settings:
            self.le_domain.setText(self.connector.ndff_api_settings["domain"])
        if "domain_key" in self.connector.ndff_api_settings:
            self.le_domain_key.setText(self.connector.ndff_api_settings["domain_key"])

    def test_api(self) -> bool:
        """
        Test the inputs of the user.
        """
        start = time.time()
        self.lbl_test_result.setText("... bezig met testen ...")
        self.lbl_test_result.repaint()
        try:
            if (
                self.le_title.text() in ("None", "", None)
                or len(self.le_title.text()) < 6
            ):
                raise NdffLibError(
                    "De titel is niet geldig: een korte tekst met minstens 6 karakters!"
                )
            # create a temporal api_config (and api) to do the credential and connection testing
            api_config = dict()
            api_config["title"] = (
                self.le_title.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["api_environment"] = self.cmb_api_environment.currentData()
            api_config["user"] = (
                self.le_api_username.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["password"] = (
                self.le_api_password.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["client_id"] = (
                self.le_api_client_id.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["client_secret"] = (
                self.le_api_client_secret.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["domain"] = (
                self.le_domain.text().strip()
            )  # just to be sure user did not put spaces on either end
            api_config["domain_key"] = (
                self.le_domain_key.text().strip()
            )  # just to be sure user did not put spaces on either end
            if self.cb_ssl_validation.checkState() in (True, 1, "1", 2, "2"):
                api_config["verify_ssl_certificate"] = True
            else:
                api_config["verify_ssl_certificate"] = False
            api = NdffApi(
                api_config=api_config, fresh_one=True
            )  # really need to get a fresh one when credentials/input has been changed !!!
            # api.get_new_tokens() # is NIET nodig: bij het aanmaken van de NdffApi met 'fresh_one' wordt dit al gedaan
        except NdffLibError as ndff_exceptie:
            log.debug(f"Error connecting to the api: {ndff_exceptie}")
            self.lbl_test_result.setText(
                "FOUT bij het maken van een verbinding met de NDFF (met deze gegevens)! Kijk in de log messages, of probeer \nin 'Geavanceerde Instellingen' eens zonder SSL validatie en zet daar Logging  aan voor meer info."
            )
            return False
        except Exception as e:
            self.lbl_test_result.setText(
                f"Er heeft zich een (netwerk?) probleem voorgedaan:\n{e}"
            )
            log.debug(f"There was a (network?) error:\n{e}")
            return False

        # now test if we have a valid api connection, should return a 200 because we look for a dataset in a domain
        result = api.test_connection()
        http_status = result["http_status"]
        http_response = result["http_response"]
        if http_status != 200:
            log.debug(
                f"Error connecting to the api: Status: {http_status} Response: {http_response}"
            )
            message = str(http_response)[0:100]
            if (
                http_status
                in [
                    404,
                    403,
                ]
                and "detail" in http_response
            ):  # this returns json with some details
                message = f"Status {http_status}: {http_response['detail']}"
                # a 403 detail is not very descriptive...
                if http_status in [
                    403,
                ]:
                    message += " (binnen dit domein)"
            self.lbl_test_result.setText(
                f"FOUT bij het maken van een verbinding met de NDFF (met deze gegevens)! Kijk in de log messages.\n{message} ..."
            )
            return False
        # OK, all fine, show a succes message in the dialog
        user = api_config["user"]
        acc_or_prd = "PRODUKTIE"
        if (
            "api_environment" in api_config
            and NdffApi.ACC_ENVIRONMENT in api_config["api_environment"].upper()
        ):
            acc_or_prd = "ACCEPTATIE"
        log.debug(f'Connected with NDFF user:"{user}" environment: ({acc_or_prd})')
        self.lbl_test_result.setText(
            f'Succesvolle verbinding als "{user}" met de NDFF ({acc_or_prd}) API gemaakt (in {time.time()-start:.1f} sec.)'
        )
        return True

    def accept(self) -> None:
        """
        Qt api call
        Before closing the dialog, test the settings (again) and make them current.
        AND (only if user clicked OK) create a new NdffConnector instance and safe the tested credentials to QSettings
        """
        log.debug(
            f'OK, clicked. Going to use settings for: "{self.le_api_username.text()}" in domain {self.le_domain.text()}'
        )
        if self.test_api():
            # save as 'CURRENT' set of values to the QSettings of the user
            # AND save ita as a set in a group named after the name of this set
            for credential_set_name in [self.le_title.text(), "current"]:
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/title",
                    self.le_title.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/api_environment",
                    self.cmb_api_environment.currentData(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/user",
                    self.le_api_username.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/password",
                    self.le_api_password.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/client_id",
                    self.le_api_client_id.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/client_secret",
                    self.le_api_client_secret.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/domain",
                    self.le_domain.text(),
                )
                QSettings().setValue(
                    f"/ndffc_plugin/credentials/{credential_set_name}/domain_key",
                    self.le_domain_key.text(),
                )
            # and as the user clicked OK, we will be using THIS set for the current NdffConnector
            self.merge_api_settings_from_qsettings_into_connector(
                self.connector, set_name="current"
            )
            self.done(QDialog.DialogCode.Accepted)

    def reject(self) -> None:
        """
        Qt api call, Closes the dialog, for example if the user clicked 'escape' (also rejects a dialog).
        """
        self.done(QDialog.DialogCode.Rejected)
