#!/usr/bin/env python3
"""GUI for freeze-watcher service. Shows live metrics and captured snapshots."""

import os
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path

from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QFont, QColor, QAction, QIcon
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QListWidget, QListWidgetItem, QTextEdit, QPushButton,
    QSplitter, QGroupBox, QGridLayout, QSystemTrayIcon, QMenu, QStyle
)

LOG_DIR = Path("/var/log/freeze-watcher")
SERVICE = "freeze-watcher.service"


def read_psi(name):
    try:
        line = (Path("/proc/pressure") / name).read_text().splitlines()[0]
        m = re.search(r"avg10=([\d.]+)", line)
        return float(m.group(1)) if m else 0.0
    except Exception:
        return 0.0


def read_dstate_count():
    try:
        out = subprocess.run(
            ["ps", "-eo", "state", "--no-headers"],
            capture_output=True, text=True, timeout=2,
        ).stdout
        return sum(1 for ln in out.splitlines() if ln.startswith("D"))
    except Exception:
        return 0


def read_loadavg():
    try:
        return float(Path("/proc/loadavg").read_text().split()[0])
    except Exception:
        return 0.0


def read_procs_blocked():
    try:
        for ln in Path("/proc/stat").read_text().splitlines():
            if ln.startswith("procs_blocked"):
                return int(ln.split()[1])
    except Exception:
        pass
    return 0


def service_active():
    r = subprocess.run(["systemctl", "is-active", SERVICE], capture_output=True, text=True)
    return r.stdout.strip() == "active"


class MetricLabel(QLabel):
    def __init__(self, name):
        super().__init__("--")
        self.name = name
        self.setAlignment(Qt.AlignCenter)
        self.setMinimumHeight(48)
        f = QFont()
        f.setPointSize(18)
        f.setBold(True)
        self.setFont(f)
        self._set_color("#888")

    def _set_color(self, hex_):
        self.setStyleSheet(
            f"background:#1e1e1e; color:{hex_}; border:1px solid #333; border-radius:6px; padding:6px;"
        )

    def set_value(self, value, warn, crit, suffix=""):
        self.setText(f"{value}{suffix}")
        if value >= crit:
            self._set_color("#ff5555")
        elif value >= warn:
            self._set_color("#f5a623")
        else:
            self._set_color("#5cdb95")


