# -*- coding: utf-8 -*-
"""
Rename Field Dialog
===================
Dialog for renaming vector layer fields and / or changing their data type.

Two modes
---------
• Rename only (same type)  → uses QgsVectorLayer.renameAttribute()
  — instant, no data copy, works on all providers that support it.

• Rename + Change type     → create new field → copy values → delete old.
  — safe fallback for providers that don't support in-place rename,
    and mandatory when the user chooses a different field type.

Author  : Loc Vu Trung / LVT Center
License : GPL-3.0
"""

from __future__ import annotations

import warnings

from qgis.PyQt.QtCore import Qt
from ..shared.qt_compat import QVariant
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
    QLabel, QLineEdit, QComboBox, QPushButton,
    QGroupBox, QMessageBox, QSizePolicy,
    QCheckBox, QFrame, QProgressBar,
)
from qgis.PyQt.QtGui import QFont, QColor, QPalette
from qgis.core import (
    QgsMapLayerProxyModel,
    QgsFieldProxyModel,
    QgsField,
    QgsVectorLayer,
    QgsWkbTypes,
    Qgis,
)
from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox

from ..shared.i18n import tr


# ---------------------------------------------------------------------------
# Field type catalogue
# ---------------------------------------------------------------------------
_FIELD_TYPES = [
    ("Text (String)",   QVariant.String),
    ("Integer",         QVariant.Int),
    ("Long Integer",    QVariant.LongLong),
    ("Double",          QVariant.Double),
    ("Boolean",         QVariant.Bool),
    ("Date",            QVariant.Date),
    ("Time",            QVariant.Time),
    ("DateTime",        QVariant.DateTime),
]

_TYPE_NAMES = {v: k for k, v in _FIELD_TYPES}


