from __future__ import annotations

import difflib, re, html as _html
from datetime import datetime
from PySide6.QtCore import Qt, Slot
from PySide6.QtWidgets import (
    QDialog,
    QVBoxLayout,
    QHBoxLayout,
    QListWidget,
    QListWidgetItem,
    QPushButton,
    QMessageBox,
    QTextBrowser,
    QTabWidget,
)


def _html_to_text(s: str) -> str:
    """Lightweight HTML→text for diff (keeps paragraphs/line breaks)."""
    IMG_RE = re.compile(r"(?is)<img\b[^>]*>")
    STYLE_SCRIPT_RE = re.compile(r"(?is)<(script|style)[^>]*>.*?</\1>")
    COMMENT_RE = re.compile(r"<!--.*?-->", re.S)
    BR_RE = re.compile(r"(?i)<br\s*/?>")
    BLOCK_END_RE = re.compile(r"(?i)</(p|div|section|article|li|h[1-6])\s*>")
    TAG_RE = re.compile(r"<[^>]+>")
    MULTINL_RE = re.compile(r"\n{3,}")

    s = IMG_RE.sub("[ Image changed - see Preview pane ]", s)
    s = STYLE_SCRIPT_RE.sub("", s)
    s = COMMENT_RE.sub("", s)
    s = BR_RE.sub("\n", s)
    s = BLOCK_END_RE.sub("\n", s)
    s = TAG_RE.sub("", s)
    s = _html.unescape(s)
    s = MULTINL_RE.sub("\n\n", s)
    return s.strip()


def _colored_unified_diff_html(old_html: str, new_html: str) -> str:
    """Return HTML with colored unified diff (+ green, - red, context gray)."""
    a = _html_to_text(old_html).splitlines()
    b = _html_to_text(new_html).splitlines()
    ud = difflib.unified_diff(a, b, fromfile="current", tofile="selected", lineterm="")
    lines = []
    for line in ud:
        if line.startswith("+") and not line.startswith("+++"):
            lines.append(
                f"<span style='color:#116329'>+ {_html.escape(line[1:])}</span>"
            )
        elif line.startswith("-") and not line.startswith("---"):
            lines.append(
                f"<span style='color:#b31d28'>- {_html.escape(line[1:])}</span>"
            )
        elif line.startswith("@@"):
            lines.append(f"<span style='color:#6f42c1'>{_html.escape(line)}</span>")
        else:
            lines.append(f"<span style='color:#586069'>{_html.escape(line)}</span>")
    css = "pre { font-family: Consolas,Menlo,Monaco,monospace; font-size: 13px; }"
    return f"<style>{css}</style><pre>{'<br>'.join(lines)}</pre>"


class HistoryDialog(QDialog):
    """Show versions for a date, preview, diff, and allow revert."""

    def __init__(self, db, date_iso: str, parent=None):
        super().__init__(parent)
        self.setWindowTitle(f"History — {date_iso}")
        self._db = db
        self._date = date_iso
        self._versions = []  # list[dict] from DB
        self._current_id = None  # id of current

        root = QVBoxLayout(self)

        # Top: list of versions
        top = QHBoxLayout()
        self.list = QListWidget()
        self.list.setMinimumSize(500, 650)
        self.list.currentItemChanged.connect(self._on_select)
        top.addWidget(self.list, 1)

        # Right: tabs (Preview / Diff)
        self.tabs = QTabWidget()
        self.preview = QTextBrowser()
        self.preview.setOpenExternalLinks(True)
        self.diff = QTextBrowser()
        self.diff.setOpenExternalLinks(False)
        self.tabs.addTab(self.preview, "Preview")
        self.tabs.addTab(self.diff, "Diff")
        self.tabs.setMinimumSize(500, 650)
        top.addWidget(self.tabs, 2)

        root.addLayout(top)

        # Buttons
        row = QHBoxLayout()
        row.addStretch(1)
        self.btn_revert = QPushButton("Revert to Selected")
        self.btn_revert.clicked.connect(self._revert)
        self.btn_close = QPushButton("Close")
        self.btn_close.clicked.connect(self.reject)
        row.addWidget(self.btn_revert)
        row.addWidget(self.btn_close)
        root.addLayout(row)

        self._load_versions()

    # --- Data/UX helpers ---
    def _fmt_local(self, iso_utc: str) -> str:
        """
        Convert UTC in the database to user's local tz
        """
        dt = datetime.fromisoformat(iso_utc.replace("Z", "+00:00"))
        local = dt.astimezone()
        return local.strftime("%Y-%m-%d %H:%M:%S %Z")

    def _load_versions(self):
        self._versions = self._db.list_versions(
            self._date
        )  # [{id,version_no,created_at,note,is_current}]
        self._current_id = next(
            (v["id"] for v in self._versions if v["is_current"]), None
        )
        self.list.clear()
        for v in self._versions:
            label = f"v{v['version_no']} — {self._fmt_local(v['created_at'])}"
            if v.get("note"):
                label += f"  ·  {v['note']}"
            if v["is_current"]:
                label += "  **(current)**"
            it = QListWidgetItem(label)
            it.setData(Qt.UserRole, v["id"])
            self.list.addItem(it)
        # select the first non-current if available, else current
        idx = 0
        for i, v in enumerate(self._versions):
            if not v["is_current"]:
                idx = i
                break
        if self.list.count():
            self.list.setCurrentRow(idx)

    @Slot()
    def _on_select(self):
        item = self.list.currentItem()
        if not item:
            self.preview.clear()
            self.diff.clear()
            self.btn_revert.setEnabled(False)
            return
        sel_id = item.data(Qt.UserRole)
        # Preview selected as HTML
        sel = self._db.get_version(version_id=sel_id)
        self.preview.setHtml(sel["content"])
        # Diff vs current (textual diff)
        cur = self._db.get_version(version_id=self._current_id)
        self.diff.setHtml(_colored_unified_diff_html(cur["content"], sel["content"]))
        # Enable revert only if selecting a non-current
        self.btn_revert.setEnabled(sel_id != self._current_id)

    @Slot()
    def _revert(self):
        item = self.list.currentItem()
        if not item:
            return
        sel_id = item.data(Qt.UserRole)
        if sel_id == self._current_id:
            return
        # Flip head pointer
        try:
            self._db.revert_to_version(self._date, version_id=sel_id)
        except Exception as e:
            QMessageBox.critical(self, "Revert failed", str(e))
            return
        self.accept()