class FreezeMonitor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Freeze Watcher")
        self.resize(1100, 700)

        central = QWidget()
        self.setCentralWidget(central)
        root = QVBoxLayout(central)

        # Status bar
        self.status_label = QLabel()
        self.status_label.setStyleSheet("padding:8px; font-weight:bold;")
        root.addWidget(self.status_label)

        # Live metrics grid
        metrics_box = QGroupBox("Live metrics (1s refresh)")
        grid = QGridLayout(metrics_box)
        labels = ["PSI IO %", "PSI CPU %", "PSI Mem %", "D-state", "Blocked", "Load (1m)"]
        self.metrics = {}
        for i, name in enumerate(labels):
            grid.addWidget(QLabel(name, alignment=Qt.AlignCenter), 0, i)
            m = MetricLabel(name)
            self.metrics[name] = m
            grid.addWidget(m, 1, i)
        root.addWidget(metrics_box)

        # Buttons
        btn_row = QHBoxLayout()
        self.btn_start = QPushButton("Start service")
        self.btn_stop = QPushButton("Stop service")
        self.btn_refresh = QPushButton("Refresh events")
        self.btn_open = QPushButton("Open log dir")
        self.btn_clear = QPushButton("Delete all snapshots")
        for b in (self.btn_start, self.btn_stop, self.btn_refresh, self.btn_open, self.btn_clear):
            btn_row.addWidget(b)
        btn_row.addStretch()
        self.btn_start.clicked.connect(lambda: self._svc("start"))
        self.btn_stop.clicked.connect(lambda: self._svc("stop"))
        self.btn_refresh.clicked.connect(self.load_events)
        self.btn_open.clicked.connect(lambda: subprocess.Popen(["xdg-open", str(LOG_DIR)]))
        self.btn_clear.clicked.connect(self.clear_snapshots)
        root.addLayout(btn_row)

        # Splitter: list | detail
        splitter = QSplitter(Qt.Horizontal)
        self.event_list = QListWidget()
        self.event_list.itemSelectionChanged.connect(self.show_selected)
        splitter.addWidget(self.event_list)
        self.detail = QTextEdit()
        self.detail.setReadOnly(True)
        self.detail.setFont(QFont("monospace"))
        splitter.addWidget(self.detail)
        splitter.setSizes([350, 750])
        root.addWidget(splitter, stretch=1)

        self._known_events = set()

        # Tray (created before timer so tick() can use it)
        self.tray = QSystemTrayIcon(self.style().standardIcon(QStyle.SP_ComputerIcon), self)
        self.tray.setToolTip("Freeze Watcher")
        menu = QMenu()
        show_act = QAction("Show window", self)
        show_act.triggered.connect(self.showNormal)
        quit_act = QAction("Quit", self)
        quit_act.triggered.connect(QApplication.quit)
        menu.addAction(show_act)
        menu.addAction(quit_act)
        self.tray.setContextMenu(menu)
        self.tray.activated.connect(self._tray_clicked)
        self.tray.show()

        # Timer
        self.timer = QTimer()
        self.timer.timeout.connect(self.tick)
        self.timer.start(1000)
        self.load_events()
        self.tick()

    def _tray_clicked(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            else:
                self.showNormal()
                self.raise_()

    def closeEvent(self, ev):
        ev.ignore()
        self.hide()

    def _svc(self, action):
        subprocess.run(["pkexec", "systemctl", action, SERVICE])
        self.tick()

    def tick(self):
        psi_io = read_psi("io")
        psi_cpu = read_psi("cpu")
        psi_mem = read_psi("memory")
        dstate = read_dstate_count()
        blocked = read_procs_blocked()
        load = read_loadavg()

        self.metrics["PSI IO %"].set_value(int(psi_io), 30, 60, "")
        self.metrics["PSI CPU %"].set_value(int(psi_cpu), 30, 60, "")
        self.metrics["PSI Mem %"].set_value(int(psi_mem), 20, 50, "")
        self.metrics["D-state"].set_value(dstate, 3, 6, "")
        self.metrics["Blocked"].set_value(blocked, 3, 6, "")
        self.metrics["Load (1m)"].set_value(int(load), 8, 16, "")

        active = service_active()
        if active:
            self.status_label.setText("● Service: ACTIVE")
            self.status_label.setStyleSheet("padding:8px; font-weight:bold; color:#5cdb95;")
        else:
            self.status_label.setText("● Service: STOPPED")
            self.status_label.setStyleSheet("padding:8px; font-weight:bold; color:#ff5555;")

        self.tray.setToolTip(
            f"Freeze Watcher — {'active' if active else 'stopped'}\n"
            f"PSI io {int(psi_io)}% / cpu {int(psi_cpu)}% — D:{dstate} blk:{blocked} load:{load:.2f}"
        )

        # Auto-refresh events list when a new file appears
        current = set(p.name for p in LOG_DIR.glob("snap-*.log")) if LOG_DIR.exists() else set()
        if current != self._known_events:
            new = current - self._known_events
            self._known_events = current
            self.load_events()
            for n in new:
                self.tray.showMessage(
                    "Freeze Watcher",
                    f"Captured: {n}",
                    QSystemTrayIcon.Warning,
                    5000,
                )

    def load_events(self):
        self.event_list.clear()
        if not LOG_DIR.exists():
            return
        files = sorted(LOG_DIR.glob("snap-*.log"), key=lambda p: p.stat().st_mtime, reverse=True)
        for p in files:
            reason = ""
            try:
                first = p.read_text().splitlines()[0]
                if "TRIGGERED:" in first:
                    reason = first.split("TRIGGERED:", 1)[1].strip().rstrip("=").strip()
            except Exception:
                pass
            ts = datetime.fromtimestamp(p.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
            item = QListWidgetItem(f"{ts}\n  {reason}")
            item.setData(Qt.UserRole, str(p))
            self.event_list.addItem(item)
        self._known_events = set(p.name for p in files)

    def show_selected(self):
        items = self.event_list.selectedItems()
        if not items:
            return
        path = items[0].data(Qt.UserRole)
        try:
            self.detail.setPlainText(Path(path).read_text())
        except Exception as e:
            self.detail.setPlainText(f"Error: {e}")

    def clear_snapshots(self):
        if not LOG_DIR.exists():
            return
        from PySide6.QtWidgets import QMessageBox
        if QMessageBox.question(self, "Delete", "Delete all captured snapshots?") != QMessageBox.Yes:
            return
        for p in LOG_DIR.glob("snap-*.log"):
            try:
                subprocess.run(["pkexec", "rm", str(p)])
            except Exception:
                pass
        self.load_events()
        self.detail.clear()


def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    w = FreezeMonitor()
    w.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
