"""Reversibly disable Windows telemetry / data collection.

Targets, in order of leak severity:

  • DiagTrack service     — primary telemetry pipeline ("Connected User
                            Experiences and Telemetry"). Phones home every
                            few minutes.
  • dmwappushservice      — WAP Push routing (DSMS), historically tied to
                            DiagTrack. Disabled when present.
  • AllowTelemetry (HKLM) — DataCollection policy. 0 = lowest tier.
  • Activity History      — EnableActivityFeed / Upload / PublishUserActivities.
  • Advertising ID        — HKCU\\…\\AdvertisingInfo\\Enabled.
  • Cortana Bing search   — HKCU\\…\\Search\\BingSearchEnabled.
  • Scheduled tasks       — Compatibility Appraiser + CEIP Consolidator
                            + UsbCeip.

Every change is snapshotted before mutation; ``restore_telemetry`` rolls back.
"""
from __future__ import annotations

import json
import logging
import subprocess
import winreg
from dataclasses import dataclass, field

from .firewall import is_admin

log = logging.getLogger(__name__)

_SERVICES = ("DiagTrack", "dmwappushservice")

_SCHED_TASKS = (
    r"\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser",
    r"\Microsoft\Windows\Customer Experience Improvement Program\Consolidator",
    r"\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip",
)

# (hive, path, value_name, dword_when_killed)
_REG_KILLS = (
    (winreg.HKEY_LOCAL_MACHINE,
     r"SOFTWARE\Policies\Microsoft\Windows\DataCollection",
     "AllowTelemetry", 0),
    (winreg.HKEY_LOCAL_MACHINE,
     r"SOFTWARE\Policies\Microsoft\Windows\System",
     "EnableActivityFeed", 0),
    (winreg.HKEY_LOCAL_MACHINE,
     r"SOFTWARE\Policies\Microsoft\Windows\System",
     "PublishUserActivities", 0),
    (winreg.HKEY_LOCAL_MACHINE,
     r"SOFTWARE\Policies\Microsoft\Windows\System",
     "UploadUserActivities", 0),
    (winreg.HKEY_LOCAL_MACHINE,
     r"SOFTWARE\Policies\Microsoft\Windows\CloudContent",
     "DisableWindowsConsumerFeatures", 1),
    (winreg.HKEY_CURRENT_USER,
     r"Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo",
     "Enabled", 0),
    (winreg.HKEY_CURRENT_USER,
     r"Software\Microsoft\Windows\CurrentVersion\Search",
     "BingSearchEnabled", 0),
    (winreg.HKEY_CURRENT_USER,
     r"Software\Microsoft\Windows\CurrentVersion\Search",
     "CortanaConsent", 0),
)


@dataclass
class TelemetrySnapshot:
    service_state: dict[str, str] = field(default_factory=dict)   # name -> 'auto'|'manual'|'disabled'|'absent'
    sched_tasks_state: dict[str, bool] = field(default_factory=dict)  # task -> was_enabled
    reg_values: list[dict] = field(default_factory=list)


