# styles.py
import os
from qgis.core import (
    QgsVectorLayer,
    QgsProject,
    QgsFillSymbol,
    QgsLineSymbol,
    QgsMarkerSymbol,
    QgsRuleBasedRenderer,
    QgsSingleSymbolRenderer,
    QgsFillSymbol,
    QgsLineSymbol,
    QgsRasterMarkerSymbolLayer,
    QgsSimpleLineSymbolLayer,
    QgsSimpleFillSymbolLayer,
    QgsRendererCategory,
    QgsCategorizedSymbolRenderer,
    QgsRenderContext,
    QgsWkbTypes
)
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtCore import Qt
from pprint import pprint
from .layer import (
    _get_border,
    _get_base_background,
    _get_background_pattern,
    _get_icon
)

LINE_WIDTH_FACTOR = 1  # globale Skalierung für Linienbreiten


def _append_icon_layers(symbol, icon_data, geom_type):
    """Hängt Icon-Symbol-Layer (auch Multi-Icon) an ein Symbol an."""
    result = _get_icon(icon_data, LINE_WIDTH_FACTOR, geom_type=geom_type)
    if result is None:
        return
    layers = result if isinstance(result, list) else [result]
    for layer in layers:
        if layer is not None:
            symbol.appendSymbolLayer(layer)


def _append_style_layers(symbol, style_layers, geom_type):
    """Hängt zusätzliche Border- und Icon-Layer aus styleLayers an ein Symbol an.

    styleLayers (Index 2+ aus Style-DB) sind separate Sub-Layer, jeder mit
    optionaler Border und optionalem Icon - z.B. Polygone mit Icon-Outline
    (Anpflanzung mit Baum-Icons entlang der Kante).
    """
    if not isinstance(style_layers, list):
        return
    for sl in style_layers:
        if not isinstance(sl, dict):
            continue
        if sl.get("borderColor"):
            border_layer = _get_border(sl, LINE_WIDTH_FACTOR)
            if border_layer is not None:
                symbol.appendSymbolLayer(border_layer)
        if sl.get("icon"):
            _append_icon_layers(symbol, sl, geom_type=geom_type)

def _find_server_styles_for_layer(vlayer, server_styles):
    """Findet ALLE passenden Server-Styles für einen Layer (kann mehrere sein!)"""
    if not server_styles:
        return []

    layer_ids = set()
    try:
        # Sammle ALLE Features, nicht nur die ersten 200!
        for f in vlayer.getFeatures():  # KEIN LIMIT mehr!
            # common field names: try several possibilities
            for possible in ("gml_id", "gmlid", "gmlId", "id", "ID", "fid", "gml_identifier"):
                idx = f.fields().indexFromName(possible)
                if idx != -1:
                    val = f[possible]
                    if val is not None:
                        layer_ids.add(str(val).strip())
                        break  # Gehe zum nächsten Feature sobald wir eine ID haben
            # also add feature id as string (fallback)
            layer_ids.add(str(f.id()))
    except Exception:
        pass

    # also include layer name itself
    layer_ids.add(vlayer.name())

    # Sammle ALLE passenden Styles
    matching_styles = []
    for style_entry in server_styles:
        ids = style_entry.get("i") or []
        ids = {str(x).strip() for x in ids if x is not None}

        matched_ids = layer_ids & ids

        if matched_ids:
            matching_styles.append({
                "style": style_entry.get("v"),
                "name": style_entry.get("n", None),
                "n_value": style_entry.get("n", None),
                "ids": list(matched_ids)
            })

    return matching_styles

