"""perf — rootless thermal/perf-mode reader + sudo-backed mode switch.

Ported from perf-tray.py (the PyQt tray) with the Qt dropped, so the same proven
logic backs the web Bridge. Reading thermals/mode is rootless (hwmon + cpufreq
sysfs); only *changing* mode needs root, done via `sudo -n` against a ROOT-OWNED,
sudoers-whitelisted copy of perf.sh at /usr/local/sbin/perf-audio (see
perf-audio.sudoers) — never the user-editable repo copy.

DRY note: perf-tray.py still has its own copy of Thermals/detect_mode; a later
pass can have it import this module. Kept separate now to avoid touching the
working tray.
"""
from __future__ import annotations

import glob
import os
import subprocess

# Root-owned, sudoers-whitelisted deployment of perf.sh (same as perf-tray).
SCRIPT = os.environ.get("PERF_TRAY_SCRIPT", "/usr/local/sbin/perf-audio")

# mode key -> (label, perf.sh flag)
MODES = {
    "cool":     ("Cool",     "--cool"),
    "standard": ("Standard", "--optimize"),
    "extreme":  ("Extreme",  "--extreme"),
    "normal":   ("Normal",   "--stop"),
}

# temp thresholds (°C) → state name; the UI maps state→color (matches tray).
TEMP_STATES = ((55, "cool"), (70, "warm"), (85, "hot"), (10_000, "critical"))


def _read(path, default=None):
    try:
        with open(path) as f:
            return f.read().strip()
    except OSError:
        return default


def _read_int(path, default=None):
    v = _read(path)
    try:
        return int(v)
    except (TypeError, ValueError):
        return default


def _hwmon_by_name(name):
    for d in glob.glob("/sys/class/hwmon/hwmon*"):
        if _read(os.path.join(d, "name")) == name:
            return d
    return None


def temp_state(c):
    if c is None:
        return "unknown"
    for ceiling, state in TEMP_STATES:
        if c < ceiling:
            return state
    return "critical"


class Thermals:
    """Rootless reader for package/core temps, fans, freq and throttle counts."""

    def __init__(self):
        self.coretemp = _hwmon_by_name("coretemp")
        self.dell = _hwmon_by_name("dell_smm")
        self._pkg_input = self._find_label_input(self.coretemp, "Package id 0")
        self._throttle_base = self._throttle_sum()

    def _find_label_input(self, chip, label):
        if not chip:
            return None
        for lbl in glob.glob(os.path.join(chip, "temp*_label")):
            if _read(lbl) == label:
                return lbl.replace("_label", "_input")
        t1 = os.path.join(chip, "temp1_input")
        return t1 if os.path.exists(t1) else None

    def package_c(self):
        v = _read_int(self._pkg_input)
        return round(v / 1000) if v is not None else None

    def cores_c(self):
        out = []
        if self.coretemp:
            for lbl in sorted(glob.glob(os.path.join(self.coretemp, "temp*_label"))):
                if (_read(lbl) or "").startswith("Core"):
                    v = _read_int(lbl.replace("_label", "_input"))
                    if v is not None:
                        out.append(round(v / 1000))
        return out

    def fans_rpm(self):
        out = []
        if self.dell:
            for fi in sorted(glob.glob(os.path.join(self.dell, "fan*_input"))):
                v = _read_int(fi)
                if v is not None:
                    out.append(v)
        return out

    def freq_mhz(self):
        freqs = [_read_int(p) for p in
                 glob.glob("/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq")]
        freqs = [f for f in freqs if f]
        if not freqs:
            return (None, None)
        return (round(max(freqs) / 1000), round(sum(freqs) / len(freqs) / 1000))

    def _throttle_sum(self):
        total = 0
        for p in glob.glob(
                "/sys/devices/system/cpu/cpu*/thermal_throttle/core_throttle_count"):
            total += _read_int(p, 0) or 0
        return total

    def throttle_delta(self):
        return self._throttle_sum() - self._throttle_base


def detect_mode():
    gov = _read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
    epp = _read("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference")
    maxp = _read_int("/sys/devices/system/cpu/intel_pstate/max_perf_pct", 100)
    minp = _read_int("/sys/devices/system/cpu/intel_pstate/min_perf_pct", 0)
    if gov == "performance" and minp >= 100:
        return "extreme"
    if gov == "performance":
        return "standard"
    if epp == "balance_performance" and (maxp or 100) < 100:
        return "cool"
    return "normal"


def script_available():
    """True if the sudoers-whitelisted root perf.sh is deployed."""
    return os.path.exists(SCRIPT)


def set_mode(mode):
    """Switch perf mode via `sudo -n`. Returns (ok, message)."""
    if mode not in MODES:
        return False, f"unknown mode {mode!r}; have {sorted(MODES)}"
    if not script_available():
        return False, (f"{SCRIPT} not deployed — install the root-owned perf.sh + "
                       "sudoers rule (see README 'Deploy mode-switching')")
    flag = MODES[mode][1]
    try:
        r = subprocess.run(["sudo", "-n", SCRIPT, flag],
                           capture_output=True, text=True, timeout=30)
    except (OSError, subprocess.SubprocessError) as e:
        return False, str(e)
    if r.returncode != 0:
        return False, (r.stderr or r.stdout or f"exit {r.returncode}").strip()
    return True, (r.stdout or "ok").strip()


def snapshot(therm: "Thermals | None" = None) -> dict:
    """One JSON-ready reading of mode + thermals (the /api/perf payload)."""
    t = therm or Thermals()
    pkg = t.package_c()
    fmax, favg = t.freq_mhz()
    return {
        "mode": detect_mode(),
        "package_c": pkg,
        "temp_state": temp_state(pkg),
        "cores_c": t.cores_c(),
        "fans_rpm": t.fans_rpm(),
        "freq_max_mhz": fmax,
        "freq_avg_mhz": favg,
        "throttle_delta": t.throttle_delta(),
        "script_available": script_available(),
        "modes": {k: v[0] for k, v in MODES.items()},
    }