def _run(args: list[str], timeout: float = 12.0) -> tuple[int, str, str]:
    try:
        r = subprocess.run(
            args, capture_output=True, text=True, timeout=timeout,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        return r.returncode, r.stdout or "", r.stderr or ""
    except Exception as e:
        return 1, "", str(e)


# ── service helpers ──────────────────────────────────────────────────────────

def _service_start_type(name: str) -> str:
    code, out, _ = _run(["sc", "qc", name])
    if code != 0:
        return "absent"
    for ln in out.splitlines():
        s = ln.strip().lower()
        if "start_type" in s:
            if "2" in s or "auto" in s:
                return "auto"
            if "3" in s or "demand" in s or "manual" in s:
                return "manual"
            if "4" in s or "disabled" in s:
                return "disabled"
    return "absent"


def _set_service_disabled(name: str) -> bool:
    _run(["sc", "stop", name], timeout=15)
    code, _, _ = _run(["sc", "config", name, "start=", "disabled"])
    return code == 0


def _set_service_start(name: str, want: str) -> bool:
    start_value = {"auto": "auto", "manual": "demand", "disabled": "disabled"}.get(want)
    if not start_value:
        return False
    _run(["sc", "stop", name], timeout=10)
    code, _, _ = _run(["sc", "config", name, "start=", start_value])
    if want == "auto":
        _run(["sc", "start", name], timeout=15)
    return code == 0


# ── scheduled-task helpers ──────────────────────────────────────────────────

def _task_is_enabled(name: str) -> bool | None:
    code, out, _ = _run(["schtasks", "/Query", "/TN", name])
    if code != 0:
        return None
    for ln in out.splitlines():
        s = ln.strip().lower()
        if s.startswith("status:") or "ready" in s or "disabled" in s:
            if "disabled" in s:
                return False
            if "ready" in s or "running" in s:
                return True
    return True  # assume ready when query succeeded


def _set_task_state(name: str, enable: bool) -> bool:
    code, _, _ = _run([
        "schtasks", "/Change", "/TN", name,
        "/Enable" if enable else "/Disable",
    ])
    return code == 0


# ── registry helpers ────────────────────────────────────────────────────────

def _read_reg_dword(hive: int, path: str, name: str) -> tuple[bool, int | None]:
    """Returns (present, value)."""
    try:
        with winreg.OpenKey(hive, path, 0, winreg.KEY_QUERY_VALUE) as key:
            v, _ = winreg.QueryValueEx(key, name)
            return True, int(v)
    except OSError:
        return False, None


def _write_reg_dword(hive: int, path: str, name: str, value: int) -> bool:
    try:
        with winreg.CreateKeyEx(hive, path, 0, winreg.KEY_SET_VALUE) as key:
            winreg.SetValueEx(key, name, 0, winreg.REG_DWORD, value)
        return True
    except OSError:
        return False


def _delete_reg_value(hive: int, path: str, name: str) -> None:
    try:
        with winreg.OpenKey(hive, path, 0, winreg.KEY_SET_VALUE) as key:
            winreg.DeleteValue(key, name)
    except OSError:
        pass


# ── public API ──────────────────────────────────────────────────────────────

def telemetry_status() -> dict[str, str]:
    """Read-only snapshot for the audit panel."""
    out: dict[str, str] = {}
    for svc in _SERVICES:
        out[f"svc.{svc}"] = _service_start_type(svc)
    pres, val = _read_reg_dword(
        winreg.HKEY_LOCAL_MACHINE,
        r"SOFTWARE\Policies\Microsoft\Windows\DataCollection",
        "AllowTelemetry",
    )
    out["AllowTelemetry"] = "0 (killed)" if pres and val == 0 else (
        f"{val}" if pres else "unset"
    )
    pres, val = _read_reg_dword(
        winreg.HKEY_CURRENT_USER,
        r"Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo",
        "Enabled",
    )
    out["AdvertisingID"] = "off" if pres and val == 0 else (
        "on" if pres and val == 1 else "unset"
    )
    return out


def engage_telemetry_kill() -> tuple[TelemetrySnapshot | None, list[str]]:
    logs: list[str] = []
    if not is_admin():
        return None, ["Telemetry kill skipped — needs Administrator."]
    snap = TelemetrySnapshot()

    for svc in _SERVICES:
        st = _service_start_type(svc)
        snap.service_state[svc] = st
        if st == "absent":
            continue
        if _set_service_disabled(svc):
            logs.append(f"Service '{svc}' stopped + disabled (was {st}).")
        else:
            logs.append(f"Service '{svc}' could not be disabled.")

    for task in _SCHED_TASKS:
        was = _task_is_enabled(task)
        if was is None:
            continue
        snap.sched_tasks_state[task] = was
        if was:
            if _set_task_state(task, enable=False):
                logs.append(f"Scheduled task disabled: {task}")

    for hive, path, name, kill_val in _REG_KILLS:
        present, prev = _read_reg_dword(hive, path, name)
        snap.reg_values.append({
            "hive": "HKLM" if hive == winreg.HKEY_LOCAL_MACHINE else "HKCU",
            "path": path,
            "name": name,
            "present": present,
            "prev": prev,
        })
        if _write_reg_dword(hive, path, name, kill_val):
            logs.append(f"reg: {('HKLM' if hive == winreg.HKEY_LOCAL_MACHINE else 'HKCU')}\\{path}\\{name} → {kill_val}")
        else:
            logs.append(f"reg write failed: {path}\\{name}")

    return snap, logs


def restore_telemetry(snap: TelemetrySnapshot | None) -> list[str]:
    if snap is None or not is_admin():
        return []
    logs: list[str] = []

    for svc, prev in snap.service_state.items():
        if prev == "absent":
            continue
        if _set_service_start(svc, prev):
            logs.append(f"Service '{svc}' restored → {prev}.")

    for task, was in snap.sched_tasks_state.items():
        if was:
            _set_task_state(task, enable=True)
            logs.append(f"Scheduled task re-enabled: {task}")

    for entry in snap.reg_values:
        hive = winreg.HKEY_LOCAL_MACHINE if entry["hive"] == "HKLM" else winreg.HKEY_CURRENT_USER
        path = entry["path"]
        name = entry["name"]
        if entry["present"]:
            _write_reg_dword(hive, path, name, int(entry["prev"]))
            logs.append(f"reg restore: {entry['hive']}\\{path}\\{name} ← {entry['prev']}")
        else:
            _delete_reg_value(hive, path, name)
            logs.append(f"reg removed (was absent): {entry['hive']}\\{path}\\{name}")
    return logs


def snapshot_to_json(snap: TelemetrySnapshot | None) -> str:
    if snap is None:
        return ""
    return json.dumps({
        "services": snap.service_state,
        "tasks": snap.sched_tasks_state,
        "reg": snap.reg_values,
    })


def snapshot_from_json(raw: str) -> TelemetrySnapshot | None:
    if not raw:
        return None
    try:
        d = json.loads(raw)
        return TelemetrySnapshot(
            service_state=dict(d.get("services") or {}),
            sched_tasks_state={k: bool(v) for k, v in (d.get("tasks") or {}).items()},
            reg_values=list(d.get("reg") or []),
        )
    except Exception:
        return None
