"""Linux process priority, scheduling, and cgroup optimization."""
from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import Optional

from mesh_boost.optimizer.base import (
    IS_LINUX, OptimizationResult, RollbackManager,
    is_admin, run_cmd, sysfs_read, sysfs_write,
)

logger = logging.getLogger(__name__)


class LinuxProcessOptimizer:
    """Optimize Linux process scheduling for low latency."""

    def __init__(self):
        self._rollback = RollbackManager("linux_process")

    def apply(self, dry_run: bool = False) -> OptimizationResult:
        result = OptimizationResult("linux_process")

        if not IS_LINUX:
            result.skip("all", "not Linux")
            return result

        self._apply_gamemode(result, dry_run)
        self._apply_io_scheduler(result, dry_run)
        self._apply_transparent_hugepages(result, dry_run)
        self._apply_watchdog(result, dry_run)

        return result

    def restore(self) -> OptimizationResult:
        result = OptimizationResult("linux_process_restore")
        if not IS_LINUX:
            return result

        self._restore_io_scheduler(result)
        self._restore_transparent_hugepages(result)
        self._restore_watchdog(result)

        self._rollback.clear()
        return result

    def boost_process(self, pid: int, priority: str = "high") -> dict:
        """Boost a specific process on Linux.

        Uses nice, ionice, and optionally SCHED_FIFO for real-time.
        """
        results = {}

        # Set nice value (lower = higher priority)
        nice_map = {"above_normal": -5, "high": -10, "realtime": -20}
        nice_val = nice_map.get(priority, -10)

        ok, _ = run_cmd(["renice", str(nice_val), "-p", str(pid)])
        if ok:
            results["nice"] = nice_val
        else:
            results["nice_error"] = "renice failed (need root for negative nice)"

        # Set I/O priority to real-time class
        ok, _ = run_cmd(["ionice", "-c", "1", "-n", "0", "-p", str(pid)])
        if ok:
            results["ionice"] = "realtime"
        else:
            results["ionice_error"] = "ionice failed"

        # For realtime priority, try SCHED_FIFO
        if priority == "realtime" and is_admin():
            ok, _ = run_cmd(["chrt", "-f", "-p", "50", str(pid)])
            if ok:
                results["scheduler"] = "SCHED_FIFO priority 50"

        return results

    def throttle_background(self, protected_pid: int) -> list[dict]:
        """Lower priority of non-essential background processes."""
        throttle_targets = {
            "tracker-miner", "tracker-extract", "baloo_file",  # File indexers
            "packagekitd", "unattended-upgrade",               # Package managers
            "snapd",                                            # Snap daemon
            "evolution-calendar",                               # Calendar sync
            "zeitgeist",                                        # Activity logging
        }

        results = []
        try:
            for entry in Path("/proc").iterdir():
                if not entry.name.isdigit():
                    continue
                pid = int(entry.name)
                if pid == protected_pid:
                    continue
                try:
                    comm = (entry / "comm").read_text().strip()
                    if any(t in comm for t in throttle_targets):
                        run_cmd(["renice", "19", "-p", str(pid)])
                        run_cmd(["ionice", "-c", "3", "-p", str(pid)])
                        results.append({"process": comm, "pid": pid, "action": "throttled"})
                except (PermissionError, FileNotFoundError):
                    continue
        except Exception as e:
            logger.debug("throttle_background error: %s", e)

        return results

    # ── GameMode ─────────────────────────────────────────────────────────

    def _apply_gamemode(self, result: OptimizationResult, dry_run: bool):
        """Check for and enable Feral GameMode if available.

        GameMode is a daemon that applies a set of optimizations
        when games are running: CPU governor, nice values, GPU clocks.
        We integrate with it rather than fighting it.
        """
        if dry_run:
            result.skip("gamemode", "dry_run")
            return

        # Check if gamemode is installed
        ok, _ = run_cmd(["which", "gamemoded"])
        if not ok:
            result.skip("gamemode", "not installed (optional: apt install gamemode)")
            return

        # Check if it's running
        ok, output = run_cmd(["gamemoded", "-s"])
        if ok:
            if "active" in output.lower():
                result.skip("gamemode", "already active")
            else:
                result.ok("gamemode=available (will activate when games detected)")
        else:
            result.skip("gamemode", "gamemoded not responding")

    # ── I/O Scheduler ────────────────────────────────────────────────────

    def _apply_io_scheduler(self, result: OptimizationResult, dry_run: bool):
        """Set I/O scheduler to 'none' for NVMe or 'mq-deadline' for SATA.

        NVMe drives have their own scheduling and don't benefit from
        kernel I/O schedulers. Setting 'none' removes overhead.
        For SATA/SAS, 'mq-deadline' provides lowest latency.
        """
        if not is_admin():
            result.skip("io_scheduler", "not root")
            return
        if dry_run:
            result.skip("io_scheduler", "dry_run")
            return

        import glob as g
        changed = 0

        for sched_path in g.glob("/sys/block/*/queue/scheduler"):
            dev = sched_path.split("/")[3]

            # Skip loop and ram devices
            if dev.startswith(("loop", "ram", "dm-")):
                continue

            orig = sysfs_read(sched_path)
            if not orig:
                continue

            # Extract current scheduler (marked with [brackets])
            current = None
            for part in orig.split():
                if part.startswith("[") and part.endswith("]"):
                    current = part[1:-1]
                    break

            self._rollback.save_original(f"io_sched_{dev}", current)

            # NVMe → none, everything else → mq-deadline
            target = "none" if dev.startswith("nvme") else "mq-deadline"

            if current == target:
                continue

            if sysfs_write(sched_path, target):
                changed += 1
                logger.info("Set %s scheduler to %s (was %s)", dev, target, current)

        if changed:
            result.ok(f"io_scheduler={changed} devices tuned")
        else:
            result.skip("io_scheduler", "all devices already optimal")

    def _restore_io_scheduler(self, result: OptimizationResult):
        import glob as g
        restored = 0
        for sched_path in g.glob("/sys/block/*/queue/scheduler"):
            dev = sched_path.split("/")[3]
            orig = self._rollback.get_original(f"io_sched_{dev}")
            if orig and sysfs_write(sched_path, orig):
                restored += 1
        if restored:
            result.ok(f"io_scheduler restored ({restored} devices)")

    # ── Transparent Huge Pages ───────────────────────────────────────────

    def _apply_transparent_hugepages(self, result: OptimizationResult, dry_run: bool):
        """Set THP to 'madvise' for game-friendly memory management.

        'always' can cause latency spikes from compaction.
        'madvise' lets applications opt-in to huge pages.
        Games and GPU drivers that benefit will use madvise().
        """
        if not is_admin():
            result.skip("thp", "not root")
            return
        if dry_run:
            result.skip("thp", "dry_run")
            return

        thp_path = "/sys/kernel/mm/transparent_hugepage/enabled"
        if not Path(thp_path).exists():
            result.skip("thp", "THP not available")
            return

        orig = sysfs_read(thp_path)
        # Extract current setting from [brackets]
        current = None
        if orig:
            for part in orig.split():
                if part.startswith("[") and part.endswith("]"):
                    current = part[1:-1]
        self._rollback.save_original("thp", current)

        if current == "madvise":
            result.skip("thp", "already madvise")
            return

        if sysfs_write(thp_path, "madvise"):
            result.ok(f"thp=madvise (was {current})")
        else:
            result.fail("thp", "cannot set THP mode")

    def _restore_transparent_hugepages(self, result: OptimizationResult):
        orig = self._rollback.get_original("thp")
        if orig:
            thp_path = "/sys/kernel/mm/transparent_hugepage/enabled"
            if sysfs_write(thp_path, orig):
                result.ok(f"thp restored to {orig}")

    # ── NMI Watchdog ─────────────────────────────────────────────────────

    def _apply_watchdog(self, result: OptimizationResult, dry_run: bool):
        """Disable NMI watchdog to free up a performance counter.

        The NMI watchdog uses a hardware performance counter on every core
        to detect lockups. Disabling it frees that counter for profiling
        and slightly reduces interrupt overhead.
        """
        if not is_admin():
            result.skip("watchdog", "not root")
            return
        if dry_run:
            result.skip("watchdog", "dry_run")
            return

        path = "/proc/sys/kernel/nmi_watchdog"
        if not Path(path).exists():
            result.skip("watchdog", "not available")
            return

        orig = sysfs_read(path)
        self._rollback.save_original("nmi_watchdog", orig)

        if orig == "0":
            result.skip("watchdog", "already disabled")
            return

        if sysfs_write(path, "0"):
            result.ok("watchdog=disabled (frees 1 perf counter per core)")
        else:
            result.fail("watchdog")

    def _restore_watchdog(self, result: OptimizationResult):
        orig = self._rollback.get_original("nmi_watchdog")
        if orig is not None:
            if sysfs_write("/proc/sys/kernel/nmi_watchdog", orig):
                result.ok("nmi_watchdog restored")

    # ── Status ───────────────────────────────────────────────────────────

    def get_status(self) -> dict:
        status = {"module": "linux_process", "optimized": False, "settings": {}}

        if not IS_LINUX:
            return status

        # THP
        thp = sysfs_read("/sys/kernel/mm/transparent_hugepage/enabled")
        if thp:
            for part in thp.split():
                if part.startswith("["):
                    status["settings"]["thp"] = part[1:-1]

        # NMI watchdog
        wd = sysfs_read("/proc/sys/kernel/nmi_watchdog")
        if wd:
            status["settings"]["nmi_watchdog"] = bool(int(wd))

        # GameMode
        ok, output = run_cmd(["gamemoded", "-s"])
        if ok:
            status["settings"]["gamemode"] = "active" in output.lower()

        status["optimized"] = self._rollback.has_saved_state
        return status