def apply_style_to_gml_layers(gml_path: str, default_name: str = "Layer", server_styles: list = None, synthetic_icon_points: list = None):
    tmp_layer = QgsVectorLayer(gml_path, default_name, "ogr")
    sublayers = tmp_layer.dataProvider().subLayers()
    if not sublayers:
        return []

    project = QgsProject.instance()
    root = project.layerTreeRoot()
    group_name = os.path.splitext(os.path.basename(gml_path))[0] or default_name
    # Gruppe ganz oben einfügen (Position 0)
    plan_group = root.insertGroup(0, default_name or group_name)

    # Sammle ALLE Memory-Layer aus ALLEN Sublayers BEVOR wir sie hinzufügen
    all_mem_layers_with_priority = []

    for sub_info in sublayers:
        name = sub_info.split("!!::!!")[1]
        uri = f"{gml_path}|layername={name}"
        vlayer = QgsVectorLayer(uri, name, "ogr")
        if not vlayer.isValid():
            continue

        # --- Split by geometry type ---
        geom_groups = {}
        for feat in vlayer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty():
                continue

            # Get full WKB type (e.g., MultiPolygon, LineString, etc.)
            wkb_type = geom.wkbType()

            # Get readable base geometry type (e.g., "Point", "Line", "Polygon")
            base_type = QgsWkbTypes.geometryType(wkb_type)
            gtype = QgsWkbTypes.geometryDisplayString(base_type)

            geom_groups.setdefault(gtype, []).append(feat)

        # --- Create memory layers with priority tracking ---
        for gtype, feats in geom_groups.items():
            first_geom = feats[0].geometry()
            wkb_type = first_geom.wkbType()

            wkb_name = QgsWkbTypes.displayString(wkb_type)

            mem_layer = QgsVectorLayer(f"{wkb_name}?crs={vlayer.crs().authid()}",
                                       f"{name} ({gtype})", "memory")
            mem_layer_data = mem_layer.dataProvider()
            mem_layer_data.addAttributes(vlayer.fields())
            mem_layer.updateFields()
            mem_layer_data.addFeatures(feats)

            # --- Find matching styles and determine priority ---
            matching_styles = _find_server_styles_for_layer(mem_layer, server_styles)

            # Bestimme die Priorität (niedrigster n_value = unten im Stack)
            if matching_styles:
                min_n_value = min([s.get("n_value") or 0 for s in matching_styles])
            else:
                min_n_value = 9999  # Layers ohne Style ganz oben

            all_mem_layers_with_priority.append({
                'layer': mem_layer,
                'styles': matching_styles,
                'priority': min_n_value,
                'gtype': gtype,
                'sublayer_name': name
            })

    # Sortiere ALLE Layer aus ALLEN Sublayers nach Priority
    # Geometrie-Typ-basierte Sortierung: Polygon unten, Line in der Mitte, Point oben
    # Innerhalb des gleichen Geometrie-Typs nach n_value sortieren
    def get_sort_key(item):
        gtype = item['gtype']
        n_value = item['priority']

        # Geometrie-Typ Basis-Wert (Point muss IMMER über Polygon liegen)
        # Höhere Basis = weiter oben in der Layer-Reihenfolge
        if gtype == 'Point':
            geom_base = 2000000  # Points (PPOs) ganz oben
        elif gtype == 'Line':
            geom_base = 1000000  # Lines in der Mitte
        else:  # Polygon
            geom_base = 0        # Polygons unten

        # Kombiniere Geometrie-Basis mit n_value
        return geom_base + n_value

    # reverse=False: Niedrigste Priorität zuerst → werden zuerst hinzugefügt
    # Mit insertLayer(0, ...) wird jeder neue Layer oben eingefügt,
    # dadurch landen niedrige Prioritäten unten und hohe oben
    all_mem_layers_with_priority.sort(key=get_sort_key, reverse=False)

    # Layer zum Projekt hinzufügen und Styles anwenden
    all_layers = []
    for item in all_mem_layers_with_priority:
        mem_layer = item['layer']
        matching_styles = item['styles']

        # Styles innerhalb des Layers auch nach n_value sortieren
        if matching_styles:
            matching_styles.sort(key=lambda x: x.get("n_value") or 0)

            for match in matching_styles:
                _apply_server_style_safe(mem_layer, match["style"], match["ids"])
        else:
            _apply_default_safe(mem_layer)

        project.addMapLayer(mem_layer, False)
        # insertLayer(0, ...) fügt oben ein → vorherige Layer rutschen nach unten
        plan_group.insertLayer(0, mem_layer)
        all_layers.append(mem_layer)

    # PPO/Point-Layer explizit nach oben verschieben (müssen immer sichtbar sein)
    _move_point_layers_to_top(plan_group)

    # Synthetische Icon-Punkte erstellen (für Polygon-Icons mit fester Position)
    # Die API sendet nur GML-IDs und Icon-Daten, QGIS berechnet die Punkt-Position
    if synthetic_icon_points:
        icon_layer = _create_synthetic_icon_layer(synthetic_icon_points, all_layers, tmp_layer.crs())
        if icon_layer and icon_layer.isValid():
            project.addMapLayer(icon_layer, False)
            # Als obersten Layer einfügen (Index 0)
            plan_group.insertLayer(0, icon_layer)
            all_layers.append(icon_layer)

    return all_layers


