import json
import re

import numpy as np
from PyQt5.QtGui import QColor
from qgis.core import QgsSymbol, QgsSvgMarkerSymbolLayer, QgsSettings, QgsMessageLog, Qgis
from PyQt5.QtCore import pyqtSignal, QObject

from .path_utils import create_existing_icon_path, get_spatial_entities_path, load_entity_embedding_json
from .name_matching import StringMatcher, find_best_matching_entity, find_best_match_by_jarowinkler

DEFAULT_ICON_SIZE = 2
MIN_JARO_WINKLER_SIMILARITY = 0.75


def load_spatial_entities():
    with open(get_spatial_entities_path(), 'r') as file:
        data = json.load(file)
    return data

class Matcher(QObject, StringMatcher):
    step_signal = pyqtSignal()

    def __init__(self, api_key: str = None, language: str = 'English'):
        super().__init__()
        self.spatial_entities = load_spatial_entities()
        self.api_key = api_key
        self.language = language
        self.spatial_entity_embeddings = None
        self.has_embeddings = []
        self.has_no_embeddings = []
        self.settings = QgsSettings()
        self.log_enabled = self.settings.value("/plugins/AIAMAS/log_enabled", False, bool)

    def match(self, layers):
        # First try embedding matching for all layers
        log_messages = []
        layers_to_fallback = []
        
        if len(layers) > 0:
            self.spatial_entity_embeddings = load_entity_embedding_json(
                "https://api.llmhub.infs.ch/embedding", 
                "intfloat/multilingual-e5-large-instruct"
            )
            
            for layer in layers:
                if layer.embedding is not None:
                    best_match, best_similarity = find_best_matching_entity(
                        layer.embedding, 
                        self.spatial_entity_embeddings
                    )
                    
                    if best_match is not None and best_similarity >= 0.8:  # Good embedding match
                        style_set = False
                        for entity in self.spatial_entities['data']:
                            for lang in ["name_en", "name_de", "name_fr"]:
                                for entity_name in entity[lang]:
                                    if entity_name == best_match and not style_set:
                                        set_style(layer, hex_string_to_qcolor(entity['color']), entity['icons'])
                                        style_set = True
                                        if self.log_enabled:
                                            log_messages.append(f"\n{round(best_similarity, 2)}; {layer.name}; {', '.join(entity['name_en'])}; {', '.join(entity['color'])}; {', '.join(entity['icons'])}")
                                        break
                            if style_set:
                                break
                    else:
                        layers_to_fallback.append(layer)  # Add to fallback list
                else:
                    layers_to_fallback.append(layer)  # No embedding, add to fallback list
                
                self.step_signal.emit()
            
            if self.log_enabled and log_messages:
                QgsMessageLog.logMessage(
                    "Verbose log of AIAMAS style suggestions using LLM-based semantic similarity.\n" + 
                    "Similarity; Layer name; Entity name; Color suggestions; Icon suggestions" + 
                    "".join(log_messages) + "\n", 
                    "AIAMAS", 
                    Qgis.Info
                )
        
        # Fallback to Jaro-Winkler for layers that need it
        if layers_to_fallback:
            log_messages = self.match_by_jarowinkler(layers_to_fallback)
            if self.log_enabled:
                QgsMessageLog.logMessage(
                    "Verbose log of AIAMAS style suggestions using Jaro-Winkler similarity (fallback).\n" + 
                    "Similarity; Layer name; Entity name; Color suggestions; Icon suggestions" + 
                    "".join(log_messages) + "\n", 
                    "AIAMAS", 
                    Qgis.Info
                )

    def match_by_jarowinkler(self, layers):
        """
        QGIS-specific wrapper function that uses the pure logic function to process layers.
        """
        log_messages = []
        for layer in layers:
            best_match, best_entity, colors, icons = find_best_match_by_jarowinkler(
                layer.normalized_name,
                self.spatial_entities,
                self.language
            )

            hex_color = hex_string_to_qcolor(colors)
            set_style(layer, hex_color, icons, best_match)
            if self.log_enabled and best_entity:
                log_messages.append(f"\n{round(best_match, 2)}; {layer.name}; {', '.join(best_entity['name_en'])}; {', '.join(best_entity['color'])}; {', '.join(best_entity['icons'])}")
                                
            self.step_signal.emit()
        return log_messages

    def match_embeddings(self, layers):
        log_messages = []
        self.spatial_entity_embeddings = load_entity_embedding_json("https://api.llmhub.infs.ch/embedding", "intfloat/multilingual-e5-large-instruct")

        if self.spatial_entity_embeddings is None or len(self.spatial_entity_embeddings) == 0:
            return

        for layer in layers:
            best_match, best_similarity = find_best_matching_entity(layer.embedding, self.spatial_entity_embeddings)

            if best_match is not None:
                style_set = False
                for entity in self.spatial_entities['data']:
                    for lang in ["name_en", "name_de", "name_fr"]:
                        for entity_name in entity[lang]:
                            if entity_name == best_match and style_set == False:
                                set_style(layer, hex_string_to_qcolor(entity['color']), entity['icons'])
                                style_set = True
                                if self.log_enabled:
                                    log_messages.append(f"\n{round(best_similarity, 2)}; {layer.name}; {', '.join(entity['name_en'])}; {', '.join(entity['color'])}; {', '.join(entity['icons'])}")
                                break
                        if style_set:
                            break
            self.step_signal.emit()
        return log_messages


def set_style(layer, colors, icons, best_match=None):
    layer.suggested_colors = colors
    layer.suggested_icons = icons

    layer.suggested_symbol = QgsSymbol.defaultSymbol(layer.qgs_layer.geometryType())
    layer.suggested_colors.insert(0, layer.qgs_layer.renderer().symbol().color())
    if best_match is None or best_match > MIN_JARO_WINKLER_SIMILARITY:
        layer.suggested_symbol.changeSymbolLayer(0, QgsSvgMarkerSymbolLayer(
            str(create_existing_icon_path(layer.suggested_icons[0])), DEFAULT_ICON_SIZE + 5, 0))
        layer.suggested_icon_path = str(layer.suggested_icons[0])
        layer.suggested_symbol.setColor(layer.suggested_colors[1])
    else:
        layer.is_default_symbol = True
        layer.suggested_symbol.setColor(layer.suggested_colors[0])
        



def hex_string_to_qcolor(hex_string):
    qcolor = []
    for i in range(len(hex_string)):
        string = hex_string[i].lstrip('#')

        # Extracting the RGB values
        r = int(string[0:2], 16)
        g = int(string[2:4], 16)
        b = int(string[4:6], 16)

        # Creating a QColor object
        qcolor.append(QColor(r, g, b))

    return qcolor

