# -*- coding: utf-8 -*-
"""
Coordinate Table Export — Thông tư 16/2025/TT-BNNMT.

Exports coordinate tables (Mẫu 03, 04, 05) from vector layers
to Excel (.xlsx) with both projected (VN-2000 meters) and
geographic (WGS84 DMS) coordinates.

Author: Lộc Vũ Trung (LVT) / Slow Forest
License: GPL-3.0
"""

import os

from qgis.PyQt.QtWidgets import (
    QComboBox, QDialog, QFileDialog, QFormLayout, QGroupBox,
    QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton,
    QVBoxLayout,
)
from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsProject,
    QgsWkbTypes,
)


# ── Template definitions ─────────────────────────────────────────────
TEMPLATE_MAU03 = "mau03"
TEMPLATE_MAU04 = "mau04"
TEMPLATE_MAU05 = "mau05"

TEMPLATES = {
    TEMPLATE_MAU03: "Mẫu 03 — Bảng tọa độ ranh giới rừng",
    TEMPLATE_MAU04: "Mẫu 04 — Bảng tọa độ vị trí mốc",
    TEMPLATE_MAU05: "Mẫu 05 — Bảng tổng hợp mốc",
}


# ── Coordinate helpers ────────────────────────────────────────────────

def dd_to_dms(dd):
    """Convert decimal degrees to degrees°minutes'seconds\" string."""
    d = int(dd)
    m_full = (abs(dd) - abs(d)) * 60
    m = int(m_full)
    s = (m_full - m) * 60
    return f"{d}°{m:02d}'{s:05.2f}\""


def _detect_central_meridian(crs):
    """Extract the central meridian (kinh tuyến trục) from a CRS.

    Returns:
        str: e.g. "105°00'" or "unknown" if not detectable.
    """
    if not crs or not crs.isValid():
        return "unknown"

    proj4 = crs.toProj4()
    for part in proj4.split():
        if part.startswith("+lon_0="):
            try:
                lon0 = float(part.split("=")[1])
                d = int(lon0)
                m = int(round((lon0 - d) * 60))
                return f"{d}°{m:02d}'"
            except (ValueError, IndexError):
                pass  # nosec B110
    return "unknown"


def _is_vn2000(crs):
    """Check if a CRS is a VN-2000 projection."""
    if not crs or not crs.isValid():
        return False
    desc = crs.description().lower()
    proj4 = crs.toProj4().lower()
    return ("vn-2000" in desc or "vn2000" in desc
            or "vietnam" in desc  # noqa: W503
            or "+datum=wgs84" in proj4 and "+proj=tmerc" in proj4)  # noqa: W503


