# -*- coding: utf-8 -*-
"""
/***************************************************************************
 PluginSearchCodeDialog
                             -------------------
        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 (
    QItemSelection,
    Qt,
    QUrl,
)
from qgis.PyQt.QtGui import (
    QDesktopServices,
    QGuiApplication,
    QStandardItem,
    QStandardItemModel,
)
from qgis.PyQt.QtWidgets import (
    QAbstractItemView,
    QDialog,
    QDialogButtonBox,
    QTableView,
)

from . import LOGGER_NAME
from .ext.ndff.connector.connector import (
    NdffConnector,
)
from .ext.ndff.utils import (
    ellipsize2string,
)

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_search_code_dialog_base.ui'))


class PluginSearchCodeDialog(QDialog, FORM_CLASS):
    """
    Dialog to search for codes based on a search string or identity.
    It's also used to search for related codes of given identity.

    The user is supposed to select a row in the result table, which then is the 'accepted' result.

    It will connect to the NDFF api and
    - search the api for Ndff Codes, See: https://accapi.ndff.nl/codes/v2/
    OR
    - search for RELATED codes (e.g. the abundance_value uri's for Tansley/Londo etc.)

    Possible types to search for:

    abundancies
    categories
    codes
    activities
    biotopes
    determinationmethods
    dwellings
    extrainfo
    lifestages
    origins
    protocols
    roletypes
    sexes
    speciesgroups
    subjecttypes
    surveymethods
    taxa
    """

    # warning text in case user did not select or typed input
    WARNING = 'Selecteer een URI uit de lijst, of gebruik "Annuleer/Cancel"'

    def __init__(self, parent=None):
        """
        Constructor for this PluginSearchCodeDialog
        """
        super(PluginSearchCodeDialog, 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.connector = None
        self.current_ndff_field = None
        self.related_to_uri = None

        self.btn_search_api.clicked.connect(self.search_code)
        self.result_model = QStandardItemModel()
        self.table_results.setModel(self.result_model)

        # select full row upon clicking on a cell / row
        self.table_results.setSelectionBehavior(QTableView.SelectRows)
        # only one row selectable at a time
        self.table_results.setSelectionMode(QAbstractItemView.SingleSelection)
        # make table cells non-editable:
        self.table_results.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_results.selectionModel().selectionChanged.connect(self.result_row_clicked)
        # search while typing is too slow...?
        # self.le_search_value.textChanged.connect(self.search_api)
        self.le_search_value.textChanged.connect(self.search_value_changed)
        self.le_search_value.returnPressed.connect(self.search_code)
        # trying to NOT close the dialog upon enter pressed
        self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False)

        # self.lbl_selected.setTextInteractionFlags(Qt.TextSelectableByMouse)  # to make sure the user can copy/paste this uri
        self.lbl_selected.linkActivated.connect(self.open_in_browser)

    def setup_gui(self, connector: NdffConnector, observation_field: str, search_value: str,
                  abundance_schema_uri: str = None, description_string: str = 'Mapping maken voor het veld:'):
        """
        To be able to re-use this dialog (without creating one every time), the setup_ui is to be used/called.
        """
        self.lbl_description.setText(description_string)
        self.connector = connector
        # log.debug(f'Setup search code gui: api={connector.get_api()}, field={observation_field}, search_text={search_value}')
        self.current_ndff_field = observation_field
        self.le_search_value.setText(f'{search_value}')
        self.lbl_field.setText(f'({self.current_ndff_field })')
        self.lbl_api_result.setText('')

        # opening the dialog, try to lookup uri's based on current search_value
        # BUT if the field to search in is abundance_value, AND there is a valid abundance_schema_uri, search for
        # related codes in the abundance_schema_uri domain, also extrainfo (TODO)
        if self.current_ndff_field == 'abundance_value' and abundance_schema_uri:
            self.lbl_field.setText(f'({self.current_ndff_field }), binnen "{abundance_schema_uri}" de waarde "{search_value}"')
            self.related_to_uri = abundance_schema_uri
            self.search_code()  # first search without clicking 'search' button
        else:
            self.related_to_uri = None
            self.search_code()  # first search without clicking 'search' button

        self.result_model.setHeaderData(0, Qt.Horizontal, "Naam")
        # self.result_model.setHeaderData(1, Qt.Horizontal, "Wetenschappelijke Naam")
        self.result_model.setHeaderData(2, Qt.Horizontal, "NDFF-URI (Identity)")
        self.result_model.setHeaderData(3, Qt.Horizontal, "Link")
        self.result_model.setHeaderData(4, Qt.Horizontal, "Omschrijving")

    def search_code(self):
        """
        Actual method doing 'the work'. It's called upon creation of the dialog (so it will immediately show results).

        Or can be called by the user (after new input)
        """
        QGuiApplication.setOverrideCursor(Qt.WaitCursor)

        # cleanup
        self.lbl_api_result.setText('')
        self.result_model.removeRows(0, self.result_model.rowCount())
        self.le_selected_uri.clear()
        self.lbl_selected.setText('')

        page_size = 100
        max_hits = 500  # NOTE it is best if max_hits is an exact multiple of page_size, as else the result will be more than max_hits

        if self.related_to_uri:
            # RELATED CODES
            search_text = None
            if self.le_search_value.text() not in ('', None):
                search_text = self.le_search_value.text()
                log.debug(f'Searching related codes: "{self.related_to_uri=}"')
            codes = self.connector.get_related_codes(self.related_to_uri, search_text=search_text)
        else:
            # 'NORMAL' CODES
            search_type = self.current_ndff_field
            search_field = 'name'
            log.debug(f'Searching codes: "{self.le_search_value.text()}" "{search_type=}" "{search_field=}"')
            codes = self.connector.search_codes(search_text=self.le_search_value.text(), search_type=search_type, search_field=search_field, max_hits=max_hits, page_size=page_size)

        if len(codes) == 0:
            log.debug('-> No results...')
            self.lbl_api_result.setText(f'De zoekterm "{ellipsize2string(self.le_search_value.text(), max_len=60)}" leverde GEEN resultaten, probeer een andere zoekterm.')

        if len(codes) >= max_hits:
            log.info(f'Search for codes got to many hits, limiting to {len(codes)}')
            self.lbl_api_result.setText(f'De zoekterm "{ellipsize2string(self.le_search_value.text(), max_len=60)}" leverde meer dan {max_hits} hits en is afgebroken, verfijn uw zoekterm.')

        for item in codes:
            name = QStandardItem(item['name'])
            identity = QStandardItem(item['identity'])  # this is the visual identity, NOT the internal identity (the 'self'-uri)
            if 'scientific' in item:
                scientific = QStandardItem(item['scientific'])
            else:
                scientific = QStandardItem('')
            if item['_links']['self']['href']:
                self_href = QStandardItem(item['_links']['self']['href'])
            else:
                self_href = QStandardItem('')
            if 'description' in item:
                description = QStandardItem(item['description'])
            else:
                description = QStandardItem('')
            self.result_model.appendRow([name, scientific, identity, self_href, description])

        # some pretty-up the table Headers:
        if self.current_ndff_field == 'taxon':
            self.result_model.setHeaderData(1, Qt.Horizontal, "Wetenschappelijke Naam")
            self.table_results.setColumnWidth(1, 200)
        else:
            self.result_model.setHeaderData(1, Qt.Horizontal, "")
            self.table_results.setColumnWidth(1, 20)
        # hide te column with numbers:
        # self.table_results.verticalHeader().setVisible(False)
        # make the width's of the columns fit as good as possible:
        self.table_results.resizeColumnToContents(0)
        self.table_results.resizeColumnToContents(1)
        self.table_results.resizeColumnToContents(2)
        self.table_results.resizeColumnToContents(3)
        self.table_results.setColumnWidth(0, 200)
        self.table_results.horizontalHeader().setStretchLastSection(True)
        # back to normal cursor...
        QGuiApplication.restoreOverrideCursor()

    def result_row_clicked(self, selection_idx: QItemSelection):
        """
        Method to handle the user clicking a row in the result table.

        It will clean/fill the self.lbl_selected which will hold the actual result.
        """
        self.lbl_selected.setText('')
        if selection_idx.isEmpty():
            return
        if selection_idx.indexes()[0].isValid() and selection_idx.indexes()[0].siblingAtColumn(2).isValid():
            name = self.result_model.data(selection_idx.indexes()[0].siblingAtColumn(0))
            uri = self.result_model.data(selection_idx.indexes()[0].siblingAtColumn(2))
            link = self.result_model.data(selection_idx.indexes()[0].siblingAtColumn(3))
            description = self.result_model.data(selection_idx.indexes()[0].siblingAtColumn(4))
            self.le_selected_uri.setText(uri)
            result_txt = f'{name}  - - {uri} - - <a href="{link}">{link}</a><br/>{description}'
            self.lbl_selected.setText(result_txt)
        else:
            log.debug('Row clicked but non valid index...')  # should never happen

    def search_value_changed(self):
        """
        When the user changes the search input, the result table is to be cleaned, and the old selection is removed.
        """
        self.lbl_api_result.setText('')
        if self.le_search_value.text() == '':
            self.result_model.removeRows(0, self.result_model.rowCount())
            self.lbl_selected.setText('')

    def accept(self) -> None:
        """
        Qt api call
        The dialog is only Accepted when the user actually selected an identity.
        To otherwise close the dialog the user has to Reject it.
        """
        log.debug('OK, clicked (Search Code Dialog)')
        if self.le_selected_uri.text() in ('', self.WARNING):
            self.le_selected_uri.setText(self.WARNING)
        else:
            self.done(QDialog.Accepted)

    def reject(self) -> None:
        """
        Qt api call
        Reject the dialog: that is the client is NOT supposed to use the self.le_selected_uri result.
        """
        # we clean up the le_selected_uri in case there is the old warning
        # clients of this dialog will grab the text from le_selected_uri...
        if self.le_selected_uri.text() == self.WARNING:
            self.le_selected_uri.setText('')
        self.done(QDialog.Rejected)

    @staticmethod
    def open_in_browser(link: str):
        """
        To make it possible for users to actually see the content of an identity or url, clicking on url's will open
        that url in a browser
        """
        QDesktopServices.openUrl(QUrl(link))
