# -*- coding: utf-8 -*-
"""
/***************************************************************************
 GeoEASINDockWidget
                                 A QGIS plugin
 Data downloader from EASIN Geospatial Web Service
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2021-12-26
        git sha              : $Format:%H$
        copyright            : (C) 2021 by Patricio Soriano. Geoinnova
        email                : patricio.soriano@geoinnova.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 json
import os
import re
from datetime import datetime
from functools import partial
from urllib import request

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QTreeWidgetItem, QMessageBox
from qgis.PyQt import QtWidgets, uic
from qgis.PyQt.QtCore import pyqtSignal
from qgis.core import Qgis, QgsMessageLog, QgsVectorLayer, QgsGeometry, QgsFeature, QgsProject

from ..tools.tools import replaceSpaces

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'species_search_dockwidget_base.ui'))


def search_for(d, lst):
    if lst:
        return [i for i in lst if all(i[target_key] == target_value for target_key, target_value in d.items())]


class GeoEASINDockWidget(QtWidgets.QDockWidget, FORM_CLASS):
    closingPlugin = pyqtSignal()

    def __init__(self, iface, parent=None):
        """Constructor."""
        super(GeoEASINDockWidget, self).__init__(parent)
        self.setupUi(self)

        self.data = None
        self.data_filter = None
        self.dict_to_search_for = {}
        self.total = 0
        self.sub_total = 0
        self.url = None

        self.iface = iface

        #  search DOCK WIDGETS

        # Search Button
        self.btnSearch.setEnabled(False)
        self.btnSearch.clicked.connect(self.fetch_specie)

        # Clean Button
        self.btnCleanResults.clicked.connect(self.clean_results)

        # Search text
        self.lineSpecieText.textChanged.connect(self.enable_button)

        # TreeData
        self.treeWidgetData.setColumnCount(2)
        self.treeWidgetData.setHeaderItem(QTreeWidgetItem(["Specie", ""]))
        self.treeWidgetData.setColumnWidth(0, 300)
        self.treeWidgetData.itemDoubleClicked.connect(partial(self.add_grid_layer))
        self.treeWidgetData.sortByColumn(0, Qt.AscendingOrder)

        # Tab
        self.tabWidget.setCurrentIndex(0)

        # CheckBox filters
        self.cb_IsEUConcern.setCheckState(1)
        self.cb_IsHighImpact.setCheckState(1)
        self.cb_IsOutermostConcern.setCheckState(1)
        ## API2
        self.cb_IsMSConcern.setCheckState(1)
        self.cb_IsPartNative.setCheckState(1)
        self.cb_IsHorizonScanning.setCheckState(1)

        self.cb_IsEUConcern.setEnabled(False)
        self.cb_IsHighImpact.setEnabled(False)
        self.cb_IsOutermostConcern.setEnabled(False)
        ## API2
        self.cb_IsMSConcern.setEnabled(False)
        self.cb_IsPartNative.setEnabled(False)
        self.cb_IsHorizonScanning.setEnabled(False)


        self.cb_IsEUConcern.stateChanged.connect(lambda: self.apply_filters(self.cb_IsEUConcern))
        self.cb_IsHighImpact.stateChanged.connect(lambda: self.apply_filters(self.cb_IsHighImpact))
        self.cb_IsOutermostConcern.stateChanged.connect(lambda: self.apply_filters(self.cb_IsOutermostConcern))
        ## API2
        self.cb_IsMSConcern.stateChanged.connect(lambda: self.apply_filters(self.cb_IsMSConcern))
        self.cb_IsPartNative.stateChanged.connect(lambda: self.apply_filters(self.cb_IsPartNative))
        self.cb_IsHorizonScanning.stateChanged.connect(lambda: self.apply_filters(self.cb_IsHorizonScanning))

        # Radiobutton filters
        # self.rb_Impact_All.setChecked(True)
        # self.rb_Impact_High.setChecked(False)
        # self.rb_Impact_Low.setChecked(False)
        #
        # self.rb_Impact_All.setEnabled(False)
        # self.rb_Impact_High.setEnabled(False)  # Hi
        # self.rb_Impact_Low.setEnabled(False)  # Lo
        #
        # self.rb_Impact_All.toggled.connect(self.onClickedImpact)
        # self.rb_Impact_High.toggled.connect(self.onClickedImpact)
        # self.rb_Impact_Low.toggled.connect(self.onClickedImpact)

        self.rb_Status_All.setChecked(True)
        self.rb_Status_Alien.setChecked(False)  # A
        self.rb_Status_Cryptogenic.setChecked(False)  # C
        self.rb_Status_Unkhow.setChecked(False)  # N
        self.rb_Status_Questionable.setChecked(False)  # Q

        self.rb_Status_All.setEnabled(False)
        self.rb_Status_Alien.setEnabled(False)  # A
        self.rb_Status_Cryptogenic.setEnabled(False)  # C
        self.rb_Status_Unkhow.setEnabled(False)  # N
        self.rb_Status_Questionable.setEnabled(False)  # Q

        self.rb_Status_All.toggled.connect(self.onClickedStatus)
        self.rb_Status_Alien.toggled.connect(self.onClickedStatus)
        self.rb_Status_Cryptogenic.toggled.connect(self.onClickedStatus)
        self.rb_Status_Unkhow.toggled.connect(self.onClickedStatus)
        self.rb_Status_Questionable.toggled.connect(self.onClickedStatus)

    def closeEvent(self, event):
        self.closingPlugin.emit()
        event.accept()

    def enable_button(self):
        text_length = len(self.lineSpecieText.text())
        if text_length > 3:
            self.btnSearch.setEnabled(True)
        else:
            self.btnSearch.setEnabled(False)

    def enable_filter(self, active: True):
        """
        Manage the checkboxes and the radio buttons state
        @param active:
        @return:
        """
        self.cb_IsEUConcern.setEnabled(active)
        self.cb_IsHighImpact.setEnabled(active)
        self.cb_IsOutermostConcern.setEnabled(active)
        self.cb_IsMSConcern.setEnabled(active)
        self.cb_IsPartNative.setEnabled(active)
        self.cb_IsHorizonScanning.setEnabled(active)
        # self.rb_Impact_All.setEnabled(active)
        # self.rb_Impact_High.setEnabled(active)
        # self.rb_Impact_Low.setEnabled(active)
        self.rb_Status_All.setEnabled(active)
        self.rb_Status_Alien.setEnabled(active)  # A
        self.rb_Status_Cryptogenic.setEnabled(active)  # C
        self.rb_Status_Unkhow.setEnabled(active)  # N
        self.rb_Status_Questionable.setEnabled(active)  # N

    def clean_results(self):
        """
        Clean the research results, reset the the default widgets values and initialize the main variables
        @return:
        """

        self.data = None
        self.data_filter = None
        self.total = 0
        self.sub_total = 0

        # self.lineSpecieText.clear()
        self.treeWidgetData.clear()

        self.requestInfo.setText(f'Info')

        self.enable_filter(False)

        self.cb_IsEUConcern.setCheckState(1)
        self.cb_IsHighImpact.setCheckState(1)
        self.cb_IsOutermostConcern.setCheckState(1)
        self.cb_IsMSConcern.setCheckState(1)
        self.cb_IsPartNative.setCheckState(1)
        self.cb_IsHorizonScanning.setCheckState(1)

        # self.rb_Impact_All.setChecked(True)
        # self.rb_Impact_High.setChecked(False)
        # self.rb_Impact_Low.setChecked(False)

        self.rb_Status_All.setChecked(True)
        self.rb_Status_Alien.setChecked(False)  # A
        self.rb_Status_Cryptogenic.setChecked(False)  # C
        self.rb_Status_Unkhow.setChecked(False)  # N
        self.rb_Status_Questionable.setChecked(False)  # Q

    def fetch_specie(self):
        """

        @return:
        """
        self.clean_results()

        term = self.lineSpecieText.text()
        term2 = re.sub('\\s+', ' ', term)
        term3 = replaceSpaces(term2)

        self.logText.appendPlainText(datetime.now().strftime("%Y/%m/%d, %H:%M:%S"))

        try:
            self.url = f'https://easin.jrc.ec.europa.eu/apixg/catxg/term/{term3}'
            print(self.url)
            # self.url = f'https://easin.jrc.ec.europa.eu/api/cat/term/{term3}'
            req = request.Request(self.url)

            with request.urlopen(req) as f:
                data_json = json.loads(f.read().decode('utf-8'))
                # self.data = data_json['results']
                self.data = data_json
                self.searchAPI(self.data, True)

        except Exception as error:
            print(f'Error: {error}')
            self.requestInfo.setText("Connection error")
            self.logText.appendPlainText("Connection error")
            QgsMessageLog.logMessage(
                f'Error: {error}', level=Qgis.Critical)

    def searchAPI(self, data, add_log=False):
        """

        @param data:
        @param add_log:
        @return:
        """
        msg = 'There are no species corresponding to your search criteria'

        # self.sub_total = len(data) if data and data != msg else 0

        if data == msg:
            self.requestInfo.setText(f'{msg}')
            self.logText.appendPlainText(f'- Term of search: {self.lineSpecieText.text()} ')
            self.logText.appendPlainText(f'- API URL: {self.url} ')
            self.logText.appendPlainText(f'{msg}')
            return None

        else:
            if add_log:
                self.total = len(data) if data and data != msg else 0
                self.logText.appendPlainText(f'- Term of search: {self.lineSpecieText.text()} ')
                self.logText.appendPlainText(f'- API URL: {self.url} ')
                self.logText.appendPlainText(f'- Total species found: {self.total}')
                self.requestInfo.setText(f'Total species found: {self.total}')
            else:
                self.requestInfo.setText(f'Filter results: {self.sub_total} of {self.total} ')

        try:
            self.enable_filter(True)
            for dataLevel0 in data:
                # speciesName = dataLevel0['SpeciesName']
                speciesName = dataLevel0['Name']
                # speciesCatalogueId = dataLevel0['SpeciesCatalogueId']
                speciesCatalogueId = dataLevel0['EASINID']
                item_level0 = QTreeWidgetItem(self.treeWidgetData,
                                              [speciesName, 'Add grid to map: ' + speciesCatalogueId, ''])
                font = QFont()
                font.setBold(True)
                item_level0.setFont(0, font)
                item_level0.setForeground(1, QColor("blue"))
                item_level0.setToolTip(1, 'Double click to add')

                # print(item_level0)
                self.treeWidgetData.addTopLevelItem(item_level0)

                for dataLevel1 in dataLevel0.items():
                    key_level_1 = dataLevel1[0]
                    value_level_1 = dataLevel1[1]
                    if type(value_level_1) is list:
                        item_level_1 = QTreeWidgetItem(item_level0, [key_level_1])
                        self.treeWidgetData.addTopLevelItem(item_level_1)
                        for dataLevel2 in value_level_1:
                            for datalevel3 in dataLevel2.items():
                                key_level_3 = datalevel3[0]
                                value_level_3 = str(datalevel3[1])
                                item_level2 = QTreeWidgetItem(item_level_1, [key_level_3, value_level_3])
                                self.treeWidgetData.addTopLevelItem(item_level2)

                    elif type(value_level_1) is dict:
                        item_level_1 = QTreeWidgetItem(item_level0, [key_level_1])
                        self.treeWidgetData.addTopLevelItem(item_level_1)
                        for datalevel3 in value_level_1.items():
                            key_level_3 = datalevel3[0]
                            value_level_3 = str(datalevel3[1])
                            item_level2 = QTreeWidgetItem(item_level_1, [key_level_3, value_level_3])
                            self.treeWidgetData.addTopLevelItem(item_level2)
                    else:
                        item_level_1 = QTreeWidgetItem(item_level0, [key_level_1, str(value_level_1)])
                        self.treeWidgetData.addTopLevelItem(item_level_1)
        except:
            self.requestInfo.setText(f'Info')
            # print('except')

    def add_grid_layer(self, treeitem, item):
        """

        @param treeitem:
        @param item:
        @return:
        """

        get_selected = self.treeWidgetData.selectedItems()

        if get_selected:
            base_node = get_selected[0]
            getChildNode = base_node.text(item)
            if "Add grid to map" in getChildNode:
                name_layer = base_node.text(0)
                idgrid = base_node.text(1).split(":")[1].strip()
                advice_message = 'Depending on the number of grids to download, the process may take some time. \n\n' \
                                 'Do you wish to continue? '
                reply = QMessageBox.question(self.iface.mainWindow(), 'Add grid to map', advice_message,
                                             QMessageBox.Yes,
                                             QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    print('0. Acepta info y Lanza la funcion create_layer', 'idgrid', idgrid)
                    self.create_layer(idgrid, name_layer)
                else:
                    print('Cancel by user')

    def apply_filters(self, control_name):
        """
        @param control_name:
        @return:
        """

        self.treeWidgetData.clear()
        print('apply_filters', self.data)

        filter_mapping = {
            'Is EU concern': 'IsEUConcern',
            'Is High Impact': 'IsHighImpact',
            'Is Outer most Concern': 'IsOutermostConcern',
            'Is MS Concern': 'IsMSConcern',
            'Is Part Native': 'IsPartNative',
            'Is Horizon Scanning': 'IsHorizonScanning'
        }

        attribute_name = filter_mapping.get(control_name.text())

        if attribute_name is None:
            return  # Exit if the control name is not recognized

        data_filter = []
        check_state = control_name.checkState()

        if check_state == 2:
            item = {attribute_name: True}
            self.dict_to_search_for.update(item)
            data_filter = search_for(self.dict_to_search_for, self.data)
        elif check_state == 0:
            item = {attribute_name: False}
            self.dict_to_search_for.update(item)
            data_filter = search_for(self.dict_to_search_for, self.data)
        else:
            del self.dict_to_search_for[attribute_name]
            data_filter = search_for(self.dict_to_search_for, self.data)

        self.treeWidgetData.clear()
        self.sub_total = len(data_filter) if data_filter else 0
        self.searchAPI(data_filter)


    def onClickedImpact(self):
        """

        @return:
        """

        radio_btn = self.sender()
        data_filter = []
        print('onClickedImpact',self.data)

        if radio_btn.isChecked():
            print(radio_btn.text())
            if radio_btn.text() == 'True':
                item = {"IsHighImpact": True, }
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)



            elif radio_btn.text() == 'False':
                item = {"IsHighImpact": False, }
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)

            else:
                del self.dict_to_search_for["IsHighImpact"]
                data_filter = search_for(self.dict_to_search_for, self.data)

        self.treeWidgetData.clear()
        self.sub_total = len(data_filter) if data_filter else 0
        self.searchAPI(data_filter)

    def onClickedStatus(self):
        """

        @return:
        """

        radio_btn = self.sender()
        data_filter = []

        if radio_btn.isChecked():
            if radio_btn.text() == 'Alien':
                item = {"Status": "A", }
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)

            elif radio_btn.text() == 'Cryptogenic':
                item = {"Status": "C", }
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)

            elif radio_btn.text() == 'Questionable':
                item = {"Status": "Q", }
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)

            elif radio_btn.text() == 'Unkhow':
                item = {"Status": "N"}
                self.dict_to_search_for.update(item)
                data_filter = search_for(self.dict_to_search_for, self.data)

            else:
                del self.dict_to_search_for["Status"]
                data_filter = search_for(self.dict_to_search_for, self.data)

        self.treeWidgetData.clear()
        # self.total = len(data) if data and data != msg else 0
        self.sub_total = len(data_filter) if data_filter else 0
        self.searchAPI(data_filter)

    def create_layer(self, species_catalogue_id, species_name=''):
        """

        @param species_catalogue_id:
        @param species_name:
        @return:
        """
        speciesid = species_catalogue_id
        speciesname = species_name
        skip = 0
        len_results = 1
        layer_name = f"{speciesid}_{speciesname}"
        grid_count = 0


        temp = QgsVectorLayer(
            "Polygon?crs=epsg:3035"
            "&field=LayerRecordId:string&index=yes"
            "&field=SpeciesId:string"
            "&field=SpeciesName:string"
            "&field=YearMin:int"
            "&field=YearMax:int"
            "&field=Reference:string(400)"
            "&field=Native:boolean"
            "&field=DataPartner:string",
            layer_name, "memory")

        def fetch_data(speciesid, skip):

            # url_grid = f'https://easin.jrc.ec.europa.eu/api/geo/speciesid/' \
            # f'{speciesid}/layertype/grid/take/50/skip/{skip}'
            url_grid = f'https://easin.jrc.ec.europa.eu/apixg/geoxg/speciesid/' \
                       f'{speciesid}/layertype/grid/skip/{skip}/take/50'

            print(url_grid)

            # self.logText.appendPlainText(f'- API Geospatial Web Service URL: {url_grid} ')

            req = request.Request(url_grid)

            self.requestInfo.setText(f'...requesting grid info')

            try:
                with request.urlopen(req) as f:
                    respuesta = json.loads(f.read().decode('utf-8'))
                    ## print(respuesta)
                    return respuesta

            except Exception as error:
                print(f'Error: {error}')
                self.requestInfo.setText(f"- API Error: {error}")
                self.logText.appendPlainText(f"- API Error: {error}")
                QgsMessageLog.logMessage(
                    f'API Error: {error}', level=Qgis.Critical)

        def addGrid(temp, apidata):
            temp.startEditing()
            # print('apidata', apidata)
            for feature in apidata:
                geom = QgsGeometry.fromWkt(feature['Wkt'])
                feat = QgsFeature()
                feat.setGeometry(geom)

                year_min = feature['YearMin'].replace("    ", "0")
                year_max = feature['YearMax'].replace("    ", "0")

                feat.setAttributes([
                    feature['LayerRecordId'],
                    feature['SpeciesId'],
                    feature['SpeciesName'],
                    int(year_min),
                    int(year_max),
                    feature['Reference'],
                    feature['Native'],
                    feature['DataPartner'],
                ])
                temp.dataProvider().addFeatures([feat])

            temp.commitChanges()

        results = fetch_data(speciesid, skip)

        if isinstance(results, dict):
            self.requestInfo.setText(
                f"Info: There are no results based on your search critieria. Please check the parameters you passed.")
            self.logText.appendPlainText(f"- Info: {results}")
        else:
            while len_results > 0 and isinstance(results, list):
                results = fetch_data(speciesid, skip)
                print('1.- fetch_data', len_results)
                print('2.- len_results', len_results)
                is_a_list = isinstance(results, list)
                print('3.- comprueba si es una lista', is_a_list)
                if is_a_list:
                    skip += 50
                    print('4.- skip += 50', skip)
                    ## results = new_data['results']
                    len_results = len(results)
                    grid_count += len_results
                    print('5.- Actualiza nº de grids', grid_count)
                    print('6.- lanzaría addGridd')
                    addGrid(temp, results)

        if temp.featureCount() > 0:
            # temp.renderer().symbol().setColor(QColor("red"))
            # temp.triggerRepaint()
            QgsProject.instance().addMapLayer(temp)

            self.requestInfo.setText(f'{layer_name} layer added. Total grids: {temp.featureCount()}')
            self.iface.messageBar().pushMessage("INFO", f'{layer_name} layer added.', Qgis.Info, 5)

            self.logText.appendPlainText('')
            self.logText.appendPlainText(datetime.now().strftime("%Y/%m/%d, %H:%M:%S"))
            self.logText.appendPlainText(f'- Grid layer added: {layer_name}')
            self.logText.appendPlainText(f'- Total grids: {temp.featureCount()}')
            self.logText.appendPlainText(f'- Attributtes summary:')


            attributes_list = ["YearMin", "YearMax", "DataPartner", "Reference", "Native"]

            def log_attribute_values(attribute_name, feature_iterator):
                self.logText.appendPlainText(f"   * Field: '{attribute_name}'")
                values_count_dic = {}

                for feature in feature_iterator:
                    value = feature[attribute_name]
                    if value in values_count_dic:
                        values_count_dic[value] += 1
                    else:
                        values_count_dic[value] = 1

                for value, count in values_count_dic.items():
                    log_line_reference = f"    + ({count}) {value}"
                    self.logText.appendPlainText(log_line_reference)

                self.logText.appendPlainText("")

            for attribute_name in attributes_list:
                log_attribute_values(attribute_name, temp.getFeatures())

            advice_message = 'Do you want to zoom in the new layer?'
            reply = QMessageBox.question(self.iface.mainWindow(), 'Add grid to map', advice_message,
                                         QMessageBox.Yes,
                                         QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                self.iface.setActiveLayer(temp)
                self.iface.zoomToActiveLayer()
            else:
                print('Cancel')

        else:
            print('No results')
