"""Device fingerprint audit and OS-level hardening helpers."""
from __future__ import annotations

import logging
import random
import re
import string
import subprocess
import winreg
from dataclasses import dataclass, field

from .firewall import is_admin
from .mac_spoof import list_nics
from .win_compat import probe as _win_probe

log = logging.getLogger(__name__)

_WEBRTC_CHROME = r"SOFTWARE\Policies\Google\Chrome"
_WEBRTC_EDGE = r"SOFTWARE\Policies\Microsoft\Edge"
# Legacy DWORD key — value 3 = disable_non_proxied_udp (value 2 was wrong: public+private only)
_WEBRTC_VALUE = "DefaultWebRtcIpHandlingPolicy"
_WEBRTC_DISABLE = 3  # disable_non_proxied_udp (correct Chrome/Edge DWORD)
# Modern REG_SZ key required by Chrome 114+ Group Policy
_WEBRTC_VALUE_STR = "WebRtcIPHandling"
_WEBRTC_DISABLE_STR = "disable_non_proxied_udp"


@dataclass
class FingerprintAudit:
    lines: list[str] = field(default_factory=list)
    hostname: str = ""
    machine_guid: str = ""
    username: str = ""
    macs: list[str] = field(default_factory=list)


def _run_ps(script: str, timeout: float = 15.0) -> str:
    try:
        r = subprocess.run(
            ["powershell", "-NoProfile", "-Command", script],
            capture_output=True,
            text=True,
            timeout=timeout,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        return (r.stdout or "").strip()
    except Exception as e:
        log.debug("fingerprint ps: %s", e)
        return ""


def get_computer_name() -> str:
    try:
        import os
        return os.environ.get("COMPUTERNAME", "") or ""
    except Exception:
        return ""


def get_machine_guid() -> str:
    try:
        with winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE,
            r"SOFTWARE\Microsoft\Cryptography",
        ) as key:
            val, _ = winreg.QueryValueEx(key, "MachineGuid")
            return str(val)
    except OSError:
        return ""


def random_hostname(prefix: str = "PC") -> str:
    suffix = "".join(random.choices(string.ascii_uppercase + string.digits, k=7))
    return f"{prefix}-{suffix}"[:15]


def set_computer_name(name: str) -> tuple[bool, str]:
    """Set NetBIOS / computer name (Admin). Reboot may be required for all apps."""
    if not is_admin():
        return False, "Administrator required to change computer name."
    name = re.sub(r"[^A-Za-z0-9\-]", "", name)[:15]
    if len(name) < 1:
        return False, "Invalid hostname."
    code = subprocess.run(
        ["powershell", "-NoProfile", "-Command",
         f'Rename-Computer -NewName "{name}" -Force -ErrorAction Stop'],
        capture_output=True,
        text=True,
        timeout=30,
        creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
    ).returncode
    if code != 0:
        # NetBIOS name via WMI (often works without full rename)
        wmi = (
            f'$n = Get-WmiObject Win32_ComputerSystem; '
            f'$r = $n.Rename("{name}"); if ($r.ReturnValue -ne 0) {{ exit $r.ReturnValue }}'
        )
        code = subprocess.run(
            ["powershell", "-NoProfile", "-Command", wmi],
            capture_output=True,
            text=True,
            timeout=30,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        ).returncode
    if code == 0:
        return True, f"Computer name set to {name} (some apps need reconnect/reboot)."
    return False, "Could not change computer name."