class CoordinateExportDialog(QDialog):
    """Dialog for exporting coordinate tables per TT16/2025."""

    def __init__(self, iface, parent=None):
        super().__init__(parent or iface.mainWindow())
        self.iface = iface
        self.setWindowTitle("Coordinate Table Export — TT16/2025")
        self.setMinimumWidth(520)
        self.setMinimumHeight(400)
        self._build_ui()
        self.refresh_layers()

    # ──────────────────────────────────────────────────────────────────
    # UI Construction
    # ──────────────────────────────────────────────────────────────────

    def _build_ui(self):
        main = QVBoxLayout(self)

        # ── Layer selection ───────────────────────────────────────────
        grp_layer = QGroupBox("Layer / Lớp dữ liệu")
        lay_layer = QFormLayout(grp_layer)
        self.cmb_layer = QComboBox()
        self.cmb_layer.currentIndexChanged.connect(self._on_layer_changed)
        lay_layer.addRow("Layer:", self.cmb_layer)

        self.lbl_crs = QLabel("—")
        lay_layer.addRow("CRS:", self.lbl_crs)

        self.lbl_meridian = QLabel("—")
        lay_layer.addRow("Kinh tuyến trục:", self.lbl_meridian)

        main.addWidget(grp_layer)

        # ── Template selection ────────────────────────────────────────
        grp_tmpl = QGroupBox("Mẫu biểu / Template")
        lay_tmpl = QFormLayout(grp_tmpl)
        self.cmb_template = QComboBox()
        for key, label in TEMPLATES.items():
            self.cmb_template.addItem(label, key)
        lay_tmpl.addRow("Template:", self.cmb_template)
        main.addWidget(grp_tmpl)

        # ── Metadata ─────────────────────────────────────────────────
        grp_meta = QGroupBox("Thông tin / Metadata")
        lay_meta = QFormLayout(grp_meta)

        self.txt_chu_rung = QLineEdit()
        self.txt_chu_rung.setPlaceholderText("Tên chủ rừng / Forest owner")
        lay_meta.addRow("Chủ rừng:", self.txt_chu_rung)

        self.txt_xa = QLineEdit()
        self.txt_xa.setPlaceholderText("Xã / Commune")
        lay_meta.addRow("Xã:", self.txt_xa)

        self.txt_tinh = QLineEdit()
        self.txt_tinh.setPlaceholderText("Tỉnh / Province")
        lay_meta.addRow("Tỉnh:", self.txt_tinh)

        main.addWidget(grp_meta)

        # ── Buttons ──────────────────────────────────────────────────
        btn_row = QHBoxLayout()
        self.btn_export = QPushButton("📤 Xuất Excel / Export")
        self.btn_export.setMinimumHeight(36)
        self.btn_export.clicked.connect(self._do_export)
        btn_row.addStretch()
        btn_row.addWidget(self.btn_export)

        btn_close = QPushButton("Đóng / Close")
        btn_close.clicked.connect(self.close)
        btn_row.addWidget(btn_close)
        main.addLayout(btn_row)

    # ──────────────────────────────────────────────────────────────────
    # Layer helpers
    # ──────────────────────────────────────────────────────────────────

    def refresh_layers(self):
        """Reload vector layers from the current project."""
        self.cmb_layer.blockSignals(True)
        self.cmb_layer.clear()

        for lyr in QgsProject.instance().mapLayers().values():
            # Accept Point, LineString, MultiPoint, MultiLineString
            if not hasattr(lyr, "wkbType"):
                continue
            wkb = lyr.wkbType()
            geom_type = QgsWkbTypes.geometryType(wkb)
            if geom_type in (QgsWkbTypes.GeometryType.PointGeometry,
                             QgsWkbTypes.GeometryType.LineGeometry):
                self.cmb_layer.addItem(lyr.name(), lyr.id())

        self.cmb_layer.blockSignals(False)
        self._on_layer_changed()

    def _current_layer(self):
        """Return the currently selected QgsVectorLayer or None."""
        layer_id = self.cmb_layer.currentData()
        if not layer_id:
            return None
        return QgsProject.instance().mapLayer(layer_id)

    def _on_layer_changed(self):
        """Update CRS info when layer selection changes."""
        lyr = self._current_layer()
        if not lyr:
            self.lbl_crs.setText("—")
            self.lbl_meridian.setText("—")
            return

        crs = lyr.crs()
        self.lbl_crs.setText(
            f"{crs.authid()} — {crs.description()}" if crs.isValid()
            else "Unknown CRS"
        )
        self.lbl_meridian.setText(_detect_central_meridian(crs))

    # ──────────────────────────────────────────────────────────────────
    # Export
    # ──────────────────────────────────────────────────────────────────

    def _do_export(self):
        """Run the export workflow."""
        lyr = self._current_layer()
        if not lyr:
            QMessageBox.warning(
                self, "LVT4U",
                "Vui lòng chọn layer.\nPlease select a layer.")
            return

        if lyr.featureCount() == 0:
            QMessageBox.warning(
                self, "LVT4U",
                "Layer không có đối tượng.\nLayer has no features.")
            return

        template_key = self.cmb_template.currentData()

        # Ask for output file
        default_name = f"ToaDo_{template_key}_{lyr.name()}.xlsx"
        path, _ = QFileDialog.getSaveFileName(
            self, "Lưu file Excel / Save Excel",
            os.path.join(os.path.expanduser("~"), "Desktop", default_name),
            "Excel Files (*.xlsx)")
        if not path:
            return

        try:
            self._export_xlsx(lyr, template_key, path)
            QMessageBox.information(
                self, "LVT4U",
                f"✅ Xuất thành công!\nExported successfully!\n\n{path}")
        except ImportError:
            QMessageBox.critical(
                self, "LVT4U",
                "Thiếu thư viện openpyxl.\n"
                "Missing openpyxl library.\n\n"
                "pip install openpyxl")
        except Exception as e:
            QMessageBox.critical(
                self, "LVT4U",
                f"Lỗi xuất file / Export error:\n{e}")

    def _export_xlsx(self, layer, template_key, path):
        """Create the Excel workbook and write coordinate data."""
        from openpyxl import Workbook
        from openpyxl.styles import (
            Alignment, Border, Font, PatternFill, Side,
        )

        wb = Workbook()
        # ── Author property ──
        wb.properties.creator = "Lộc Vũ Trung"

        ws = wb.active
        ws.title = TEMPLATES[template_key].split("—")[0].strip()

        # ── Style definitions ─────────────────────────────────────────
        font_title = Font(name="Times New Roman", size=14, bold=True)
        font_header = Font(name="Times New Roman", size=11, bold=True)
        font_normal = Font(name="Times New Roman", size=11)
        font_meta = Font(name="Times New Roman", size=11, italic=True)
        align_center = Alignment(
            horizontal="center", vertical="center", wrap_text=True)
        align_left = Alignment(
            horizontal="left", vertical="center", wrap_text=True)
        thin_border = Border(
            left=Side(style="thin"),
            right=Side(style="thin"),
            top=Side(style="thin"),
            bottom=Side(style="thin"),
        )
        fill_header = PatternFill(
            start_color="D9E1F2", end_color="D9E1F2", fill_type="solid")

        # ── CRS setup ────────────────────────────────────────────────
        src_crs = layer.crs()
        wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")

        # Transform: layer CRS → WGS84
        xform_geo = QgsCoordinateTransform(
            src_crs, wgs84, QgsProject.instance())

        # If layer is already in projected VN-2000, use its coords directly
        # Otherwise transform to WGS84 only
        is_projected = not src_crs.isGeographic()
        meridian_str = _detect_central_meridian(src_crs)

        # ── Header rows ──────────────────────────────────────────────
        row = 1
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        c = ws.cell(row=row, column=1,
                    value="CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM")
        c.font = font_header
        c.alignment = align_center

        row = 2
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        c = ws.cell(row=row, column=1,
                    value="Độc lập — Tự do — Hạnh phúc")
        c.font = font_meta
        c.alignment = align_center

        row = 4
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        if template_key == TEMPLATE_MAU03:
            title = "BẢNG TỌA ĐỘ RANH GIỚI RỪNG"
        elif template_key == TEMPLATE_MAU04:
            title = "BẢNG TỌA ĐỘ VỊ TRÍ MỐC"
        else:
            title = "BẢNG TỔNG HỢP MỐC"
        c = ws.cell(row=row, column=1, value=title)
        c.font = font_title
        c.alignment = align_center

        row = 5
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        tmpl_label = TEMPLATES[template_key].split("—")[0].strip()
        c = ws.cell(row=row, column=1,
                    value=f"(Theo {tmpl_label} — Thông tư 16/2025/TT-BNNMT)")
        c.font = font_meta
        c.alignment = align_center

        # ── Metadata rows ────────────────────────────────────────────
        row = 7
        chu_rung = self.txt_chu_rung.text().strip() or "(Chủ rừng)"
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        c = ws.cell(row=row, column=1, value=f"Chủ rừng: {chu_rung}")
        c.font = font_normal

        row = 8
        xa = self.txt_xa.text().strip() or "(Xã)"
        tinh = self.txt_tinh.text().strip() or "(Tỉnh)"
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        c = ws.cell(row=row, column=1,
                    value=f"Xã: {xa}  —  Tỉnh: {tinh}")
        c.font = font_normal

        row = 9
        crs_desc = src_crs.description() if src_crs.isValid() else "Unknown"
        ws.merge_cells(start_row=row, start_column=1, end_row=row,
                       end_column=7)
        c = ws.cell(
            row=row, column=1,
            value=f"Hệ tọa độ: {crs_desc}  |  "
                  f"Kinh tuyến trục: {meridian_str}")
        c.font = font_normal

        # ── Column headers ───────────────────────────────────────────
        row = 11

        if template_key == TEMPLATE_MAU03:
            headers = ["STT", "X (m)", "Y (m)",
                       "Kinh độ (λ)", "Vĩ độ (φ)"]
            col_widths = [8, 16, 16, 20, 20]
        elif template_key == TEMPLATE_MAU04:
            headers = ["Tên mốc", "X (m)", "Y (m)",
                       "Kinh độ (λ)", "Vĩ độ (φ)"]
            col_widths = [16, 16, 16, 20, 20]
        else:  # MAU05
            headers = ["Tên mốc", "Loại mốc", "X (m)", "Y (m)",
                       "Kinh độ (λ)", "Vĩ độ (φ)", "Ghi chú"]
            col_widths = [16, 14, 16, 16, 20, 20, 20]

        for ci, (hdr, w) in enumerate(zip(headers, col_widths), start=1):
            c = ws.cell(row=row, column=ci, value=hdr)
            c.font = font_header
            c.alignment = align_center
            c.border = thin_border
            c.fill = fill_header
            # Set column width
            col_letter = chr(64 + ci) if ci <= 26 else "A"
            ws.column_dimensions[col_letter].width = w

        # ── Data rows ────────────────────────────────────────────────
        data_start = row + 1
        feat_idx = 0

        for feat in layer.getFeatures():
            geom = feat.geometry()
            if geom.isNull() or geom.isEmpty():
                continue

            # Extract points from geometry
            points = []
            if geom.isMultipart():
                for part in geom.asMultiPolyline() if (
                    QgsWkbTypes.geometryType(geom.wkbType())
                    == QgsWkbTypes.GeometryType.LineGeometry  # noqa: W503
                ) else geom.asMultiPoint():
                    if isinstance(part, list):
                        points.extend(part)
                    else:
                        points.append(part)
            else:
                gt = QgsWkbTypes.geometryType(geom.wkbType())
                if gt == QgsWkbTypes.GeometryType.PointGeometry:
                    points.append(geom.asPoint())
                elif gt == QgsWkbTypes.GeometryType.LineGeometry:
                    points.extend(geom.asPolyline())

            for pt in points:
                feat_idx += 1
                r = data_start + feat_idx - 1

                # Projected coordinates (X, Y in meters)
                if is_projected:
                    x_m = round(pt.x(), 2)
                    y_m = round(pt.y(), 2)
                else:
                    x_m = ""
                    y_m = ""

                # Geographic coordinates (WGS84)
                pt_geo = xform_geo.transform(pt)
                lon_dms = dd_to_dms(pt_geo.x())
                lat_dms = dd_to_dms(pt_geo.y())

                if template_key == TEMPLATE_MAU03:
                    # STT uses Excel formula =ROW()-data_start_row+1
                    # for auto-numbering
                    ws.cell(row=r, column=1,
                            value=f'=ROW()-{data_start - 1}').font = (
                                font_normal)
                    ws.cell(row=r, column=2, value=x_m).font = font_normal
                    ws.cell(row=r, column=3, value=y_m).font = font_normal
                    ws.cell(row=r, column=4, value=lon_dms).font = font_normal
                    ws.cell(row=r, column=5, value=lat_dms).font = font_normal
                    ncols = 5

                elif template_key == TEMPLATE_MAU04:
                    # Try to get marker name from attributes
                    marker_name = self._get_attr(
                        feat, ["ten_moc", "name", "ten",
                               "marker", "ten_diem", "so_hieu"],
                        default=f"M{feat_idx}")
                    ws.cell(
                        row=r, column=1, value=marker_name
                    ).font = font_normal
                    ws.cell(row=r, column=2, value=x_m).font = font_normal
                    ws.cell(row=r, column=3, value=y_m).font = font_normal
                    ws.cell(row=r, column=4, value=lon_dms).font = font_normal
                    ws.cell(row=r, column=5, value=lat_dms).font = font_normal
                    ncols = 5

                else:  # MAU05
                    marker_name = self._get_attr(
                        feat, ["ten_moc", "name", "ten",
                               "marker", "ten_diem", "so_hieu"],
                        default=f"M{feat_idx}")
                    marker_type = self._get_attr(
                        feat, ["loai_moc", "type", "loai",
                               "marker_type"],
                        default="")
                    note = self._get_attr(
                        feat, ["ghi_chu", "note", "notes",
                               "description"],
                        default="")
                    ws.cell(
                        row=r, column=1, value=marker_name
                    ).font = font_normal
                    ws.cell(
                        row=r, column=2, value=marker_type
                    ).font = font_normal
                    ws.cell(row=r, column=3, value=x_m).font = font_normal
                    ws.cell(row=r, column=4, value=y_m).font = font_normal
                    ws.cell(row=r, column=5, value=lon_dms).font = font_normal
                    ws.cell(row=r, column=6, value=lat_dms).font = font_normal
                    ws.cell(row=r, column=7, value=note).font = font_normal
                    ncols = 7

                # Apply borders and alignment to data row
                for ci in range(1, ncols + 1):
                    cell = ws.cell(row=r, column=ci)
                    cell.border = thin_border
                    cell.alignment = (
                        align_center if ci <= 1 else align_center)

        # ── Summary row (total count formula) ────────────────────────
        if feat_idx > 0:
            summary_row = data_start + feat_idx
            ws.merge_cells(
                start_row=summary_row, start_column=1,
                end_row=summary_row, end_column=2)
            c = ws.cell(
                row=summary_row, column=1,
                value=f"Tổng số điểm: {feat_idx}")
            c.font = font_header
            c.alignment = align_left

        # ── Print setup ──────────────────────────────────────────────
        ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
        ws.page_setup.paperSize = ws.PAPERSIZE_A4
        ws.print_title_rows = f"1:{row}"  # Repeat header on each page

        wb.save(path)

    @staticmethod
    def _get_attr(feature, field_names, default=""):
        """Try to read an attribute by multiple possible field names.

        Args:
            feature: QgsFeature
            field_names: list of candidate field names (case-insensitive)
            default: fallback value

        Returns:
            str: attribute value or default
        """
        fields = [f.name().lower() for f in feature.fields()]
        for candidate in field_names:
            candidate_lower = candidate.lower()
            for i, fn in enumerate(fields):
                if fn == candidate_lower:
                    val = feature.attributes()[i]
                    if val is not None and str(val).strip():
                        return str(val).strip()
        return default
