# -*- coding: utf-8 -*-
"""LVT Map Layout - Engine: template loading, layout creation.

Design principle: templates are SELF-CONTAINED.  They handle ALL
positioning, grids, and frames via data-defined expressions.
This engine only:
  1. Loads the template
  2. Sets page size
  3. Sets map extent & scale
  4. Refreshes until expressions converge
  5. Updates user-provided labels
  6. Toggles element visibility per user checkboxes
"""

import os

from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
    QgsProject,
    QgsPrintLayout,
    QgsReadWriteContext,
    QgsLayoutItemMap,
    QgsLayoutItemLabel,
    QgsLayoutItemLegend,
    QgsLayoutItemPicture,
    QgsLayoutPoint,
    QgsLayoutSize,
    QgsUnitTypes,
)


# Template file mapping: (mode, lang) → filename
TEMPLATES = {
    ("slide", "EN"): "Khung_Slide_EN.qpt",
    ("slide", "VN"): "Khung_Slide.qpt",
    ("print", "EN"): "Khung LVT2601_print_27Apr2026_EN.qpt",
    ("print", "VN"): "Khung LVT2601_print_27Apr2026_VN.qpt",
}

# Element IDs that the engine may update (labels, scalebars, coords).
# These are the ONLY template items the plugin touches — everything
# else (grids, frames, positions) is left to the template.
ITEM_IDS = {
    ("slide", "EN"): {
        "map": "Map",
        "map_data": "Map data",
        "scalebar_m": "Scale meter",
    },
    ("slide", "VN"): {
        "map": "Map",
        "map_data": "Dữ liệu bản đồ",
        "scalebar_m": "Scale mét",
    },
    ("print", "EN"): {
        "map": "Map",
        "title": "Map title",
        "org_name": "Map org name",
        "study_area": "Study area",
        "map_data": "Map data",
        "references": "References",
        "page_size": "Print page size",
        "scalebar_m": "Scale meter",
        "scalebar_deg": "Scalebar deg min",
        "main_frame": "Main frame",
    },
    ("print", "VN"): {
        "map": "Map",
        "title": "Tên bản đồ",
        "org_name": "Tên đv xd bản đồ",
        "study_area": "Khu vực bản đồ",
        "map_data": "Dữ liệu bản đồ",
        "references": "Viện dẫn",
        "page_size": "Kích thước trang in",
        "scalebar_m": "Scale mét",
        "scalebar_deg": "Scalebar độ phút",
        "main_frame": "Khung tổng",
    },
}