def _move_point_layers_to_top(group):
    """Verschiebt alle Point-Layer (PPO) an die Spitze der Gruppe.

    PPO-Layer müssen immer über Polygon- und Line-Layern sichtbar sein.
    """
    from qgis.core import QgsLayerTreeLayer, QgsVectorLayer

    # Sammle alle Point-Layer und deren aktuelle Positionen
    point_layers_info = []
    for i, node in enumerate(group.children()):
        if isinstance(node, QgsLayerTreeLayer):
            layer = node.layer()
            if layer and isinstance(layer, QgsVectorLayer):
                # geometryType(): 0 = Point, 1 = Line, 2 = Polygon
                if layer.geometryType() == 0:
                    point_layers_info.append({
                        'layer': layer,
                        'node': node,
                        'index': i
                    })

    if not point_layers_info:
        return

    # Verschiebe jeden Point-Layer an den Anfang (Index 0)
    # Rückwärts iterieren damit die Reihenfolge erhalten bleibt
    for info in reversed(point_layers_info):
        layer = info['layer']
        old_node = info['node']

        # Neuen Node erstellen und an Position 0 einfügen
        new_node = QgsLayerTreeLayer(layer)
        group.insertChildNode(0, new_node)

        # Alten Node entfernen (ist jetzt weiter unten weil wir oben eingefügt haben)
        group.removeChildNode(old_node)


