from __future__ import annotations

import os
import platform
import socket
import time
import uuid

import psutil

from .winops import IS_WINDOWS

_last_net = {"t": time.time(), "sent": 0, "recv": 0}
SYSTEM_NAMES = {"system", "registry", "smss.exe", "csrss.exe", "wininit.exe", "services.exe", "lsass.exe", "svchost.exe", "dwm.exe",
                "winlogon.exe", "fontdrvhost.exe", "conhost.exe", "runtimebroker.exe", "searchindexer.exe", "taskhostw.exe",
                "sihost.exe", "ctfmon.exe", "spoolsv.exe", "memcompression", "audiodg.exe", "wmiprvse.exe", "dllhost.exe",
                "searchhost.exe", "startmenuexperiencehost.exe", "shellexperiencehost.exe", "textinputhost.exe", "python.exe",
                "systemsettings.exe", "applicationframehost.exe", "securityhealthsystray.exe", "msedgewebview2.exe"}


def local_ip(server_host: str = "8.8.8.8") -> str:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect((server_host, 9))
        return s.getsockname()[0]
    except OSError:
        return "127.0.0.1"
    finally:
        s.close()


def mac_address() -> str:
    n = uuid.getnode()
    return ":".join(f"{(n >> i) & 0xFF:02X}" for i in range(40, -8, -8))


def os_name() -> str:
    if IS_WINDOWS:
        try:
            return f"Windows {platform.release()} {platform.win32_edition()} (build {platform.version()})"
        except Exception:
            pass
    return platform.platform()


def metrics() -> dict:
    vm = psutil.virtual_memory()
    try:
        du = psutil.disk_usage("C:\\" if IS_WINDOWS else "/")
        disk = {"disk_total": du.total, "disk_used": du.used, "disk_percent": du.percent}
    except Exception:
        disk = {}
    net = psutil.net_io_counters()
    now = time.time()
    dt = max(now - _last_net["t"], 0.001)
    up = max(0, (net.bytes_sent - _last_net["sent"]) / dt) if _last_net["sent"] else 0
    down = max(0, (net.bytes_recv - _last_net["recv"]) / dt) if _last_net["recv"] else 0
    _last_net.update(t=now, sent=net.bytes_sent, recv=net.bytes_recv)
    return {"cpu": psutil.cpu_percent(interval=None), "ram_percent": vm.percent, "ram_used": vm.used, "ram_total": vm.total,
            **disk, "net_up_bps": int(up), "net_down_bps": int(down), "uptime_s": int(now - psutil.boot_time())}


def running_apps(limit: int = 30) -> list[dict]:
    """Aplikasi yang sedang berjalan (nama proses unik milik pengguna biasa), tanpa proses sistem."""
    agg: dict[str, dict] = {}
    for p in psutil.process_iter(["name", "username", "memory_info"]):
        try:
            name = (p.info["name"] or "").lower()
            user = (p.info["username"] or "").lower()
            if not name or name in SYSTEM_NAMES or user.endswith("system") or user.endswith("service") or "\\" not in user and IS_WINDOWS:
                continue
            a = agg.setdefault(name, {"name": name, "count": 0, "mem_mb": 0})
            a["count"] += 1
            a["mem_mb"] += round((p.info["memory_info"].rss if p.info["memory_info"] else 0) / 1048576)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
    return sorted(agg.values(), key=lambda x: -x["mem_mb"])[:limit]


def active_user() -> str:
    try:
        us = psutil.users()
        if us:
            return us[0].name
    except Exception:
        pass
    return os.environ.get("USERNAME") or os.environ.get("USER") or ""


def installed_applications() -> list[dict]:
    if not IS_WINDOWS:
        return []
    import winreg
    out, seen = [], set()
    roots = [(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", winreg.KEY_WOW64_64KEY),
             (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", winreg.KEY_WOW64_32KEY)]
    for hive, path, flag in roots:
        try:
            with winreg.OpenKey(hive, path, 0, winreg.KEY_READ | flag) as k:
                for i in range(winreg.QueryInfoKey(k)[0]):
                    try:
                        with winreg.OpenKey(k, winreg.EnumKey(k, i)) as sk:
                            def val(n):
                                try:
                                    return str(winreg.QueryValueEx(sk, n)[0])
                                except OSError:
                                    return ""
                            name = val("DisplayName")
                            if not name or val("SystemComponent") == "1" or (name, val("DisplayVersion")) in seen:
                                continue
                            seen.add((name, val("DisplayVersion")))
                            out.append({"name": name, "version": val("DisplayVersion"), "publisher": val("Publisher"), "install_date": val("InstallDate")})
                    except OSError:
                        continue
        except OSError:
            continue
    return out
