# -*- coding: utf-8 -*-
"""
/***************************************************************************
 PluginMapDefaultDialog
                             -------------------
        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

from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt, QUrl
from qgis.PyQt.QtGui import QDesktopServices
from qgis.PyQt.QtWidgets import (
    QDialog,
)

from . import LOGGER_NAME
from .ext.ndff.connector.connector import (
    NdffConnector,
)
from .ext.ndff.exceptions import (
    NdffLibError,
)
from .ext.ndff.utils import (
    ellipsize2string,
)
from .ndffc_plugin_dataset_dialog import PluginDatasetDialog
from .ndffc_plugin_search_code_dialog import PluginSearchCodeDialog

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_map_or_default_dialog_base.ui"
    )
)


class PluginMapDefaultDialog(QDialog, FORM_CLASS):
    """
    This Dialog is a dialog let the user either select a fieldname from the data, of which the value from a record
    is to be used as value for given field. Or the user can search/select, of copy/paste a value identity uri which
    for given field will be used for ALL records for the observations of this dataset.

    So it will either create a 'field mapping', or a 'field default' for a dataset.
    """

    # Title to be used for all plugin dialogs (if possible)
    PLUGIN_DIALOGS_TITLE = "NDFF Connector Plugin"

    def __init__(self, parent=None):
        """Constructor."""
        super(PluginMapDefaultDialog, self).__init__(parent)
        # Set up the user interface from Designer through FORM_CLASS.
        # After self.setupUi() you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)
        self.lbl_info_url.linkActivated.connect(self.open_in_browser)
        # self.lbl_info_url.setTextInteractionFlags(Qt.TextSelectableByMouse)  # to make sure the user can copy/paste this uri
        self.combo_layer_fields.fieldChanged.connect(self.layer_field_changed)
        # self.le_default_value.textEdited.connect(self.default_value_edited)  # textEdited signal is only emitted in ui and programmatically
        self.le_default_value.textChanged.connect(
            self.default_value_edited
        )  # textChanged signal is BOTH emitted in ui AND when changed programmatically
        self.lbl_default_url.linkActivated.connect(self.open_in_browser)
        self.lbl_default_url.setTextInteractionFlags(
            Qt.TextInteractionFlag.TextSelectableByMouse
        )  # to make sure the user can copy/paste this uri
        self.record = None
        self.field = None
        self.connector = None
        self.layer = None

        self.search_code_dlg = None

        # self.search_code_dlg = PluginSearchCodeDialog(parent=self)
        # self.search_code_dlg = PluginDatasetDialog(parent=self)
        #
        # self.search_code_dlg.setModal(True)
        # self.search_code_dlg.setWindowModality(Qt.ApplicationModal)  # https://doc.qt.io/qt-5/qt.html#WindowModality-enum
        # self.search_code_dlg.finished.connect(self.search_uri_finished)

        self.btn_search_api.clicked.connect(self.search_uri)

        self.max_len = 85  # maximum length of (value) string, to not play havoc with size of buttons/labels/widgets

    @staticmethod
    def open_in_browser(link: str):
        """
        Open given link in the user's default browser to show the content.
        """
        QDesktopServices.openUrl(QUrl(link))

    def accept(self) -> None:
        """
        By Accepting the dialog you are actually SETTING the mapping.
        That is the NdffConnector instance is instructed to set the mappings (not yet written to disk!)
        """
        log.debug(f"OK, clicked {self}")
        data_field = self.combo_layer_fields.currentField()
        default = self.le_default_value.text()
        log.debug(
            f'Mapping of observation "{self.field}" set to data "{data_field}" with default: "{default}" in connector'
        )
        if len(data_field) == 0:  # that is: an empty string: NO mapping
            self.connector.set_field_mapping(self.field, None)
        else:
            self.connector.set_field_mapping(self.field, data_field)
        if (
            len(default) == 0
        ):  # empty input: that is, user removed the uri or value from the input
            self.connector.set_field_default(self.field, None)
        else:
            # add the ndff-field->default to the defaults of the datasource
            self.connector.set_field_default(self.field, default)
            # just to be sure in case of value there is NO mapped data_field
            self.combo_layer_fields.setField("")
        # log.debug(f'Connector now: {self.connector}')
        self.done(QDialog.DialogCode.Accepted)

    def reject(self) -> None:
        """
        Rejecting the dialog, should do nothing
        """
        log.debug(f"CANCEL, clicked {self}")
        self.done(QDialog.DialogCode.Rejected)

    def show(self, layer, connector: NdffConnector, record: dict, field: str) -> None:
        """
        This method 'show' is to be used to init the Dialog and show it to the user
        """
        log.debug(
            f"Calling show of PluginMapDefaultDialog for layer: {layer} field: {field}"
        )
        if layer is None or connector is None or field is None or field == "":
            # should not happen!
            raise NdffLibError(
                f"Calling PluginMapDefaultDialog with some None value: \nlayer: {layer} \nfield: {field} \nconnector: {connector} \nrecord: {record}"
            )
        self.connector = connector
        self.record = record
        self.field = field
        self.layer = layer
        self.combo_layer_fields.setLayer(self.layer)
        label = f" {connector.get_field_text(field)} ({connector.get_field_ndff_name(field)})"
        default_label = ""
        if connector.get_field_url2(field):
            default_label = f"{connector.get_field_url2(field)}"
        info_label = ""
        if (
            connector.get_field_url1(field)
            and len(connector.get_field_url1(field)) > 15
        ):
            f'Zie: <a href="{connector.get_field_url1(field)}">{connector.get_field_ndff_name(field)} in NDFF-woordenboek</a>'
        description_label = f"<i>{connector.get_field_description(field)}</i>"

        if field in ("identity", "location_buffer", "period_start", "period_stop"):
            # search_default_group
            self.btn_search_api.setEnabled(False)
        else:
            self.btn_search_api.setEnabled(True)

        self.lbl_field.setText(f"{label}")
        self.lbl_info_url.setText(f"{info_label}")
        self.lbl_default_url.setText(f"{default_label}")
        self.lbl_description.setText(f"{description_label}")

        mapped_to = connector.get_field_mapping(field)

        if connector.get_field_default(field):
            self.le_default_value.setText(
                ellipsize2string(
                    f"{connector.get_field_default(field)}", max_len=self.max_len
                )
            )
        else:
            self.le_default_value.setText("")
            # PRESETTING THE DIALOG
            if mapped_to in self.record.keys():
                self.combo_layer_fields.setField(mapped_to)
                self.lbl_example_value.setText(
                    ellipsize2string(f"{self.record[mapped_to]}", max_len=self.max_len)
                )
            elif field in self.record.keys():
                # IF field is field in the record, preset it...
                self.combo_layer_fields.setField(field)
                self.lbl_example_value.setText(
                    ellipsize2string(f"{self.record[field]}", max_len=self.max_len)
                )
            else:
                self.combo_layer_fields.setField("")
                self.lbl_example_value.setText("")

        self.showNormal()

    def layer_field_changed(self, data_field: str):
        """
        Setting the dropdown AND the example from data based on a field/attribute from a data record

        So observation_field here is a data attribute
        """
        # show if there is a mapping for field = SET dropdown value AND the corresponding data if a record is available
        log.debug(f"Calling layer_field_changed for data field {data_field}")
        example_label = ""
        if data_field != "" and self.record and self.record[data_field]:
            example_label = self.record[data_field]  # TODO value <=> key ???
            # and cleanup the default value field
            self.le_default_value.setText("")
        self.lbl_example_value.setText(
            ellipsize2string(f"{example_label}", max_len=self.max_len)
        )

    def default_value_edited(self, default_text: str):
        """
        When the user edit's the default value, the (earlier) selected field is to be removed
        """
        if default_text != "":
            # if a default field is defined, let's make the field dropdown empty
            self.combo_layer_fields.setField("")

    def search_uri_finished(self):
        """
        When the user searched for a (default) identity to be used for given field and accepted a result in the
        search code dialog, the result identity will be set as default.
        """
        default_uri = self.search_code_dlg.le_selected_uri.text()
        self.le_default_value.setText(
            ellipsize2string(default_uri, max_len=self.max_len)
        )  # setting it here will also clean up an earlier selected fieldname

    def search_uri(self):
        """
        Going to search for a NDFF uri or code/name/description for this field.

        Either a free name search (like 'man' for sex-field will search for 'man' to find an uri):
            https://accapi.ndff.nl/codes/v2/sexes/?name=man
        OR the other way around: search for description or terms based on the ndff uri
            https://accapi.ndff.nl/codes/v2/sexes/?identity=http://ndff-ecogrid.nl/codes/domainvalues/observation/sexes/undefined

        Note that if searching uri/codes for abundance_value, we have to take the (hopefully already set)
        abundance_SCHEMA. That is: if the schema is Tansley (which has special Tansley related VALUE codes), searching
        for or setting a default VALUE, you have to search in the resources which are mentioned in the Tansley
        resource in 'related_codes'...
        """
        search_for_text = self.le_default_value.text()
        if search_for_text == "":
            # first see if there was data for this field (is shown in self.lbl_example_value.text()
            if self.lbl_example_value.text() not in ("", None, " ", "-"):
                search_for_text = self.lbl_example_value.text()
            else:
                # try lbl_default_url (which is the 'undefined' uri, defined in client_settings.csv)
                search_for_text = self.lbl_default_url.text()

        abundance_schema_uri = None
        if self.field == "abundance_value":
            # OK, the user clicked on the 'abundance_value' button. it IS possible that current 'abundance_schema'
            # has 'related_codes', IF that is the case, we are going to search for them...
            # We do not have the actual observation here, only the record and connector, so map to observation via those:
            observation = self.connector.map_data_to_ndff_observation(self.record)
            abundance_schema_uri = observation.get("abundance_schema")
            # ONLY open the search dialog with related uri's if the abundance_schema has related codes...
            if self.connector.identity_has_related_codes(abundance_schema_uri):
                # then do NOT use the (default/example) value to search, nor the value from the record
                # it seems better to just show ALL related codes (instead of just one based on the current record value)
                # search_for_text = ''
                pass
            else:
                # make sure to NOT use the abundance schema uri. WITH an abundance_schema_uri we will try to find
                # related codes!!
                abundance_schema_uri = None

        # either we are here searching for ndff codes for (user/domain) dataset, OR searching for other fields
        if self.field == "dataset":
            self.search_code_dlg = PluginDatasetDialog(parent=self)
            self.search_code_dlg.setModal(True)
            self.search_code_dlg.setWindowModality(
                Qt.WindowModality.ApplicationModal
            )  # https://doc.qt.io/qt-5/qt.html#WindowModality-enum
            self.search_code_dlg.finished.connect(self.search_uri_finished)
            self.search_code_dlg.show_for_dataset_default(
                self.connector, self.record, self.field
            )
        else:
            description_string = f"Zoek Een Standaard URI voor het veld {self.connector.get_field_text(self.field)}"
            self.search_code_dlg = PluginSearchCodeDialog(parent=self)
            self.search_code_dlg.setModal(True)
            self.search_code_dlg.setWindowModality(
                Qt.WindowModality.ApplicationModal
            )  # https://doc.qt.io/qt-5/qt.html#WindowModality-enum
            self.search_code_dlg.finished.connect(self.search_uri_finished)
            self.search_code_dlg.setup_gui(
                self.connector,
                self.field,
                search_value=search_for_text,
                abundance_schema_uri=abundance_schema_uri,
                description_string=description_string,
            )

        self.search_code_dlg.open()
