"""Legend block rendering for Map Layout.

Uses QGIS native QgsLayoutItemLegend for proper legend display.
Users can edit legend items directly in Layout Designer afterward.

Symbol overrides use QgsMapLayerLegendUtils to change legend display
without affecting the layer renderer on the map canvas.
"""
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QFont, QColor
from qgis.core import (
    QgsLayoutItemLegend,
    QgsLayoutItemMap,
    QgsLayoutMeasurement,
    QgsLayoutPoint,
    QgsLayoutSize,
    QgsUnitTypes,
    QgsLegendStyle,
    QgsProject,
    QgsStyle,
    QgsMapLayerLegendUtils,
)

# ── Predefined geographic symbol groups ──────────────────────────
# Maps directly to TCVN 11565-2016 symbols in QGIS Style Manager.
# Each entry: (display_label, tcvn_symbol_name, geometry_type)

GEO_SYMBOLS = {
    "admin": {
        "label": "Ranh giới hành chính",
        "items": [
            ("Ranh giới Quốc gia", "Ranh giới Quốc gia", "line"),
            ("Ranh giới tỉnh", "Ranh giới tỉnh, TP trực thuộc TW", "line"),
            ("Ranh giới huyện", "Ranh giới huyện, Thị xã, TP trực thuộc tỉnh", "line"),
            ("Ranh giới xã", "Ranh giới xã, phường, thị trấn", "line"),
        ],
    },
    "forestry": {
        "label": "Ranh giới lâm nghiệp",
        "items": [
            ("Ranh giới tiểu khu", "Ranh giới tiểu khu", "line"),
            ("Ranh giới khoảnh", "Ranh giới khoảnh", "line"),
            ("Ranh giới thửa đất", "Ranh giới thửa đất", "line"),
            ("Ranh giới phân trường", "Ranh giới phân trường", "line"),
            ("Ranh giới lô trạng thái", "Ranh giới lô trạng thái rừng, đất", "line"),
            ("Ranh giới lâm trường", "Ranh giới lâm trường hoặc Cty lâm nghiệp", "line"),
        ],
    },
    "forest_boundary": {
        "label": "Ranh giới 3 loại rừng",
        "items": [
            ("Rừng phòng hộ", "Ranh giới rừng phòng hộ", "line"),
            ("Rừng đặc dụng", "Ranh giới rừng đặc dụng", "line"),
            ("Rừng sản xuất", "Ranh giới rừng sản xuất", "line"),
            ("Khu bảo tồn", "Ranh giới khu bảo tồn, đặc dụng", "line"),
            ("Vườn quốc gia", "Ranh giới vườn quốc gia", "line"),
        ],
    },
    "transport": {
        "label": "Giao thông",
        "items": [
            ("Đường quốc lộ", "Đường quốc lộ", "line"),
            ("Đường tỉnh lộ", "Đường tỉnh lộ", "line"),
            ("Đường huyện lộ", "Đường huyện lộ", "line"),
            ("Đường liên xã", "Đường liên xã", "line"),
            ("Đường mòn", "Đường mòn", "line"),
            ("Đường sắt", "Đường sắt", "line"),
        ],
    },
    "hydro": {
        "label": "Thủy văn",
        "items": [
            ("Bình đồ cái", "Bình đồ cái", "line"),
            ("Bình đồ con", "Bình đồ con", "line"),
            ("Sông, suối", "Sông, suối có nước theo mùa", "line"),
        ],
    },
    "poi": {
        "label": "Điểm dân cư / Hạ tầng",
        "items": [
            ("UBND Tỉnh", "UBND Tỉnh", "marker"),
            ("UBND Huyện", "UBND huyện", "marker"),
            ("UBND Xã", "UBND Xã", "marker"),
            ("Bệnh viện", "Bệnh viện", "marker"),
            ("Trường học", "TrHoc", "marker"),
            ("Đền, chùa", "Đền, chùa", "marker"),
            ("Cầu cống", "Cầu cống", "marker"),
        ],
    },
    "forest_mgmt": {
        "label": "Quản lý bảo vệ rừng",
        "items": [
            ("Trạm bảo vệ rừng", "Trạm bảo vệ rừng", "marker"),
            ("Chòi canh lửa", "Chòi canh lửa", "marker"),
            ("Trạm cứu hỏa", "Trạm cứu hỏa", "marker"),
            ("Vườn ươm", "Vườn ươm", "marker"),
        ],
    },
    "numbering": {
        "label": "Số hiệu",
        "items": [
            ("Số hiệu tiểu khu", "Số hiệu tiểu khu", "marker"),
            ("Số hiệu khoảnh", "Số hiệu khoảnh", "marker"),
            ("Số hiệu thửa đất", "Số hiệu thửa đất", "marker"),
        ],
    },
}