def _create_synthetic_icon_layer(synthetic_points, polygon_layers, crs):
    """Erstellt einen Point-Layer für synthetische Icon-Punkte.

    Die API sendet nur GML-IDs und Icon-Daten.
    QGIS berechnet die Punkt-Position mit pointOnSurface() - das ist CRS-korrekt
    und garantiert einen Punkt INNERHALB der Fläche (auch bei U/L-Formen).
    """
    from qgis.core import QgsField, QgsFeature, QgsGeometry
    from qgis.PyQt.QtCore import QVariant

    if not synthetic_points:
        return None

    # Baue ein Mapping von GML-ID zu Liste von Icon-Daten (Multi-Icon je Fläche)
    # Fallback-Icons für fehlende PPO-Mappings ("new"/"neu") überspringen
    icons_by_gml_id = {}
    for pt in synthetic_points:
        gml_id = pt.get("gmlId", "")
        if not gml_id:
            continue
        if pt.get("icon") in ("new", "neu"):
            continue
        if not pt.get("iconUrl"):
            continue
        icons_by_gml_id.setdefault(gml_id, []).append(pt)

    if not icons_by_gml_id:
        return None

    # Memory Layer erstellen
    mem_layer = QgsVectorLayer(
        f"Point?crs={crs.authid()}",
        "Flächen-Icons (synthetisch)",
        "memory"
    )

    if not mem_layer.isValid():
        return None

    provider = mem_layer.dataProvider()

    # Felder für Icon-Daten (Offset pro Icon, damit Multi-Icons nebeneinander
    # gerendert werden statt übereinander)
    provider.addAttributes([
        QgsField("gml_id", QVariant.String),
        QgsField("icon", QVariant.String),
        QgsField("icon_url", QVariant.String),
        QgsField("icon_rotate", QVariant.Double),
        QgsField("icon_offset_x", QVariant.Double),
        QgsField("icon_offset_y", QVariant.Double),
    ])
    mem_layer.updateFields()

    # Suche Polygon-Features in allen Layern und berechne pointOnSurface
    features = []
    processed_gml_ids = set()

    for layer in polygon_layers:
        if not layer or not layer.isValid():
            continue

        # Nur Polygon-Layer verarbeiten
        if layer.geometryType() != 2:  # 2 = Polygon
            continue

        # Finde GML-ID Feld
        gml_field = None
        for field_name in layer.fields().names():
            if field_name.lower() in ["gml_id", "gmlid", "_gml_id", "fid", "id"]:
                gml_field = field_name
                break

        if not gml_field:
            continue

        # Iteriere über Features
        for feature in layer.getFeatures():
            gml_id = str(feature[gml_field]).strip() if gml_field else None

            if not gml_id or gml_id in processed_gml_ids:
                continue

            if gml_id not in icons_by_gml_id:
                continue

            # Feature gefunden - berechne pointOnSurface
            geom = feature.geometry()
            if geom.isEmpty():
                continue

            # pointOnSurface() garantiert einen Punkt INNERHALB der Fläche
            point_geom = geom.pointOnSurface()
            if point_geom.isEmpty():
                continue

            # Pro Icon ein eigenes Feature mit eigenem Offset (Multi-Icon).
            # Server-Spacing (iconSize*12) ist für den Web-Viewer skaliert -
            # bei QGIS-Icons (Basis 25px) überlappen die Icons. Faktor sorgt
            # für klare Trennung in Pixel-Einheiten.
            MULTI_ICON_OFFSET_SCALE = 2.8
            for icon_data in icons_by_gml_id[gml_id]:
                offset = icon_data.get("iconOffset") or [0, 0]
                if not isinstance(offset, (list, tuple)) or len(offset) < 2:
                    offset = [0, 0]

                feat = QgsFeature()
                feat.setFields(mem_layer.fields())
                feat.setGeometry(point_geom)

                feat["gml_id"] = gml_id
                feat["icon"] = icon_data.get("icon", "")
                feat["icon_url"] = icon_data.get("iconUrl", "")
                feat["icon_rotate"] = icon_data.get("iconRotate", 0)
                feat["icon_offset_x"] = float(offset[0]) * MULTI_ICON_OFFSET_SCALE
                feat["icon_offset_y"] = float(offset[1]) * MULTI_ICON_OFFSET_SCALE

                features.append(feat)

            processed_gml_ids.add(gml_id)

    if not features:
        return None

    provider.addFeatures(features)
    mem_layer.updateExtents()

    # Style für Icons anwenden
    _apply_synthetic_icon_style(mem_layer, synthetic_points)

    return mem_layer


def _apply_synthetic_icon_style(vlayer, synthetic_points):
    """Wendet Icon-Styles auf den synthetischen Punkt-Layer an.

    Verwendet einen kategorisierten Renderer, um verschiedene Icons
    korrekt anzuzeigen (gruppiert nach Icon-Name).
    """
    from qgis.core import (
        QgsMarkerSymbol,
        QgsRasterMarkerSymbolLayer,
        QgsUnitTypes,
        QgsProperty,
        QgsSymbolLayer
    )

    if not synthetic_points:
        return

    icon_size = 25

    # Sammle eindeutige Icons mit ihren Daten
    unique_icons = {}
    for pt in synthetic_points:
        icon_name = pt.get("icon", "default")
        if icon_name in ("new", "neu"):
            continue
        icon_url = pt.get("iconUrl")
        if not icon_url:
            continue
        if icon_name not in unique_icons:
            unique_icons[icon_name] = {
                'iconUrl': icon_url,
                'iconRotate': pt.get("iconRotate", 0)
            }

    # Erstelle Kategorien für jeden Icon-Typ
    categories = []
    for icon_name, icon_data in unique_icons.items():
        # Marker Symbol erstellen
        marker_symbol = QgsMarkerSymbol()
        marker_symbol.deleteSymbolLayer(0)

        icon_layer = QgsRasterMarkerSymbolLayer(icon_data['iconUrl'])
        icon_layer.setSize(icon_size)
        icon_layer.setAngle(icon_data['iconRotate'])
        icon_layer.setSizeUnit(QgsUnitTypes.RenderPixels)
        icon_layer.setOffsetUnit(QgsUnitTypes.RenderPixels)

        # Data-defined Offset: jedes Feature liefert seinen eigenen Offset
        # (Multi-Icon-Anordnung: idx*spacing vom Server)
        # Y wird invertiert, weil der Server "math. Y" sendet (positiv = oben)
        # während QGIS "screen Y" verwendet (positiv = unten)
        icon_layer.setDataDefinedProperty(
            QgsSymbolLayer.PropertyOffset,
            QgsProperty.fromExpression(
                "concat(\"icon_offset_x\", ',', -1 * \"icon_offset_y\")"
            )
        )

        marker_symbol.appendSymbolLayer(icon_layer)

        # Kategorie erstellen
        category = QgsRendererCategory(
            icon_name,  # Wert zum Matchen (icon Feld)
            marker_symbol,
            icon_name,  # Label in Legende
            True  # render
        )
        categories.append(category)

    # Kategorisierten Renderer erstellen (gruppiert nach "icon" Feld)
    renderer = QgsCategorizedSymbolRenderer("icon", categories)
    vlayer.setRenderer(renderer)
    vlayer.triggerRepaint()