class RenameFieldDialog(QDialog):
    """Modal dialog — Rename / retype a vector layer field.

    Parameters
    ----------
    iface : QgisInterface
        Main QGIS interface (used for canvas + message bar access).
    parent : QWidget, optional
    """

    def __init__(self, iface, parent=None):
        super().__init__(parent or iface.mainWindow())
        self.iface = iface
        self.setWindowTitle(tr("Rename Field & Type"))
        self.setMinimumWidth(460)
        self.setWindowFlags(
            self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint
        )
        self._build_ui()
        self._connect_signals()
        self._refresh_fields()

    # ------------------------------------------------------------------
    # UI construction
    # ------------------------------------------------------------------

    def _build_ui(self):
        root = QVBoxLayout(self)
        root.setSpacing(10)
        root.setContentsMargins(14, 14, 14, 14)

        # ── Title ──
        title = QLabel(tr("Rename Field & Type"))
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        font = QFont()
        font.setBold(True)
        font.setPointSize(12)
        title.setFont(font)
        title.setStyleSheet(
            "color: #1565C0; padding: 4px 0 8px 0;"
        )
        root.addWidget(title)

        # ── Source group ──
        src_box = QGroupBox(tr("Source"))
        src_layout = QFormLayout(src_box)
        src_layout.setSpacing(8)

        self.cbx_layer = QgsMapLayerComboBox()
        self.cbx_layer.setFilters(QgsMapLayerProxyModel.Filter.VectorLayer)
        self.cbx_layer.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
        )
        src_layout.addRow(tr("Layer:"), self.cbx_layer)

        self.cbx_field = QgsFieldComboBox()
        self.cbx_field.setFilters(QgsFieldProxyModel.Filter.AllTypes)
        src_layout.addRow(tr("Field to rename:"), self.cbx_field)

        # Current type info
        self.lbl_current_type = QLabel("—")
        self.lbl_current_type.setStyleSheet("color: #555; font-style: italic;")
        src_layout.addRow(tr("Current type:"), self.lbl_current_type)

        root.addWidget(src_box)

        # ── Target group ──
        tgt_box = QGroupBox(tr("New settings"))
        tgt_layout = QFormLayout(tgt_box)
        tgt_layout.setSpacing(8)

        self.txt_name = QLineEdit()
        self.txt_name.setPlaceholderText(tr("New field name (leave blank to keep)"))
        tgt_layout.addRow(tr("New name:"), self.txt_name)

        self.cbx_type = QComboBox()
        for label, variant in _FIELD_TYPES:
            self.cbx_type.addItem(tr(label), variant)
        tgt_layout.addRow(tr("New type:"), self.cbx_type)

        self.chk_type_change = QCheckBox(tr("Also change field type"))
        self.chk_type_change.setToolTip(
            tr(
                "If unchecked, only the field name is changed (fast).\n"
                "If checked, a new field is created, values are copied,\n"
                "and the old field is deleted."
            )
        )
        tgt_layout.addRow("", self.chk_type_change)
        self.cbx_type.setEnabled(False)

        root.addWidget(tgt_box)

        # ── Progress ──
        self.progress = QProgressBar()
        self.progress.setRange(0, 0)   # indeterminate
        self.progress.setVisible(False)
        self.progress.setFixedHeight(10)
        root.addWidget(self.progress)

        # ── Status label ──
        self.lbl_status = QLabel("")
        self.lbl_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.lbl_status.setWordWrap(True)
        self.lbl_status.setStyleSheet("font-size: 11px; color: #333;")
        root.addWidget(self.lbl_status)

        # ── Separator ──
        sep = QFrame()
        sep.setFrameShape(QFrame.Shape.HLine)
        sep.setStyleSheet("color: #ccc;")
        root.addWidget(sep)

        # ── Buttons ──
        btn_row = QHBoxLayout()
        btn_row.addStretch()

        self.btn_rename = QPushButton(tr("✔  Rename"))
        self.btn_rename.setDefault(True)
        self.btn_rename.setFixedHeight(32)
        self.btn_rename.setStyleSheet(
            "QPushButton{background:#1565C0;color:#fff;border-radius:4px;padding:0 18px;}"
            "QPushButton:hover{background:#1976D2;}"
            "QPushButton:disabled{background:#b0bec5;}"
        )

        self.btn_close = QPushButton(tr("✕  Close"))
        self.btn_close.setFixedHeight(32)
        self.btn_close.setStyleSheet(
            "QPushButton{background:#e53935;color:#fff;border-radius:4px;padding:0 18px;}"
            "QPushButton:hover{background:#ef5350;}"
        )

        btn_row.addWidget(self.btn_rename)
        btn_row.addWidget(self.btn_close)
        root.addLayout(btn_row)

    # ------------------------------------------------------------------
    # Signal wiring
    # ------------------------------------------------------------------

    def _connect_signals(self):
        self.cbx_layer.layerChanged.connect(self._refresh_fields)
        self.cbx_field.fieldChanged.connect(self._on_field_changed)
        self.chk_type_change.toggled.connect(self._on_type_change_toggled)
        self.btn_rename.clicked.connect(self._do_rename)
        self.btn_close.clicked.connect(self.close)

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    def _refresh_fields(self):
        """Sync the field combo with the currently selected layer."""
        layer = self.cbx_layer.currentLayer()
        self.cbx_field.setLayer(layer)
        self._on_field_changed(self.cbx_field.currentField())

    def _on_field_changed(self, field_name: str):
        """Update current-type label and preset the type combo."""
        layer = self.cbx_layer.currentLayer()
        if not layer or not field_name:
            self.lbl_current_type.setText("—")
            return

        idx = layer.fields().indexOf(field_name)
        if idx < 0:
            self.lbl_current_type.setText("—")
            return

        fld = layer.fields().at(idx)
        ftype = fld.type()
        type_label = _TYPE_NAMES.get(ftype, f"QVariant({ftype})")
        self.lbl_current_type.setText(tr(type_label))

        # Pre-select matching type in combo
        for i in range(self.cbx_type.count()):
            if self.cbx_type.itemData(i) == ftype:
                self.cbx_type.setCurrentIndex(i)
                break

        # Pre-fill new name
        self.txt_name.setPlaceholderText(field_name)

    def _on_type_change_toggled(self, checked: bool):
        self.cbx_type.setEnabled(checked)

    def _set_status(self, msg: str, ok: bool = True):
        color = "#2e7d32" if ok else "#c62828"
        self.lbl_status.setStyleSheet(
            f"font-size: 11px; color: {color}; font-weight: bold;"
        )
        self.lbl_status.setText(msg)

    # ------------------------------------------------------------------
    # Core rename logic
    # ------------------------------------------------------------------

    def _do_rename(self):
        layer: QgsVectorLayer = self.cbx_layer.currentLayer()
        if not layer:
            self._set_status(tr("⚠ Please select a layer."), ok=False)
            return

        old_name = self.cbx_field.currentField()
        if not old_name:
            self._set_status(tr("⚠ Please select a field."), ok=False)
            return

        new_name = self.txt_name.text().strip() or old_name
        change_type = self.chk_type_change.isChecked()
        new_type = self.cbx_type.currentData() if change_type else None

        # Validation
        if new_name == old_name and not change_type:
            self._set_status(
                tr("⚠ No changes requested (same name, type unchanged)."),
                ok=False,
            )
            return

        if new_name != old_name and new_name in [f.name() for f in layer.fields()]:
            self._set_status(
                tr(f'⚠ Field "{new_name}" already exists.'), ok=False
            )
            return

        if not _is_valid_field_name(new_name):
            self._set_status(
                tr("⚠ Invalid field name (use letters, digits, underscore; max 10 chars)."),
                ok=False,
            )
            return

        # Start editing if needed
        was_editing = layer.isEditable()
        if not was_editing and not layer.startEditing():
            self._set_status(tr("✖ Cannot start editing on this layer."), ok=False)
            return

        self.btn_rename.setEnabled(False)
        self.progress.setVisible(True)
        self.lbl_status.setText(tr("Working…"))
        self.repaint()

        try:
            if not change_type:
                # ── Fast path: in-place rename ──────────────────────
                old_idx = layer.fields().indexOf(old_name)
                ok = layer.renameAttribute(old_idx, new_name)
                if not ok:
                    raise RuntimeError(
                        tr("renameAttribute() failed — provider may not support it.")
                    )
            else:
                # ── Rebuild path: create → copy → delete ─────────────
                self._rebuild_field(layer, old_name, new_name, new_type)

            # Commit
            if not layer.commitChanges():
                errs = layer.commitErrors()
                raise RuntimeError("\n".join(errs) if errs else tr("Commit failed."))

            if not was_editing:
                layer.startEditing()  # leave in same state as before

            self._set_status(
                tr(f'✔ Field "{old_name}" → "{new_name}" — done!'), ok=True
            )
            self.txt_name.clear()
            self._refresh_fields()

            # Notification bar — non-critical, log but don't raise
            try:
                self.iface.messageBar().pushMessage(
                    "LVT4U",
                    tr(f'Field renamed: "{old_name}" → "{new_name}"'),
                    level=Qgis.MessageLevel.Info,
                    duration=3,
                )
            except Exception as bar_err:
                import logging
                logging.getLogger(__name__).warning(
                    "LVT RenameField: messageBar.pushMessage failed: %s", bar_err
                )

        except Exception as exc:
            # Attempt rollback; report both errors if rollback itself fails
            try:
                layer.rollBack()
            except Exception as rb_err:
                import logging
                logging.getLogger(__name__).error(
                    "LVT RenameField: rollBack failed after '%s': %s",
                    exc, rb_err,
                )
                self._set_status(
                    f"✖ {exc}  [⚠ rollback also failed: {rb_err}]", ok=False
                )
                return
            self._set_status(f"✖ {exc}", ok=False)

        finally:
            self.progress.setVisible(False)
            self.btn_rename.setEnabled(True)

    def _rebuild_field(
        self,
        layer: QgsVectorLayer,
        old_name: str,
        new_name: str,
        new_type: int,
    ):
        """Create new field → copy all values → delete old field."""
        # 1. Create new field
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", DeprecationWarning)
            new_field = QgsField(new_name, new_type)

        if not layer.addAttribute(new_field):
            raise RuntimeError(tr("Failed to add new field."))

        new_idx = layer.fields().indexOf(new_name)
        old_idx = layer.fields().indexOf(old_name)

        if new_idx < 0 or old_idx < 0:
            raise RuntimeError(tr("Field index lookup failed after add."))

        # 2. Copy values feature by feature
        for feat in layer.getFeatures():
            raw_val = feat[old_name]
            # Type-coerce where sensible
            typed_val = _coerce_value(raw_val, new_type)
            layer.changeAttributeValue(feat.id(), new_idx, typed_val)

        # 3. Delete old field (re-lookup index in case it shifted)
        old_idx = layer.fields().indexOf(old_name)
        if not layer.deleteAttribute(old_idx):
            raise RuntimeError(tr("Failed to delete original field."))


# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------

def _is_valid_field_name(name: str) -> bool:
    """Basic check: non-empty, <= 10 chars, no spaces/special chars."""
    if not name or len(name) > 10:
        return False
    import re
    return bool(re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', name))


def _coerce_value(value, target_type: int):
    """Best-effort value conversion when changing field type."""
    if value is None:
        return None
    try:
        if target_type == QVariant.String:
            return str(value)
        if target_type in (QVariant.Int, QVariant.LongLong):
            return int(float(str(value)))
        if target_type == QVariant.Double:
            return float(str(value))
        if target_type == QVariant.Bool:
            return bool(value)
    except (ValueError, TypeError):
        return None
    return value