def add_legend_block(layout, params, plugin_dir):
    """Add a native QGIS legend to layout.

    Uses QgsLayoutItemLegend — users can fully edit this legend
    in Layout Designer after creation.

    Args:
        layout: QgsPrintLayout
        params: dict from dialog with legend settings
        plugin_dir: plugin root directory (kept for compatibility)
    """
    legend_cfg = params.get("legend", {})
    if not legend_cfg.get("enabled"):
        return

    # ── Clean up old legend items ──
    to_remove = []
    for item in layout.items():
        if isinstance(item, QgsLayoutItemLegend):
            item_id = item.id()
            if item_id and item_id.startswith("Legend_"):
                to_remove.append(item)
    for item in to_remove:
        layout.removeLayoutItem(item)

    # ── Find map frame ──
    map_item = None
    for item in layout.items():
        if isinstance(item, QgsLayoutItemMap):
            map_item = item
            break
    if not map_item:
        return

    # ── Extract settings ──
    font_family = legend_cfg.get("font", "Times New Roman")
    font_size = legend_cfg.get("font_size", 8)
    title_text = legend_cfg.get("title", "CHÚ DẪN")
    title_size = legend_cfg.get("title_size", 14)
    line_spacing = legend_cfg.get("line_spacing", 1.4)
    sym_w = legend_cfg.get("symbol_width", 23)
    sym_h = legend_cfg.get("symbol_height", 8)
    col_count = legend_cfg.get("columns", 2)
    split_layer = legend_cfg.get("split_layer", True)
    filter_by_map = legend_cfg.get("filter_by_map", True)
    header_bold = legend_cfg.get("header_bold", True)
    layer_ids = legend_cfg.get("layer_ids", [])
    symbol_overrides = legend_cfg.get("symbol_overrides", {})
    layer_renames = legend_cfg.get("layer_renames", {})
    hide_groups = legend_cfg.get("hide_groups", False)

    # ── Create native QGIS Legend ──
    leg = QgsLayoutItemLegend(layout)
    leg.setId("Legend_Native")

    # Link to map
    leg.setLinkedMap(map_item)
    leg.setLegendFilterByMapEnabled(filter_by_map)

    # Title
    leg.setTitle(title_text)
    leg.setTitleAlignment(Qt.AlignHCenter)

    # Column settings
    leg.setColumnCount(col_count)
    leg.setColumnSpace(5)
    leg.setSplitLayer(split_layer)
    leg.setEqualColumnWidth(False)

    # Symbol dimensions
    leg.setSymbolWidth(sym_w)
    leg.setSymbolHeight(sym_h)

    # Spacing
    leg.setBoxSpace(3)
    leg.setLineSpacing(line_spacing)

    # ── Fonts ──
    _apply_fonts(leg, font_family, font_size, title_size, header_bold)

    # Frame and background
    leg.setFrameEnabled(True)
    leg.setFrameStrokeColor(QColor(0, 0, 0))
    leg.setFrameStrokeWidth(QgsLayoutMeasurement(
        0.2, QgsUnitTypes.LayoutMillimeters))
    leg.setBackgroundEnabled(True)
    leg.setBackgroundColor(QColor(255, 255, 255, 240))

    # ── Populate legend model ──
    # First, auto-update to populate from project layer tree
    leg.setAutoUpdateModel(True)

    # Add to layout (must happen before model manipulation)
    layout.addLayoutItem(leg)
    leg.updateLegend()

    # CRITICAL: Disconnect legend model from project layer tree
    # BEFORE any filtering/modification.  This ensures that
    # removing nodes from the legend does NOT affect the map.
    leg.setAutoUpdateModel(False)

    # ── Filter layers ──
    if layer_ids:
        _filter_legend_layers(leg, layer_ids)

    # ── Rename layers in legend ──
    if layer_renames:
        _rename_legend_layers(leg, layer_renames)

    # ── Hide group headers ──
    if hide_groups:
        _hide_group_headers(leg)

    # ── Apply TCVN symbol overrides (legend-only) ──
    if symbol_overrides:
        _apply_symbol_overrides(leg, symbol_overrides)

    # ── Position ──
    _position_legend(leg, map_item, legend_cfg, params)