def _apply_server_style_safe(vlayer, style_dict, ids):
    geom_type = vlayer.geometryType()

    base_border = style_dict.get("baseBorder") or None
    base_bg = style_dict.get("baseBackground") or None
    basesIcon = style_dict.get("basesIcon") or None
    base_bg_pattern = style_dict.get("baseBackgroundPattern") or None
    base_text = style_dict.get("basesText") or None
    overlay_border = style_dict.get("overlayBorder") or None
    overlay_icon = style_dict.get("overlayIcon") or None
    style_layers = style_dict.get("styleLayers") or None

    # POLYGON
    if geom_type == 2:
        existing_renderer = vlayer.renderer()
        if isinstance(existing_renderer, QgsRuleBasedRenderer):
            renderer = existing_renderer
            root_rule = renderer.rootRule()
        else:
            root_rule = QgsRuleBasedRenderer.Rule(None)
            renderer = QgsRuleBasedRenderer(root_rule)
            vlayer.setRenderer(renderer)

        fill_sym = QgsFillSymbol.createSimple({})
        fill_sym.deleteSymbolLayer(0)

        if isinstance(base_border, dict):
            outline_layer = _get_border(base_border, LINE_WIDTH_FACTOR)
            fill_sym.appendSymbolLayer(outline_layer)

        if isinstance(base_bg, dict):
            outline_layer, fill_layer, pattern_layer = _get_base_background(base_bg, LINE_WIDTH_FACTOR)

            for layer in [fill_layer, pattern_layer, outline_layer]:
                if layer is not None:
                    fill_sym.appendSymbolLayer(layer)

        if isinstance(base_bg_pattern, dict):
            pattern_layer = _get_background_pattern(base_bg_pattern, LINE_WIDTH_FACTOR)
            if pattern_layer is not None:
                fill_sym.appendSymbolLayer(pattern_layer)

        if isinstance(overlay_border, dict):
            outline_layer = _get_border(overlay_border, LINE_WIDTH_FACTOR)
            fill_sym.appendSymbolLayer(outline_layer)

        # Icon direkt im Fill-Symbol hinzufügen (für Polygon-Centroid)
        if isinstance(overlay_icon, dict):
            _append_icon_layers(fill_sym, overlay_icon, geom_type=2)

        if isinstance(basesIcon, dict):
            _append_icon_layers(fill_sym, basesIcon, geom_type=2)

        # styleLayers: zusätzliche Borders + Icons (z.B. Anpflanzung mit
        # Baum-Icons entlang der Polygon-Outline)
        _append_style_layers(fill_sym, style_layers, geom_type=2)

        id_conditions = [f'"gml_id" = \'{gid}\'' for gid in ids]
        filter_exp = " OR ".join(id_conditions)
        rule = QgsRuleBasedRenderer.Rule(fill_sym, filterExp=filter_exp, label=f"Style")
        root_rule.appendChild(rule)

        vlayer.triggerRepaint()

    # LINE
    elif geom_type == 1:
        existing_renderer = vlayer.renderer()
        if isinstance(existing_renderer, QgsRuleBasedRenderer):
            renderer = existing_renderer
            root_rule = renderer.rootRule()
        else:
            root_rule = QgsRuleBasedRenderer.Rule(None)
            renderer = QgsRuleBasedRenderer(root_rule)
            vlayer.setRenderer(renderer)

        line_sym = QgsLineSymbol.createSimple({})
        line_sym.deleteSymbolLayer(0)

        if isinstance(base_border, dict):
            outline_layer = _get_border(base_border, LINE_WIDTH_FACTOR)
            line_sym.appendSymbolLayer(outline_layer)

        if isinstance(overlay_border, dict):
            outline_layer = _get_border(overlay_border, LINE_WIDTH_FACTOR)
            line_sym.appendSymbolLayer(outline_layer)

        # Icon direkt im Line-Symbol hinzufügen
        if isinstance(overlay_icon, dict):
            _append_icon_layers(line_sym, overlay_icon, geom_type=1)

        if isinstance(basesIcon, dict):
            _append_icon_layers(line_sym, basesIcon, geom_type=1)

        # styleLayers: zusätzliche Borders + Linien-Icons
        _append_style_layers(line_sym, style_layers, geom_type=1)

        id_conditions = [f'"gml_id" = \'{gid}\'' for gid in ids]
        filter_exp = " OR ".join(id_conditions)
        rule = QgsRuleBasedRenderer.Rule(line_sym, filterExp=filter_exp, label=f"Style")
        root_rule.appendChild(rule)

        vlayer.triggerRepaint()

    elif geom_type == 0:
        icon_sym = QgsMarkerSymbol()
        icon_sym.deleteSymbolLayer(0)
        if isinstance(basesIcon, dict):
            _append_icon_layers(icon_sym, basesIcon, geom_type=0)

        if isinstance(overlay_icon, dict):
            _append_icon_layers(icon_sym, overlay_icon, geom_type=0)

        # Kein renderbares Icon vorhanden (z.B. "new"/"neu" oder fehlende URL)
        # → Renderer leer lassen / nichts anhängen, sonst Default-Marker (rot)
        if icon_sym.symbolLayerCount() == 0:
            existing_renderer = vlayer.renderer()
            if not isinstance(existing_renderer, QgsRuleBasedRenderer):
                root_rule = QgsRuleBasedRenderer.Rule(None)
                vlayer.setRenderer(QgsRuleBasedRenderer(root_rule))
            return

        existing_renderer = vlayer.renderer()
        if isinstance(existing_renderer, QgsRuleBasedRenderer):
            renderer = existing_renderer
            root_rule = renderer.rootRule()
        else:
            root_rule = QgsRuleBasedRenderer.Rule(None)
            renderer = QgsRuleBasedRenderer(root_rule)
            vlayer.setRenderer(renderer)

        id_conditions = [f'"gml_id" = \'{gid}\'' for gid in ids]
        filter_exp = " OR ".join(id_conditions)
        rule = QgsRuleBasedRenderer.Rule(icon_sym, filterExp=filter_exp, label=f"Style")
        root_rule.appendChild(rule)

        vlayer.triggerRepaint()

def _apply_default_safe(vlayer):
    """Standard-Style wenn kein Server-Style vorhanden"""
    geom_type = vlayer.geometryType()
    if geom_type == 2:
        sym = QgsFillSymbol.createSimple({
            "color": "#e6e6e6",
            "outline_color": "#333333",
            "outline_width": str(LINE_WIDTH_FACTOR)
        })
    elif geom_type == 1:
        sym = QgsLineSymbol.createSimple({
            "color": "#333333",
            "width": str(LINE_WIDTH_FACTOR)
        })
    else:
        sym = QgsMarkerSymbol.createSimple({
            "name": "circle",
            "color": "#333333",
            "size": str(3.0 * LINE_WIDTH_FACTOR)
        })

    vlayer.setRenderer(QgsSingleSymbolRenderer(sym))
    vlayer.triggerRepaint()