def disable_ipv6_on_adapters() -> tuple[list[str], list[str]]:
    """Disable IPv6 binding on up physical adapters. Returns (adapter names, log lines)."""
    if not is_admin():
        return [], ["IPv6 disable skipped (not Admin)."]
    if not _win_probe().has_net_cmdlets:
        return [], [
            "IPv6 disable skipped — requires PowerShell 3.0+ "
            "(Windows 8 / Server 2012+). Detected legacy PowerShell."
        ]
    script = (
        "Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | "
        "ForEach-Object { $_.Name }"
    )
    names = [n.strip() for n in _run_ps(script).splitlines() if n.strip()]
    logs: list[str] = []
    changed: list[str] = []
    skip = ("virtual", "vmware", "hyper-v", "loopback", "bluetooth")
    for name in names:
        if any(h in name.lower() for h in skip):
            continue
        cmd = (
            f'Disable-NetAdapterBinding -Name "{name}" -ComponentID ms_tcpip6 -Confirm:$false '
            f'-ErrorAction SilentlyContinue'
        )
        subprocess.run(
            ["powershell", "-NoProfile", "-Command", cmd],
            capture_output=True,
            timeout=20,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        changed.append(name)
        logs.append(f"IPv6 disabled on {name}")
    return changed, logs


def enable_ipv6_on_adapters(adapters: list[str]) -> list[str]:
    if not is_admin() or not adapters:
        return []
    if not _win_probe().has_net_cmdlets:
        return []
    logs: list[str] = []
    for name in adapters:
        cmd = (
            f'Enable-NetAdapterBinding -Name "{name}" -ComponentID ms_tcpip6 -Confirm:$false '
            f'-ErrorAction SilentlyContinue'
        )
        subprocess.run(
            ["powershell", "-NoProfile", "-Command", cmd],
            capture_output=True,
            timeout=20,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        logs.append(f"IPv6 re-enabled on {name}")
    return logs


def apply_webrtc_hardening(enable: bool) -> tuple[bool, str]:
    """Chrome/Edge: disable WebRTC non-proxied UDP (Admin, HKLM policies).

    Writes both the legacy DWORD key (DefaultWebRtcIpHandlingPolicy=3) and
    the modern REG_SZ key (WebRtcIPHandling=disable_non_proxied_udp) so that
    all Chrome/Edge versions are covered.
    """
    if not is_admin():
        return False, "Administrator required for browser WebRTC policy."
    paths = [_WEBRTC_CHROME, _WEBRTC_EDGE]
    try:
        for path in paths:
            if enable:
                try:
                    key = winreg.CreateKeyEx(
                        winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_SET_VALUE
                    )
                except OSError:
                    continue
                with key:
                    winreg.SetValueEx(key, _WEBRTC_VALUE, 0, winreg.REG_DWORD, _WEBRTC_DISABLE)
                    winreg.SetValueEx(key, _WEBRTC_VALUE_STR, 0, winreg.REG_SZ, _WEBRTC_DISABLE_STR)
            else:
                try:
                    with winreg.OpenKey(
                        winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_SET_VALUE
                    ) as key:
                        for val_name in (_WEBRTC_VALUE, _WEBRTC_VALUE_STR):
                            try:
                                winreg.DeleteValue(key, val_name)
                            except OSError:
                                pass
                except OSError:
                    pass
        return True, (
            "WebRTC hardened (Chrome/Edge: non-proxied UDP disabled)."
            if enable
            else "WebRTC policy removed."
        )
    except OSError as e:
        return False, str(e)


def _get_os_info() -> str:
    try:
        import platform
        v = platform.version()
        r = platform.release()
        m = platform.machine()
        return f"Windows {r}  build {v}  {m}"
    except Exception:
        return "unknown"


def _get_timezone() -> str:
    try:
        import datetime
        tz = datetime.datetime.now(datetime.timezone.utc).astimezone()
        name = str(tz.tzname() or "")
        offset = tz.utcoffset()
        h = int(offset.total_seconds() // 3600) if offset else 0
        return f"{name} (UTC{h:+d})" if name else f"UTC{h:+d}"
    except Exception:
        return "unknown"


def _get_screen_resolution() -> str:
    try:
        r = subprocess.run(
            ["powershell", "-NoProfile", "-Command",
             "Add-Type -AssemblyName System.Windows.Forms;"
             "[System.Windows.Forms.Screen]::PrimaryScreen.Bounds | "
             "ForEach-Object { \"$($_.Width)x$($_.Height)\" }"],
            capture_output=True, text=True, timeout=8,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        return (r.stdout or "").strip() or "unknown"
    except Exception:
        return "unknown"


def check_browser_fingerprint_consistency(profile_dir: "Path") -> list[str]:
    """
    Verify that the managed Firefox user.js is internally consistent and
    doesn't mix prefs that would expose the browser as fingerprint-hardened
    while still advertising a normal user-agent.

    Returns a list of warning strings (empty = consistent).
    """
    from pathlib import Path
    user_js = Path(profile_dir) / "user.js"
    if not user_js.is_file():
        return ["user.js not found — profile not yet initialized"]

    try:
        content = user_js.read_text(encoding="utf-8", errors="replace")
    except Exception as e:
        return [f"Cannot read user.js: {e}"]

    warnings: list[str] = []

    def _has(pref: str, value: str) -> bool:
        return f'"{pref}", {value}' in content

    # 1. RFP + custom UA is a contradiction (RFP overrides UA)
    if _has("privacy.resistFingerprinting", "true") and 'general.useragent.override' in content:
        warnings.append(
            "RFP + UA override conflict: privacy.resistFingerprinting=true overrides "
            "general.useragent.override — the spoofed UA is ignored"
        )

    # 2. proxy not set but force_proxy claimed
    if _has("network.proxy.type", "1"):
        if 'network.proxy.http",' not in content:
            warnings.append("proxy.type=1 but network.proxy.http is missing — browsers will error")

    # 3. FPI on + third-party cookies allowed = inconsistent privacy posture
    if _has("privacy.firstparty.isolate", "true") and _has("network.cookie.cookieBehavior", "0"):
        warnings.append(
            "FPI enabled but cookieBehavior=0 (accept all) — cookies are isolated but not blocked"
        )

    # 4. sanitizeOnShutdown without clearing history = incomplete wipe
    if _has("privacy.sanitize.sanitizeOnShutdown", "true"):
        if not _has("privacy.clearOnShutdown.history", "true"):
            warnings.append(
                "sanitizeOnShutdown=true but clearOnShutdown.history not set — "
                "history may survive session"
            )

    # 5. WebRTC peerconnection disabled is good — flag if missing
    if not _has("media.peerconnection.enabled", "false"):
        warnings.append("WebRTC not disabled in profile — real IP can leak via STUN")

    # 6. network.trr.mode=5 (DoH off) is expected when using proxy DNS
    if not _has("network.trr.mode", "5"):
        warnings.append(
            "network.trr.mode is not 5 — Firefox may use its own DoH resolver, "
            "bypassing the proxy chain DNS path"
        )

    return warnings


def audit_device() -> FingerprintAudit:
    """Collect OS-level identifiers and consistency notes."""
    import getpass
    import os
    from pathlib import Path

    host = get_computer_name()
    guid = get_machine_guid()
    user = getpass.getuser()
    nics  = list_nics()
    macs  = [f"{n.name}: {n.mac}" for n in nics]
    os_   = _get_os_info()
    tz_   = _get_timezone()
    res_  = _get_screen_resolution()

    lines: list[str] = [
        "── OS / Identity ──────────────────────────────────",
        f"  OS           : {os_}",
        f"  Computer name: {host or '—'}",
        f"  Username     : {user}",
        f"  MachineGuid  : " + (f"{guid[:8]}…{guid[-4:]}" if len(guid) > 12 else guid or "—"),
        f"  Timezone     : {tz_}",
        f"  Screen res   : {res_}",
        "",
        "── Network adapters ───────────────────────────────",
    ]
    lines.extend([f"  • {m}" for m in macs[:10]] or ["  • (none detected)"])
    if len(macs) > 10:
        lines.append(f"  … and {len(macs) - 10} more")

    lines += [
        "",
        "── Browser fingerprint note ───────────────────────",
        "  Canvas, WebGL, font metrics, audio context, and screen dimensions",
        "  are NOT changed at the OS level by this app.",
        "  → Use 'blend_windows_chrome' persona OR 'hardened' mode to control",
        "    what the browser reports to sites.",
        "  → RFP (Resist Fingerprinting) makes Firefox stand out as hardened.",
        "  → Blend persona spoofs UA/platform to look like a normal Windows Chrome.",
        "",
        "── Recommendations ────────────────────────────────",
    ]

    # Quick consistency checks — WebRTC IP-handling policy (Chrome / Edge)
    # Accept either: DWORD DefaultWebRtcIpHandlingPolicy==3
    #             or REG_SZ WebRtcIPHandling=="disable_non_proxied_udp"
    webrtc_ok = False
    for hive_root in (_WEBRTC_CHROME, _WEBRTC_EDGE):
        try:
            with winreg.OpenKey(
                winreg.HKEY_LOCAL_MACHINE, hive_root, 0, winreg.KEY_QUERY_VALUE
            ) as k:
                try:
                    v = int(winreg.QueryValueEx(k, _WEBRTC_VALUE)[0])
                    if v == _WEBRTC_DISABLE:
                        webrtc_ok = True
                        break
                except OSError:
                    pass
                try:
                    v_str = str(winreg.QueryValueEx(k, _WEBRTC_VALUE_STR)[0])
                    if v_str == _WEBRTC_DISABLE_STR:
                        webrtc_ok = True
                        break
                except OSError:
                    pass
        except OSError:
            continue

    if webrtc_ok:
        lines.append("  ✓ Chrome/Edge WebRTC IP-handling policy is set (disable_non_proxied_udp)")
    else:
        lines.append("  ⚠ Chrome/Edge WebRTC policy not set — enable in Privacy tab")

    # Check IPv6
    try:
        r = subprocess.run(
            ["powershell", "-NoProfile", "-NonInteractive", "-Command",
             "Get-NetAdapterBinding -ComponentID ms_tcpip6 | Where-Object { $_.Enabled } | Measure-Object | Select-Object -ExpandProperty Count"],
            capture_output=True, text=True, timeout=8,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        count = int((r.stdout or "0").strip() or "0")
        if count > 0:
            lines.append(f"  ⚠ IPv6 active on {count} adapter(s) — disable in Privacy tab for full v4-only chain")
        else:
            lines.append("  ✓ IPv6 disabled on all adapters")
    except Exception:
        lines.append("  ? IPv6 status unknown")

    return FingerprintAudit(
        lines=lines,
        hostname=host,
        machine_guid=guid,
        username=user,
        macs=[n.mac for n in nics],
    )