# ── Helper Functions ─────────────────────────────────────────────


def _apply_fonts(leg, font_family, font_size, title_size, header_bold):
    """Apply font styling to all legend style categories."""
    title_font = QFont(font_family, title_size)
    title_font.setBold(True)

    group_font = QFont(font_family, font_size + 1)
    group_font.setBold(header_bold)

    subgroup_font = QFont(font_family, font_size)
    subgroup_font.setBold(False)

    item_font = QFont(font_family, font_size)

    title_style = leg.style(QgsLegendStyle.Title)
    title_style.setFont(title_font)
    leg.setStyle(QgsLegendStyle.Title, title_style)

    group_style = leg.style(QgsLegendStyle.Group)
    group_style.setFont(group_font)
    leg.setStyle(QgsLegendStyle.Group, group_style)

    subgroup_style = leg.style(QgsLegendStyle.Subgroup)
    subgroup_style.setFont(subgroup_font)
    leg.setStyle(QgsLegendStyle.Subgroup, subgroup_style)

    symbol_style = leg.style(QgsLegendStyle.SymbolLabel)
    symbol_style.setFont(item_font)
    leg.setStyle(QgsLegendStyle.SymbolLabel, symbol_style)


def _filter_legend_layers(leg, layer_ids):
    """Remove layers not in the selected list from legend model.

    Works recursively through all group levels.
    """
    model = leg.model()
    root = model.rootGroup()

    def _filter_recursive(group):
        to_rm = []
        for child in group.children():
            if hasattr(child, 'layerId'):
                # Layer node: remove if not in selected list
                if child.layerId() not in layer_ids:
                    to_rm.append(child)
            elif hasattr(child, 'children'):
                # Group node: recurse into it
                _filter_recursive(child)
        for child in to_rm:
            group.removeChildNode(child)

    _filter_recursive(root)


def _rename_legend_layers(leg, layer_renames):
    """Rename layer labels in both legend AND the actual project map.

    Works recursively through all group levels.
    Layer position in the layer tree is preserved.

    Args:
        leg: QgsLayoutItemLegend
        layer_renames: dict {layer_id: new_display_name}
    """
    model = leg.model()
    root = model.rootGroup()

    def _rename_recursive(group):
        for child in group.children():
            if hasattr(child, 'layerId'):
                lid = child.layerId()
                if lid in layer_renames and layer_renames[lid]:
                    new_name = layer_renames[lid]
                    # Rename in legend model
                    child.setName(new_name)
                    # Rename actual map layer (keeps position)
                    layer = QgsProject.instance().mapLayer(lid)
                    if layer:
                        layer.setName(new_name)
            elif hasattr(child, 'children'):
                _rename_recursive(child)

    _rename_recursive(root)


def _hide_group_headers(leg):
    """Hide group and subgroup header labels from legend.

    Flattens the legend tree by moving all layer nodes to root
    and removing empty group containers.
    """
    from qgis.core import QgsLayerTreeGroup, QgsLayerTreeLayer
    model = leg.model()
    root = model.rootGroup()

    # Collect all layer nodes from all groups (recursively)
    def _collect_layers(group):
        layers = []
        for child in group.children():
            if isinstance(child, QgsLayerTreeLayer):
                layers.append(child)
            elif isinstance(child, QgsLayerTreeGroup):
                layers.extend(_collect_layers(child))
        return layers

    all_layers = _collect_layers(root)

    # Clone layer nodes and add to root
    for layer_node in all_layers:
        if layer_node.parent() != root:
            cloned = layer_node.clone()
            root.addChildNode(cloned)

    # Remove all group nodes from root
    groups_to_remove = [
        child for child in root.children()
        if isinstance(child, QgsLayerTreeGroup)
    ]
    for grp in groups_to_remove:
        root.removeChildNode(grp)


