"""Lapisan eksekusi perintah Windows.

Dalam mode dry_run (atau bukan Windows) perintah TIDAK dijalankan, hanya dicatat -> dipakai untuk pengujian & pratinjau.
"""
from __future__ import annotations

import logging
import os
import subprocess
import threading
from pathlib import Path

log = logging.getLogger("agent.ops")
IS_WINDOWS = os.name == "nt"
CREATE_NO_WINDOW = 0x08000000 if IS_WINDOWS else 0

if IS_WINDOWS:
    import winreg

HKLM_ROOT = "HKLM"


class Ops:
    def __init__(self, dry_run: bool = False):
        self.dry_run = dry_run or not IS_WINDOWS
        self.commands: list[list[str]] = []
        self.registry: dict[tuple[str, str], object] = {}  # tiruan registry saat dry_run
        self.files: dict[str, str] = {}
        self._lock = threading.Lock()

    # ---- proses ------------------------------------------------------------------------------
    def run(self, args: list[str], timeout: int = 60) -> tuple[int, str]:
        with self._lock:
            self.commands.append(list(args))
        log.info("%s %s", "[dry-run]" if self.dry_run else "[exec]", " ".join(args)[:300])
        if self.dry_run:
            return 0, "dry-run"
        p = subprocess.run(args, capture_output=True, text=True, timeout=timeout, creationflags=CREATE_NO_WINDOW,
                           encoding="utf-8", errors="replace")
        return p.returncode, (p.stdout + p.stderr).strip()

    def powershell(self, script: str, timeout: int = 90) -> tuple[int, str]:
        return self.run(["powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], timeout)

    # ---- registry ------------------------------------------------------------------------------
    def reg_set(self, key: str, name: str, value, kind: str = "dword") -> None:
        if self.dry_run:
            self.registry[(key, name)] = value
            return
        k = winreg.CreateKeyEx(winreg.HKEY_LOCAL_MACHINE, key, 0, winreg.KEY_SET_VALUE | winreg.KEY_WOW64_64KEY)
        with k:
            winreg.SetValueEx(k, name, 0, winreg.REG_DWORD if kind == "dword" else winreg.REG_SZ, value)

    def reg_delete_tree(self, key: str) -> None:
        if self.dry_run:
            for kk in [k for k in self.registry if k[0] == key or k[0].startswith(key + "\\")]:
                self.registry.pop(kk, None)
            return
        try:
            self._delete_tree(key)
        except FileNotFoundError:
            pass

    def _delete_tree(self, key: str) -> None:
        with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key, 0, winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY) as k:
            while True:
                try:
                    sub = winreg.EnumKey(k, 0)
                except OSError:
                    break
                self._delete_tree(key + "\\" + sub)
        winreg.DeleteKeyEx(winreg.HKEY_LOCAL_MACHINE, key, winreg.KEY_WOW64_64KEY, 0)

    def reg_delete_value(self, key: str, name: str) -> None:
        if self.dry_run:
            self.registry.pop((key, name), None)
            return
        try:
            with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key, 0, winreg.KEY_SET_VALUE | winreg.KEY_WOW64_64KEY) as k:
                winreg.DeleteValue(k, name)
        except FileNotFoundError:
            pass

    def reg_set_list(self, key: str, values: list[str]) -> None:
        """Tulis daftar (URLBlocklist dst.) sebagai subkey dengan nilai bernomor '1','2',..."""
        self.reg_delete_tree(key)
        for i, v in enumerate(values, 1):
            self.reg_set(key, str(i), v, "sz")

    # ---- berkas ---------------------------------------------------------------------------------
    def read_text(self, path: str) -> str:
        if self.dry_run and path in self.files:
            return self.files[path]
        p = Path(path)
        return p.read_text(encoding="utf-8", errors="replace") if p.exists() else ""

    def write_text(self, path: str, text: str) -> None:
        if self.dry_run:
            self.files[path] = text
            return
        Path(path).write_text(text, encoding="utf-8")