class LvtEngine:
    """Core engine: loads .qpt templates, builds layouts."""

    def __init__(self, iface, plugin_dir):
        self.iface = iface
        self.plugin_dir = plugin_dir
        self._layout_counter = 0
        self._tpl_cache = {}  # (mode, lang) → XML string

    # ── Template Resolution ──────────────────────────────────────

    def _resolve_template(self, mode, lang):
        """Find the .qpt template file."""
        fname = TEMPLATES[(mode, lang)]
        p = os.path.join(self.plugin_dir, "templates", fname)
        if os.path.exists(p):
            return p
        p = os.path.join(os.path.dirname(self.plugin_dir), fname)
        if os.path.exists(p):
            return p
        raise FileNotFoundError(
            f"Template not found: {fname}\n"
            f"Place it in: {os.path.join(self.plugin_dir, 'templates')}"
        )

    def _load_template_doc(self, mode, lang):
        """Load and cache template XML as QDomDocument."""
        key = (mode, lang)
        if key not in self._tpl_cache:
            tpl_path = self._resolve_template(mode, lang)
            with open(tpl_path, "r", encoding="utf-8") as f:
                self._tpl_cache[key] = f.read()

        doc = QDomDocument()
        ok, err_msg, err_line, _col = doc.setContent(self._tpl_cache[key])
        if not ok:
            raise RuntimeError(
                f"Template XML error at line {err_line}: {err_msg}"
            )
        return doc

    # ── Layout Creation ──────────────────────────────────────────

    def create_layout(self, params, progress_cb=None):
        """Create a QgsPrintLayout from template + user params.

        Args:
            params: Dict of layout parameters from dialog.
            progress_cb: Optional callback(step, message) for progress updates.

        Pipeline (optimized):
          1. Load template
          2. Set page size + extent + scale (batch, no refresh yet)
          3. Multi-refresh for DD convergence (2 for slide, 3 for print)
          4. Re-enforce scale + single settle refresh
          5. Update labels + toggle visibility (no refresh needed)
        """
        def _progress(step, msg):
            if progress_cb:
                progress_cb(step, msg)

        project = QgsProject.instance()
        mode = params["mode"]   # "slide" or "print"
        lang = params["lang"]   # "EN" or "VN"
        tpl_key = (mode, lang)
        ids = ITEM_IDS[tpl_key]

        # Load cached template XML
        _progress(1, "Loading template...")
        doc = self._load_template_doc(mode, lang)

        # Create layout
        self._layout_counter += 1
        layout_name = f"LVT_{mode}_{lang}_{self._layout_counter}"
        layout = QgsPrintLayout(project)

        _progress(2, "Applying template design...")
        _items, ok2 = layout.loadFromTemplate(doc, QgsReadWriteContext())
        if not ok2:
            raise RuntimeError("Failed to load template into layout.")

        layout.setName(layout_name)
        project.layoutManager().addLayout(layout)
        layout = project.layoutManager().layoutByName(layout_name)

        # ── Step 1: Batch setup (page + extent + scale) ───────────
        _progress(3, "Setting page size & map extent...")
        pw = params["page_width"]
        ph = params["page_height"]
        page = layout.pageCollection().page(0)
        page.setPageSize(
            QgsLayoutSize(pw, ph, QgsUnitTypes.LayoutMillimeters)
        )

        map_item = self._find_item(layout, ids["map"], QgsLayoutItemMap)
        if map_item:
            extent = self._resolve_extent(params)
            map_item.zoomToExtent(extent)
            map_item.setScale(params["scale"])

        # ── Step 2: Multi-refresh for DD convergence ──────────────
        cycles = 3 if mode == "print" else 2
        for i in range(cycles):
            _progress(4 + i, f"Rendering layout (pass {i + 1}/{cycles})...")
            layout.refresh()
            QCoreApplication.processEvents()

        # ── Step 3: Re-enforce scale + settle ─────────────────────
        _progress(4 + cycles, "Finalizing scale...")
        if map_item:
            map_item.setScale(params["scale"])
            layout.refresh()
            QCoreApplication.processEvents()

        # ── Step 4: Update labels ─────────────────────────────────
        _progress(5 + cycles, "Writing labels...")
        self._update_labels(layout, ids, params)

        # ── Step 5: Toggle element visibility ─────────────────────
        _progress(6 + cycles, "Configuring elements...")
        if not params["show_scalebar"]:
            self._hide_item(layout, ids["scalebar_m"])
            if "scalebar_deg" in ids:
                self._hide_item(layout, ids["scalebar_deg"])

        if not params["show_legend"]:
            for item in layout.items():
                if isinstance(item, QgsLayoutItemLegend):
                    item.setVisibility(False)

        if not params["show_north"]:
            for item in layout.items():
                if isinstance(item, QgsLayoutItemPicture):
                    item.setVisibility(False)
        else:
            # Apply selected north arrow style
            svg_path = params.get("north_arrow_svg", "")
            if svg_path and os.path.isfile(svg_path):
                for item in layout.items():
                    if isinstance(item, QgsLayoutItemPicture):
                        item.setPicturePath(svg_path)

        # ── Step 6: Signature block ────────────────────────────────
        if params.get("signature_block"):
            _progress(7 + cycles, "Adding signature block...")
            self._add_signature_block(layout, params)

        # ── Final: re-enforce scale (lightweight, no full refresh) ──
        if map_item:
            map_item.setScale(params["scale"])

        _progress(8 + cycles, "Done!")
        return layout

    # ── Label Updates ─────────────────────────────────────────────

    def _update_labels(self, layout, ids, params):
        """Write user-provided text into template label items."""
        # Title
        if "title" in ids and params.get("title"):
            self._set_label(layout, ids["title"], params["title"])

        # Organization name
        if "org_name" in ids and params.get("org_name"):
            self._set_label(layout, ids["org_name"], params["org_name"])

        # Study area
        if "study_area" in ids and params.get("study_area"):
            self._set_label(layout, ids["study_area"], params["study_area"])

        # Data sources
        if "map_data" in ids and params.get("data_sources"):
            self._set_label(layout, ids["map_data"], params["data_sources"])

        # References / Viện dẫn: ALWAYS restore fixed title text
        if "references" in ids:
            fixed_ref = ("Dữ liệu xây dựng bản đồ:"
                         if params.get("lang") == "VN"
                         else "Map data references:")
            self._set_label(layout, ids["references"], fixed_ref)

    # ── Extent Resolution ────────────────────────────────────────

    def _resolve_extent(self, params):
        """Get the map extent based on user selection."""
        mode = params["extent_mode"]
        if mode == "drawn" and params.get("drawn_extent"):
            return params["drawn_extent"]
        elif mode == "layer" and params.get("extent_layer"):
            return params["extent_layer"].extent()
        else:
            return self.iface.mapCanvas().extent()

    # ── Helpers ───────────────────────────────────────────────────

    @staticmethod
    def _find_item(layout, item_id, item_type=None):
        item = layout.itemById(item_id)
        if item and item_type and not isinstance(item, item_type):
            return None
        return item

    @staticmethod
    def _set_label(layout, item_id, text):
        item = layout.itemById(item_id)
        if item and isinstance(item, QgsLayoutItemLabel):
            item.setText(text)

    @staticmethod
    def _hide_item(layout, item_id):
        item = layout.itemById(item_id)
        if item:
            item.setVisibility(False)

    def _add_signature_block(self, layout, params):
        """Add N signature blocks at bottom-left of layout.

        Each block uses 3 items:
          - QgsLayoutItemShape (rectangle) for border
          - QgsLayoutItemLabel for header (title + date + separator)
          - QgsLayoutItemLabel for name (native VAlign bottom)
        """
        from qgis.PyQt.QtGui import QFont
        from qgis.PyQt.QtCore import Qt
        from qgis.core import QgsLayoutItemShape
        from qgis.core import QgsFillSymbol

        # Remove any existing signature items (prevent duplicates)
        to_remove = []
        for item in layout.items():
            item_id = getattr(item, 'id', lambda: '')()
            if item_id and item_id.startswith("Sig "):
                to_remove.append(item)
        for item in to_remove:
            layout.removeLayoutItem(item)

        sig_h = params.get("sig_height", 50)
        sig_w = params.get("sig_width", 50)
        blocks = params.get("sig_blocks", [])

        # Font tuples: (family, size, bold, italic)
        ft = params.get("sig_font_title",
                        ("Times New Roman", 12, True, False))
        fd = params.get("sig_font_date",
                        ("Times New Roman", 10, False, True))
        fn = params.get("sig_font_name",
                        ("Times New Roman", 11, True, False))

        # Position: read map frame (= blue frame) geometry directly
        # This is always accurate regardless of paper/orientation
        map_item = None
        for item in layout.items():
            if isinstance(item, QgsLayoutItemMap):
                map_item = item
                break

        if not map_item:
            return  # No map frame, can't position

        map_x = map_item.positionWithUnits().x()
        map_y = map_item.positionWithUnits().y()
        map_h = map_item.sizeWithUnits().height()

        # Block offset from map frame (user-configurable, default 3mm)
        ml = params.get("sig_margin_left", 3)
        mb = params.get("sig_margin_bottom", 3)
        base_x = map_x + ml
        frame_bottom = map_y + map_h
        base_y = frame_bottom - mb - sig_h

        # Header label height (title + date + separator)
        hdr_h = 16  # mm
        # Name label height
        name_h = 8  # mm

        def _css(fam, sz, bold, italic):
            """Build CSS style string from font tuple."""
            w = "bold" if bold else "normal"
            s = "italic" if italic else "normal"
            return (f"font-family:'{fam}';font-size:{sz}pt;"
                    f"font-weight:{w};font-style:{s};")

        def _make_font(fam, sz, bold, italic):
            """Create QFont from tuple."""
            f = QFont(fam, sz)
            f.setBold(bold)
            f.setItalic(italic)
            return f

        for i, blk in enumerate(blocks):
            x = base_x + i * sig_w
            title = blk.get("title", "")
            sig_date = blk.get("date", "")
            name = blk.get("name", "")

            # ── 1. Rectangle (border frame) ──
            rect = QgsLayoutItemShape(layout)
            rect.setShapeType(QgsLayoutItemShape.Rectangle)
            rect.attemptMove(
                QgsLayoutPoint(x, base_y,
                               QgsUnitTypes.LayoutMillimeters))
            rect.attemptResize(
                QgsLayoutSize(sig_w, sig_h,
                              QgsUnitTypes.LayoutMillimeters))
            # White fill + 0.2mm black border
            sym = QgsFillSymbol.createSimple({
                'color': '255,255,255,255',
                'outline_color': '0,0,0,255',
                'outline_width': '0.2',
                'outline_width_unit': 'MM',
            })
            rect.setSymbol(sym)
            rect.setBackgroundEnabled(False)
            rect.setFrameEnabled(False)
            rect.setLocked(True)
            rect.setId(f"Sig Frame {i + 1}")
            layout.addLayoutItem(rect)
            layout.raiseItem(rect)

            # ── 2. Header label (title + date + hr) at top ──
            hdr_html = (
                '<div style="text-align:center;">'
                f'<div style="text-align:center;{_css(*ft)}">'
                f'{title}</div>'
                f'<div style="text-align:center;{_css(*fd)}">'
                f'{sig_date}</div>'
                '<hr style="border:none;border-top:0.2mm solid #000;'
                'width:90%;margin:2mm auto 0 auto;"/>'
                '</div>'
            )
            hdr = QgsLayoutItemLabel(layout)
            hdr.setMode(QgsLayoutItemLabel.ModeHtml)
            hdr.setText(hdr_html)
            hdr.attemptMove(
                QgsLayoutPoint(x, base_y + 3,
                               QgsUnitTypes.LayoutMillimeters))
            hdr.attemptResize(
                QgsLayoutSize(sig_w, hdr_h,
                              QgsUnitTypes.LayoutMillimeters))
            hdr.setBackgroundEnabled(False)
            hdr.setFrameEnabled(False)
            hdr.setLocked(True)
            hdr.setId(f"Sig Header {i + 1}")
            layout.addLayoutItem(hdr)
            layout.raiseItem(hdr)

            # ── 3. Name label at bottom (native VAlign) ──
            name_lbl = QgsLayoutItemLabel(layout)
            name_lbl.setMode(QgsLayoutItemLabel.ModeFont)
            name_lbl.setText(name)
            name_lbl.setFont(_make_font(*fn))
            name_lbl.setHAlign(Qt.AlignHCenter)
            name_lbl.setVAlign(Qt.AlignBottom)
            name_y = base_y + sig_h - name_h - 3  # 3mm from bottom
            name_lbl.attemptMove(
                QgsLayoutPoint(x, name_y,
                               QgsUnitTypes.LayoutMillimeters))
            name_lbl.attemptResize(
                QgsLayoutSize(sig_w, name_h,
                              QgsUnitTypes.LayoutMillimeters))
            name_lbl.setBackgroundEnabled(False)
            name_lbl.setFrameEnabled(False)
            name_lbl.setLocked(True)
            name_lbl.setId(f"Sig Name {i + 1}")
            layout.addLayoutItem(name_lbl)
            layout.raiseItem(name_lbl)