def _apply_symbol_overrides(leg, symbol_overrides):
    """Apply TCVN symbol overrides to legend items (legend-only).

    Uses QgsMapLayerLegendUtils.setLegendNodeCustomSymbol to change
    only the legend display without affecting the layer renderer.

    Args:
        leg: QgsLayoutItemLegend
        symbol_overrides: dict {layer_id: symbol_name_in_style_manager}
    """
    style = QgsStyle.defaultStyle()
    model = leg.model()
    root = model.rootGroup()

    for child in root.children():
        if not hasattr(child, 'layerId'):
            continue
        lid = child.layerId()
        if lid not in symbol_overrides:
            continue

        sym_name = symbol_overrides[lid]
        sym = style.symbol(sym_name)
        if not sym:
            continue

        try:
            QgsMapLayerLegendUtils.setLegendNodeCustomSymbol(
                child, 0, sym.clone())
            model.refreshLayerLegend(child)
        except Exception:
            pass  # Skip if layer has incompatible symbol type


def _position_legend(leg, map_item, legend_cfg, params):
    """Position legend inside map frame.

    Rules:
      - Left edge: margin_left mm from blue frame left (default 3)
      - Bottom edge: margin_bottom mm above signature top (if sig)
                     margin_bottom mm above blue frame bottom (if no sig)
    """
    map_x = map_item.positionWithUnits().x()
    map_y = map_item.positionWithUnits().y()
    map_h = map_item.sizeWithUnits().height()
    frame_bottom = map_y + map_h
    max_width = legend_cfg.get("max_width", 120)

    # ── Let QGIS calculate actual content size ──
    leg.setResizeToContents(True)
    leg.updateLegend()

    from qgis.PyQt.QtCore import QCoreApplication
    leg.layout().refresh()
    QCoreApplication.processEvents()
    leg.adjustSizeToContents()
    QCoreApplication.processEvents()

    leg_h = leg.sizeWithUnits().height()
    leg_w = leg.sizeWithUnits().width()

    # Fallback if size is still 0
    if leg_h < 5:
        leg_h = 80
    if leg_w < 5:
        leg_w = max_width

    # Cap width
    if leg_w > max_width:
        leg_w = max_width

    # ── Margins (configurable from dialog) ──
    MARGIN_LEFT = legend_cfg.get("margin_left", 3)      # mm from blue frame left
    MARGIN_BOTTOM = legend_cfg.get("margin_bottom", 6)   # mm above reference edge

    # ── Calculate legend_bottom (= Y of legend's bottom edge) ──
    has_sig = params.get("signature_block", False)
    if has_sig:
        # Signature top = frame_bottom - sig_margin_bottom - sig_height
        # (same formula as engine._add_signature_block)
        sig_mb = params.get("sig_margin_bottom", 3)
        sig_h = params.get("sig_height", 50)
        sig_top = frame_bottom - sig_mb - sig_h
        legend_bottom = sig_top - MARGIN_BOTTOM
    else:
        legend_bottom = frame_bottom - MARGIN_BOTTOM

    # Available height (from map top + 5mm to legend_bottom)
    available_h = legend_bottom - map_y - 5
    if leg_h > available_h and available_h > 20:
        leg_h = available_h

    # ── Final coordinates ──
    legend_x = map_x + MARGIN_LEFT
    legend_y = legend_bottom - leg_h  # top-left Y

    # Safety: keep inside map frame
    if legend_y < map_y + 3:
        legend_y = map_y + 3

    # ── Apply ──
    leg.setResizeToContents(False)
    leg.attemptResize(
        QgsLayoutSize(leg_w, leg_h,
                      QgsUnitTypes.LayoutMillimeters))
    leg.attemptMove(
        QgsLayoutPoint(legend_x, legend_y,
                       QgsUnitTypes.LayoutMillimeters))

    # Raise above map
    leg.layout().raiseItem(leg)
