# -*- coding: utf-8 -*-
"""
LVT4U — Coordinate Converter Dialog.

Quick field coordinate conversion:
  - DMS (Degrees Minutes Seconds) → WGS 84 decimal
  - Metric (VN-2000 per province)  → WGS 84 decimal
  - Zoom to point on map canvas

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

import re

from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QGridLayout,
    QLabel, QLineEdit, QPushButton, QComboBox,
    QGroupBox, QApplication, QMessageBox,
)
from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsProject,
    QgsPointXY,
    QgsVectorLayer,
    QgsFeature,
    QgsGeometry,
    QgsField,
)
from ..shared.qt_compat import QVariant
from qgis.gui import QgsVertexMarker

from ..shared.i18n import tr
from .._crs_list import CRS_LIST


def _make_crs(code):
    """Create QgsCoordinateReferenceSystem from EPSG code or proj4 string."""
    if code.startswith('EPSG:'):
        return QgsCoordinateReferenceSystem(code)
    crs = QgsCoordinateReferenceSystem()
    crs.createFromProj(code)
    return crs


class CoordConverterDialog(QDialog):
    """Compact coordinate converter for field work."""

    def __init__(self, iface, parent=None):
        super().__init__(parent or iface.mainWindow())
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self._marker = None
        self._mem_layer = None  # memory layer for collected points
        self.setWindowTitle(f"LVT4U — {tr('Coordinate Converter')}")
        self.setMinimumWidth(520)
        self._build_ui()

    # ------------------------------------------------------------------
    # UI
    # ------------------------------------------------------------------

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

        # ── Input: DMS ──
        grp_dms = QGroupBox(tr("DMS Input (Degrees Minutes Seconds)"))
        g1 = QGridLayout(grp_dms)

        g1.addWidget(QLabel(tr("Latitude:")), 0, 0)
        self.txt_lat_dms = QLineEdit()
        self.txt_lat_dms.setPlaceholderText("21°14'05\"  or  21 14 05")
        g1.addWidget(self.txt_lat_dms, 0, 1)

        g1.addWidget(QLabel(tr("Longitude:")), 1, 0)
        self.txt_lon_dms = QLineEdit()
        self.txt_lon_dms.setPlaceholderText("103°22'20\"  or  103 22 20")
        g1.addWidget(self.txt_lon_dms, 1, 1)

        btn_dms = QPushButton(f"🔄 {tr('Convert DMS → Decimal')}")
        btn_dms.clicked.connect(self._convert_dms)
        g1.addWidget(btn_dms, 2, 0, 1, 2)
        root.addWidget(grp_dms)

        # ── Input: Metric (VN-2000) ──
        grp_met = QGroupBox(tr("Metric Input (VN-2000 / UTM)"))
        g2 = QGridLayout(grp_met)

        g2.addWidget(QLabel(tr("Province CRS:")), 0, 0)
        self.cmb_crs = QComboBox()
        for label, code in CRS_LIST:
            if code:
                self.cmb_crs.addItem(label, code)
            else:
                self.cmb_crs.addItem(f"── {label} ──", "")
        self.cmb_crs.setCurrentIndex(0)
        g2.addWidget(self.cmb_crs, 0, 1)

        g2.addWidget(QLabel("X (Easting):"), 1, 0)
        self.txt_x = QLineEdit()
        self.txt_x.setPlaceholderText("500000.000")
        g2.addWidget(self.txt_x, 1, 1)

        g2.addWidget(QLabel("Y (Northing):"), 2, 0)
        self.txt_y = QLineEdit()
        self.txt_y.setPlaceholderText("2350000.000")
        g2.addWidget(self.txt_y, 2, 1)

        btn_met = QPushButton(f"🔄 {tr('Convert Metric → WGS 84')}")
        btn_met.clicked.connect(self._convert_metric)
        g2.addWidget(btn_met, 3, 0, 1, 2)
        root.addWidget(grp_met)

        # ── Output: WGS 84 ──
        grp_out = QGroupBox(tr("Result — WGS 84 (EPSG:4326)"))
        g3 = QGridLayout(grp_out)

        g3.addWidget(QLabel(tr("Latitude (DD):")), 0, 0)
        self.txt_lat_dd = QLineEdit()
        self.txt_lat_dd.setReadOnly(True)
        g3.addWidget(self.txt_lat_dd, 0, 1)

        g3.addWidget(QLabel(tr("Longitude (DD):")), 1, 0)
        self.txt_lon_dd = QLineEdit()
        self.txt_lon_dd.setReadOnly(True)
        g3.addWidget(self.txt_lon_dd, 1, 1)

        g3.addWidget(QLabel(tr("DMS:")), 2, 0)
        self.txt_result_dms = QLineEdit()
        self.txt_result_dms.setReadOnly(True)
        g3.addWidget(self.txt_result_dms, 2, 1)

        root.addWidget(grp_out)

        # ── Output: VN-2000 ──
        grp_vn = QGroupBox(tr("Result — VN-2000"))
        g4 = QGridLayout(grp_vn)

        g4.addWidget(QLabel(tr("Output CRS:")), 0, 0)
        self.cmb_out_crs = QComboBox()
        # Only VN-2000 entries (skip WGS84 section)
        self.cmb_out_crs.addItem(
            f"— {tr('Select VN-2000 meridian')} —", "")
        for label, code in CRS_LIST:
            if not code:
                # Add separator headers
                if "VN-2000" in label:
                    self.cmb_out_crs.addItem(f"── {label} ──", "")
                    idx = self.cmb_out_crs.count() - 1
                    self.cmb_out_crs.model().item(idx).setEnabled(False)
            elif code and ":" in code:
                try:
                    epsg_num = int(code.split(":")[1])
                    if code in ("EPSG:3405", "EPSG:3406") or epsg_num >= 5896:
                        self.cmb_out_crs.addItem(label, code)
                except (ValueError, IndexError):
                    pass  # nosec B110
        self.cmb_out_crs.currentIndexChanged.connect(self._update_vn2000)
        g4.addWidget(self.cmb_out_crs, 0, 1)

        g4.addWidget(QLabel("X (Easting):"), 1, 0)
        self.txt_out_x = QLineEdit()
        self.txt_out_x.setReadOnly(True)
        g4.addWidget(self.txt_out_x, 1, 1)

        g4.addWidget(QLabel("Y (Northing):"), 2, 0)
        self.txt_out_y = QLineEdit()
        self.txt_out_y.setReadOnly(True)
        g4.addWidget(self.txt_out_y, 2, 1)

        root.addWidget(grp_vn)

        # ── Point label ──
        lbl_row = QHBoxLayout()
        lbl_row.addWidget(QLabel(tr("Point name:")))
        self.txt_point_name = QLineEdit()
        self.txt_point_name.setPlaceholderText(tr("e.g. Field point 1"))
        lbl_row.addWidget(self.txt_point_name)
        root.addLayout(lbl_row)

        # ── Action buttons ──
        btn_row = QHBoxLayout()

        btn_copy = QPushButton(f"📋 {tr('Copy')}")
        btn_copy.clicked.connect(self._copy_result)
        btn_row.addWidget(btn_copy)

        btn_zoom = QPushButton(f"🔍 {tr('Zoom to Point')}")
        btn_zoom.clicked.connect(self._zoom_to_point)
        btn_row.addWidget(btn_zoom)

        btn_mark = QPushButton(f"📍 {tr('Add Marker')}")
        btn_mark.clicked.connect(self._add_marker)
        btn_row.addWidget(btn_mark)

        btn_clear = QPushButton(f"🗑 {tr('Clear')}")
        btn_clear.clicked.connect(self._clear_all)
        btn_row.addWidget(btn_clear)

        btn_layer = QPushButton(f"📌 {tr('Add to Layer')}")
        btn_layer.setToolTip(tr("Add point to memory layer for export"))
        btn_layer.clicked.connect(self._add_to_layer)
        btn_row.addWidget(btn_layer)

        btn_help = QPushButton(f"❓ {tr('Help')}")
        btn_help.setStyleSheet(
            "QPushButton{background:#ff8f00;color:#fff;font-weight:bold;"
            "padding:4px 14px;border-radius:4px}"
            "QPushButton:hover{background:#ffa000}"
        )
        btn_help.clicked.connect(self._show_help)
        btn_row.addWidget(btn_help)

        btn_close = QPushButton(tr("Close"))
        btn_close.clicked.connect(self.close)
        btn_row.addWidget(btn_close)

        root.addLayout(btn_row)

    # ------------------------------------------------------------------
    # DMS Parsing
    # ------------------------------------------------------------------

    @staticmethod
    def _parse_dms(text):
        """Parse a DMS string into decimal degrees.

        Supports formats:
            21°14'05"    21°14'05''    21 14 05
            21d14m05s    21-14-05      N21°14'05"
        """
        text = text.strip().upper()

        # Extract hemisphere
        hemisphere = 1
        if text and text[0] in "NSEW":
            if text[0] in "SW":
                hemisphere = -1
            text = text[1:].strip()
        elif text and text[-1] in "NSEW":
            if text[-1] in "SW":
                hemisphere = -1
            text = text[:-1].strip()

        # Replace comma with dot if it looks like a decimal separator (e.g. 21,12345)
        # but keep it if it looks like a coordinate separator (e.g. 103 22 20, 21 14 05)
        if "," in text and "." not in text:
            # If only one comma exists and it's not followed by space, treat as decimal
            if text.count(",") == 1 and not re.search(r",\s", text):
                text = text.replace(",", ".")

        # Split into parts using various delimiters
        parts = re.split(r"[°º\'\"\''\"dDmMsS\s;:\-]+", text)
        # Note: removed comma from split delimiters to handle decimal comma better
        # If we need comma split, we do it separately if parts is still 1
        if len(parts) == 1 and "," in parts[0]:
            parts = parts[0].split(",")

        parts = [p.strip() for p in parts if p.strip()]

        if len(parts) == 1:
            return float(parts[0]) * hemisphere
        elif len(parts) == 2:
            d = float(parts[0])
            m = float(parts[1])
            return hemisphere * (d + m / 60.0)
        elif len(parts) >= 3:
            d = float(parts[0])
            m = float(parts[1])
            s = float(parts[2])
            return hemisphere * (d + m / 60.0 + s / 3600.0)
        else:
            raise ValueError(f"Cannot parse: {text}")

    @staticmethod
    def _dd_to_dms(dd, is_lat=True):
        """Convert decimal degrees to DMS string."""
        direction = ""
        if is_lat:
            direction = "N" if dd >= 0 else "S"
        else:
            direction = "E" if dd >= 0 else "W"

        dd = abs(dd)
        d = int(dd)
        m = int((dd - d) * 60)
        s = (dd - d - m / 60.0) * 3600.0

        return f"{d}°{m:02d}'{s:05.2f}\"{direction}"

    # ------------------------------------------------------------------
    # Conversion Logic
    # ------------------------------------------------------------------

    def _convert_dms(self):
        """Convert DMS input to decimal degrees."""
        try:
            lat = self._parse_dms(self.txt_lat_dms.text())
            lon = self._parse_dms(self.txt_lon_dms.text())
        except Exception:
            QMessageBox.warning(
                self, "LVT4U",
                tr("Invalid DMS format. Try: 21°14'05\" or 21 14 05"))
            return

        self._show_result(lat, lon)

    def _convert_metric(self):
        """Convert metric coordinates (VN-2000/UTM) to WGS 84."""
        crs_code = self.cmb_crs.currentData()
        if not crs_code:
            QMessageBox.warning(
                self, "LVT4U", tr("Please select a valid CRS."))
            return

        try:
            x = float(self.txt_x.text().replace(",", "").strip())
            y = float(self.txt_y.text().replace(",", "").strip())
        except ValueError:
            QMessageBox.warning(
                self, "LVT4U", tr("Invalid coordinates. Enter numeric values."))
            return

        src_crs = _make_crs(crs_code)
        dst_crs = QgsCoordinateReferenceSystem("EPSG:4326")

        if not src_crs.isValid():
            QMessageBox.warning(
                self, "LVT4U",
                f"{tr('CRS not recognized:')} {crs_code}")
            return

        transform = QgsCoordinateTransform(
            src_crs, dst_crs, QgsProject.instance())
        try:
            pt = transform.transform(x, y)
            lat = pt.y()
            lon = pt.x()
        except Exception as e:
            QMessageBox.warning(
                self, "LVT4U", f"{tr('Conversion error:')} {e}")
            return

        self._show_result(lat, lon)

    def _show_result(self, lat, lon):
        """Display converted coordinates in WGS84 and optionally VN-2000."""
        self.txt_lat_dd.setText(f"{lat:.8f}")
        self.txt_lon_dd.setText(f"{lon:.8f}")
        self.txt_result_dms.setText(
            f"{self._dd_to_dms(lat, True)}  {self._dd_to_dms(lon, False)}")
        # Also update VN-2000 output
        self._update_vn2000()

    def _update_vn2000(self):
        """Recompute VN-2000 X/Y from current WGS84 result."""
        crs_code = self.cmb_out_crs.currentData()
        if not crs_code:
            self.txt_out_x.clear()
            self.txt_out_y.clear()
            return
        try:
            lat = float(self.txt_lat_dd.text())
            lon = float(self.txt_lon_dd.text())
        except (ValueError, TypeError):
            self.txt_out_x.clear()
            self.txt_out_y.clear()
            return

        src_crs = QgsCoordinateReferenceSystem("EPSG:4326")
        dst_crs = _make_crs(crs_code)
        if not dst_crs.isValid():
            self.txt_out_x.setText("CRS invalid")
            self.txt_out_y.clear()
            return

        xform = QgsCoordinateTransform(
            src_crs, dst_crs, QgsProject.instance())
        try:
            pt = xform.transform(QgsPointXY(lon, lat))
            self.txt_out_x.setText(f"{pt.x():.3f}")
            self.txt_out_y.setText(f"{pt.y():.3f}")
        except Exception:
            self.txt_out_x.setText("Error")
            self.txt_out_y.clear()

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------

    def _get_result_point(self):
        """Return the current result as (lat, lon) or None.
        Automatically triggers conversion if inputs are present but result is empty.
        """
        # 1. Try reading existing result
        try:
            lat_txt = self.txt_lat_dd.text().strip()
            lon_txt = self.txt_lon_dd.text().strip()
            if lat_txt and lon_txt:
                return float(lat_txt), float(lon_txt)
        except (ValueError, TypeError):
            pass  # nosec B110

        # 2. If no result, try converting from Metric (Priority)
        if self.txt_x.text().strip() and self.txt_y.text().strip():
            self._convert_metric()
            try:
                lat_txt = self.txt_lat_dd.text().strip()
                lon_txt = self.txt_lon_dd.text().strip()
                if lat_txt and lon_txt:
                    return float(lat_txt), float(lon_txt)
            except (ValueError, TypeError):
                pass  # nosec B110

        # 3. If still no result, try converting from DMS
        if self.txt_lat_dms.text().strip() and self.txt_lon_dms.text().strip():
            self._convert_dms()
            try:
                lat_txt = self.txt_lat_dd.text().strip()
                lon_txt = self.txt_lon_dd.text().strip()
                if lat_txt and lon_txt:
                    return float(lat_txt), float(lon_txt)
            except (ValueError, TypeError):
                pass  # nosec B110

        return None

    def _copy_result(self):
        """Copy result to clipboard."""
        pt = self._get_result_point()
        if not pt:
            return
        text = f"{pt[0]:.8f}, {pt[1]:.8f}"
        QApplication.clipboard().setText(text)
        self.iface.messageBar().pushSuccess(
            "LVT4U", f"📋 {tr('Copied:')} {text}")

    def _zoom_to_point(self):
        """Zoom map canvas to the converted point."""
        pt = self._get_result_point()
        if not pt:
            return

        wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
        canvas_crs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(
            wgs84, canvas_crs, QgsProject.instance())
        canvas_pt = transform.transform(QgsPointXY(pt[1], pt[0]))

        self.canvas.setCenter(canvas_pt)
        self.canvas.zoomScale(5000)
        self.canvas.refresh()

    def _add_marker(self):
        """Place a vertex marker on the canvas at the result point."""
        pt = self._get_result_point()
        if not pt:
            return

        # Remove old marker
        if self._marker:
            self.canvas.scene().removeItem(self._marker)
            self._marker = None

        wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
        canvas_crs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(
            wgs84, canvas_crs, QgsProject.instance())
        canvas_pt = transform.transform(QgsPointXY(pt[1], pt[0]))

        marker = QgsVertexMarker(self.canvas)
        marker.setCenter(canvas_pt)
        marker.setColor(Qt.GlobalColor.red)
        marker.setIconType(QgsVertexMarker.IconType.ICON_CROSS)
        marker.setIconSize(15)
        marker.setPenWidth(3)
        self._marker = marker

        self.canvas.setCenter(canvas_pt)
        self.canvas.zoomScale(5000)
        self.canvas.refresh()

        self.iface.messageBar().pushSuccess(
            "LVT4U",
            f"📍 {tr('Marker placed at')} {pt[0]:.6f}, {pt[1]:.6f}")

    def _clear_all(self):
        """Clear all input and output fields."""
        self.txt_lat_dms.clear()
        self.txt_lon_dms.clear()
        self.txt_x.clear()
        self.txt_y.clear()
        self.txt_lat_dd.clear()
        self.txt_lon_dd.clear()
        self.txt_result_dms.clear()
        self.txt_out_x.clear()
        self.txt_out_y.clear()

        if self._marker:
            self.canvas.scene().removeItem(self._marker)
            self._marker = None

    def _add_to_layer(self):
        """Add the current result point to a memory layer."""
        pt = self._get_result_point()
        if not pt:
            QMessageBox.warning(
                self, "LVT4U", tr("No result to add. Convert first."))
            return

        # Create memory layer on first use
        proj = QgsProject.instance()
        if not self._mem_layer or not proj.mapLayer(self._mem_layer.id()):
            self._mem_layer = QgsVectorLayer(
                "Point?crs=EPSG:4326",
                "LVT4U Converted Points",
                "memory")
            dp = self._mem_layer.dataProvider()
            dp.addAttributes([
                QgsField("name", QVariant.String),
                QgsField("lat", QVariant.Double),
                QgsField("lon", QVariant.Double),
                QgsField("dms", QVariant.String),
            ])
            self._mem_layer.updateFields()
            QgsProject.instance().addMapLayer(self._mem_layer)

        # Add feature
        feat = QgsFeature(self._mem_layer.fields())
        feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(pt[1], pt[0])))
        name = self.txt_point_name.text().strip()
        if not name:
            name = f"Point {self._mem_layer.featureCount() + 1}"
        feat.setAttributes([
            name,
            round(pt[0], 8),
            round(pt[1], 8),
            self.txt_result_dms.text(),
        ])
        self._mem_layer.dataProvider().addFeature(feat)
        self._mem_layer.updateExtents()
        self._mem_layer.triggerRepaint()

        self.iface.messageBar().pushSuccess(
            "LVT4U",
            f"📌 {tr('Added:')} {name} ({pt[0]:.6f}, {pt[1]:.6f})")

    def closeEvent(self, event):
        """Clean up marker on close."""
        if self._marker:
            self.canvas.scene().removeItem(self._marker)
            self._marker = None
        super().closeEvent(event)

    def _show_help(self):
        """Open help dialog for Coord Converter module."""
        from ..shared.help_dialog import HelpDialog
        dlg = HelpDialog("coord_convert", parent=self)
        dlg.exec()
