"""Windows process priority, CPU affinity, and thread optimization."""
from __future__ import annotations

import ctypes
import logging
import os
from typing import Optional

from mesh_boost.optimizer.base import (
    IS_WINDOWS, OptimizationResult, RollbackManager,
    is_admin, reg_read, reg_write, require_admin, run_cmd, run_powershell,
)

logger = logging.getLogger(__name__)

# Windows priority classes
IDLE_PRIORITY_CLASS = 0x40
BELOW_NORMAL_PRIORITY_CLASS = 0x4000
NORMAL_PRIORITY_CLASS = 0x20
ABOVE_NORMAL_PRIORITY_CLASS = 0x8000
HIGH_PRIORITY_CLASS = 0x80
REALTIME_PRIORITY_CLASS = 0x100

# Process access rights
PROCESS_SET_INFORMATION = 0x0200
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_ALL_ACCESS = 0x1F0FFF

# Thread priority levels
THREAD_PRIORITY_ABOVE_NORMAL = 1
THREAD_PRIORITY_HIGHEST = 2
THREAD_PRIORITY_TIME_CRITICAL = 15

# Memory priority
MEMORY_PRIORITY_INFORMATION = 1


class ProcessOptimizer:
    """Optimize process priority, affinity, and memory settings."""

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

    def apply(self, dry_run: bool = False) -> OptimizationResult:
        """Apply system-wide process optimizations."""
        result = OptimizationResult("process")

        if not IS_WINDOWS:
            result.skip("all", "not Windows")
            return result

        self._apply_large_pages(result, dry_run)
        self._apply_priority_boost(result, dry_run)
        self._apply_background_throttle(result, dry_run)

        return result

    def restore(self) -> OptimizationResult:
        """Restore process settings."""
        result = OptimizationResult("process_restore")
        if not IS_WINDOWS:
            return result
        self._restore_background_throttle(result)
        self._rollback.clear()
        return result

    def boost_process(self, pid: int, priority: str = "high",
                      affinity_mask: Optional[int] = None) -> dict:
        """Boost a specific process (e.g., a detected game).

        Args:
            pid: Process ID to boost
            priority: "high", "above_normal", or "realtime" (admin only)
            affinity_mask: CPU affinity bitmask (None = all cores)

        Returns:
            Dict with results of each optimization applied
        """
        if not IS_WINDOWS:
            return {"error": "not Windows"}

        results = {}

        try:
            kernel32 = ctypes.WinDLL("kernel32")

            # Open process
            access = PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION
            handle = kernel32.OpenProcess(access, False, pid)
            if not handle:
                return {"error": f"Cannot open process {pid} (access denied?)"}

            try:
                # Set priority
                priority_map = {
                    "above_normal": ABOVE_NORMAL_PRIORITY_CLASS,
                    "high": HIGH_PRIORITY_CLASS,
                    "realtime": REALTIME_PRIORITY_CLASS,
                }
                pclass = priority_map.get(priority, HIGH_PRIORITY_CLASS)

                # Realtime requires admin
                if pclass == REALTIME_PRIORITY_CLASS and not is_admin():
                    pclass = HIGH_PRIORITY_CLASS
                    results["priority_note"] = "Realtime requires admin, using High"

                if kernel32.SetPriorityClass(handle, pclass):
                    results["priority"] = priority
                else:
                    results["priority_error"] = "SetPriorityClass failed"

                # Set affinity
                if affinity_mask is not None:
                    if kernel32.SetProcessAffinityMask(handle, affinity_mask):
                        results["affinity"] = hex(affinity_mask)
                    else:
                        results["affinity_error"] = "SetProcessAffinityMask failed"

                # Disable priority boost (prevents Windows from dynamically lowering)
                # Actually for games we WANT priority boost disabled — it prevents
                # Windows from temporarily boosting then dropping priority
                kernel32.SetProcessPriorityBoost(handle, False)
                results["priority_boost"] = "enabled"

            finally:
                kernel32.CloseHandle(handle)

        except Exception as e:
            results["error"] = str(e)

        return results

    def set_game_affinity(self, pid: int, core_count: int) -> dict:
        """Set game process to use performance cores only.

        For Intel hybrid CPUs (12th gen+), restricts game to P-cores.
        For other CPUs, uses all cores.

        Args:
            pid: Game process ID
            core_count: Total logical core count
        """
        if not IS_WINDOWS:
            return {"error": "not Windows"}

        # For now, use all cores (affinity = all bits set)
        # TODO: detect P-cores vs E-cores on Intel hybrid CPUs
        mask = (1 << core_count) - 1
        return self.boost_process(pid, priority="high", affinity_mask=mask)

    def throttle_background(self, game_pid: int) -> list[dict]:
        """Lower priority of non-essential background processes.

        Reduces CPU contention for the game by lowering priority of
        known resource-hogging background processes.
        """
        if not IS_WINDOWS:
            return []

        throttle_targets = {
            "searchindexer.exe", "searchprotocolhost.exe",
            "compattelrunner.exe", "devicecensus.exe",
            "msmpeng.exe",  # Windows Defender (careful — don't kill it)
            "onedrive.exe", "teams.exe",
            "chrome.exe", "msedge.exe", "firefox.exe",
            "discord.exe", "spotify.exe",
            "windowsterminal.exe", "code.exe",
        }

        results = []
        try:
            import psutil
            for proc in psutil.process_iter(['pid', 'name']):
                try:
                    pname = proc.info['name']
                    if not pname:
                        continue
                    if pname.lower() in throttle_targets and proc.info['pid'] != game_pid:
                        proc.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
                        results.append({
                            "process": pname,
                            "pid": proc.info['pid'],
                            "action": "throttled",
                        })
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
        except ImportError:
            logger.warning("psutil not available for background throttling")

        return results

    def restore_background(self) -> list[dict]:
        """Restore background process priorities to normal."""
        if not IS_WINDOWS:
            return []

        results = []
        try:
            import psutil
            for proc in psutil.process_iter(['pid', 'name']):
                try:
                    if proc.nice() == psutil.BELOW_NORMAL_PRIORITY_CLASS:
                        proc.nice(psutil.NORMAL_PRIORITY_CLASS)
                        results.append({
                            "process": proc.info['name'],
                            "pid": proc.info['pid'],
                            "action": "restored",
                        })
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
        except ImportError:
            pass

        return results

    # ── System-wide optimizations ────────────────────────────────────────

    def _apply_large_pages(self, result: OptimizationResult, dry_run: bool):
        """Enable Large Pages privilege for the current user.

        Large Pages (2MB instead of 4KB) reduce TLB misses for games
        with large memory footprints. Requires SeLockMemoryPrivilege.
        """
        if not require_admin("large_pages"):
            result.skip("large_pages", "not admin")
            return

        if dry_run:
            result.skip("large_pages", "dry_run")
            return

        # Grant SeLockMemoryPrivilege to current user
        ok, output = run_powershell(
            "$sid = (New-Object System.Security.Principal.NTAccount($env:USERNAME))"
            ".Translate([System.Security.Principal.SecurityIdentifier]).Value; "
            "$tmp = [System.IO.Path]::GetTempFileName(); "
            "secedit /export /cfg $tmp /quiet; "
            "$cfg = Get-Content $tmp; "
            "$line = $cfg | Where-Object { $_ -match 'SeLockMemoryPrivilege' }; "
            "if ($line -and $line -notmatch $sid) { "
            "  $newline = $line + ',*' + $sid; "
            "  $cfg = $cfg -replace [regex]::Escape($line), $newline; "
            "  $cfg | Set-Content $tmp; "
            "  secedit /configure /db secedit.sdb /cfg $tmp /quiet; "
            "  'granted' "
            "} elseif ($line -match $sid) { 'already_granted' } "
            "else { 'no_policy_line' }"
        )

        if ok:
            if "already_granted" in output:
                result.skip("large_pages", "already granted")
            elif "granted" in output:
                result.ok("large_pages=SeLockMemoryPrivilege granted")
            else:
                result.skip("large_pages", output[:80])
        else:
            result.fail("large_pages", "secedit failed")

    def _apply_priority_boost(self, result: OptimizationResult, dry_run: bool):
        """Enable foreground priority boost via registry."""
        if dry_run:
            result.skip("priority_boost", "dry_run")
            return

        # This is already handled by power.py's Win32PrioritySeparation
        # but we add the foreground lock timeout to prevent mouse-away
        # from stealing foreground from games
        key = r"HKCU\Control Panel\Desktop"
        orig = reg_read(key, "ForegroundLockTimeout")
        self._rollback.save_original("fg_lock_timeout", orig)

        # Set to 0 — allows games to grab foreground immediately
        if reg_write(key, "ForegroundLockTimeout", 0):
            result.ok("priority_boost=ForegroundLockTimeout=0")
        else:
            result.fail("priority_boost")

    def _apply_background_throttle(self, result: OptimizationResult, dry_run: bool):
        """Configure Windows to aggressively throttle background apps."""
        if dry_run:
            result.skip("background_throttle", "dry_run")
            return

        key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\BackgroundAccessApplications"
        orig = reg_read(key, "GlobalUserDisabled")
        self._rollback.save_original("bg_apps_disabled", orig)

        if reg_write(key, "GlobalUserDisabled", 1):
            result.ok("background_throttle=GlobalUserDisabled (background apps restricted)")
        else:
            result.skip("background_throttle", "registry write failed (may not exist on this Windows version)")

    def _restore_background_throttle(self, result: OptimizationResult):
        orig = self._rollback.get_original("bg_apps_disabled")
        if orig is not None:
            key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\BackgroundAccessApplications"
            if reg_write(key, "GlobalUserDisabled", orig):
                result.ok("background_throttle restored")

        orig = self._rollback.get_original("fg_lock_timeout")
        if orig is not None:
            key = r"HKCU\Control Panel\Desktop"
            if reg_write(key, "ForegroundLockTimeout", orig):
                result.ok("ForegroundLockTimeout restored")

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

    def get_status(self) -> dict:
        """Return current process optimization status."""
        status = {"module": "process", "optimized": False, "settings": {}}

        if not IS_WINDOWS:
            return status

        # Check foreground lock timeout
        key = r"HKCU\Control Panel\Desktop"
        val = reg_read(key, "ForegroundLockTimeout")
        if val is not None:
            status["settings"]["foreground_lock_timeout"] = val

        # Check background apps
        key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\BackgroundAccessApplications"
        val = reg_read(key, "GlobalUserDisabled")
        if val is not None:
            status["settings"]["background_apps_disabled"] = bool(val)

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