"""Randomize and restore NIC MAC addresses (Admin required)."""
from __future__ import annotations

import logging
import random
import re
import subprocess
from dataclasses import dataclass

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

log = logging.getLogger(__name__)

_MAC_RE = re.compile(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")


@dataclass
class NicMac:
    name: str
    mac: str
    description: str = ""


def _run(args: list[str], timeout: float = 20.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)


def list_nics() -> list[NicMac]:
    """Physical/up adapters with current MAC.

    Falls back to ``wmic nic`` for hosts without PowerShell 3.0 (Server 2008 R2).
    """
    compat = _win_probe()
    if not compat.has_net_cmdlets:
        return _list_nics_wmic()

    script = (
        "Get-NetAdapter | Where-Object { $_.Status -ne 'Disabled' } | "
        "Select-Object Name, MacAddress, InterfaceDescription | "
        "ConvertTo-Json -Compress"
    )
    code, out, _ = _run(["powershell", "-NoProfile", "-Command", script])
    if code != 0 or not out.strip():
        return _list_nics_wmic()
    import json

    try:
        data = json.loads(out)
    except json.JSONDecodeError:
        return []
    rows = data if isinstance(data, list) else [data]
    nics: list[NicMac] = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        name = str(row.get("Name", "")).strip()
        mac = str(row.get("MacAddress", "")).strip().replace("-", ":")
        desc = str(row.get("InterfaceDescription", "")).strip()
        if name and mac and mac != "00:00:00:00:00:00":
            nics.append(NicMac(name=name, mac=mac, description=desc))
    return nics


def random_mac() -> str:
    """Locally administered unicast MAC."""
    b = [random.randint(0, 255) for _ in range(6)]
    b[0] = (b[0] | 0x02) & 0xFE
    return ":".join(f"{x:02X}" for x in b)


def _list_nics_wmic() -> list[NicMac]:
    """Server 2008 R2 / PowerShell 2.0 fallback via wmic."""
    code, out, _ = _run([
        "wmic", "nic", "where", "NetEnabled=true",
        "get", "NetConnectionID,MACAddress,Name", "/format:list",
    ])
    if code != 0 or not out:
        return []
    nics: list[NicMac] = []
    cur = {"id": "", "mac": "", "name": ""}
    for ln in out.splitlines():
        s = ln.strip()
        if not s:
            if cur["id"] and cur["mac"]:
                mac = cur["mac"].replace("-", ":")
                if mac and mac != "00:00:00:00:00:00":
                    nics.append(NicMac(name=cur["id"], mac=mac, description=cur["name"]))
            cur = {"id": "", "mac": "", "name": ""}
            continue
        if s.startswith("NetConnectionID="):
            cur["id"] = s.split("=", 1)[1].strip()
        elif s.startswith("MACAddress="):
            cur["mac"] = s.split("=", 1)[1].strip()
        elif s.startswith("Name="):
            cur["name"] = s.split("=", 1)[1].strip()
    if cur["id"] and cur["mac"]:
        mac = cur["mac"].replace("-", ":")
        if mac and mac != "00:00:00:00:00:00":
            nics.append(NicMac(name=cur["id"], mac=mac, description=cur["name"]))
    return nics


def set_mac(adapter: str, mac: str) -> tuple[bool, str]:
    if not is_admin():
        return False, "Administrator required to change MAC."
    if not _win_probe().has_net_cmdlets:
        return False, (
            "MAC change requires PowerShell 3.0+ (Windows 8 / Server 2012+). "
            "Detected legacy PowerShell — feature unavailable on this host."
        )
    mac = mac.replace("-", ":").upper()
    if not _MAC_RE.match(mac.replace(":", "-")):
        return False, f"Invalid MAC: {mac}"
    ps = (
        f'$a = Get-NetAdapter -Name "{adapter}" -ErrorAction Stop; '
        f'Set-NetAdapter -Name $a.Name -MacAddress "{mac}" -Confirm:$false'
    )
    code, _, err = _run(["powershell", "-NoProfile", "-Command", ps])
    if code != 0:
        return False, err or "Set-NetAdapter failed"
    log.info("MAC set %s → %s", adapter, mac)
    return True, f"{adapter} → {mac}"


def spoof_all_physical(snapshot: dict[str, str] | None = None) -> tuple[dict[str, str], list[str]]:
    """Randomize MAC on non-virtual adapters. Returns (original_map, log lines)."""
    originals: dict[str, str] = dict(snapshot or {})
    logs: list[str] = []
    skip_hints = ("virtual", "vmware", "hyper-v", "loopback", "bluetooth", "wan miniport")
    for nic in list_nics():
        low = (nic.description + nic.name).lower()
        if any(h in low for h in skip_hints):
            continue
        if nic.name not in originals:
            originals[nic.name] = nic.mac
        new_mac = random_mac()
        ok, msg = set_mac(nic.name, new_mac)
        logs.append(msg if ok else f"{nic.name}: {msg}")
    return originals, logs


def restore_macs(originals: dict[str, str]) -> list[str]:
    logs: list[str] = []
    for name, mac in originals.items():
        ok, msg = set_mac(name, mac)
        logs.append(msg if ok else f"{name}: {msg}")
    return logs
