from __future__ import annotations
import hashlib
import os
import platform
import re
import subprocess
from functools import lru_cache
from typing import Iterable
from ._exceptions import HWIDError
def _safe_run(cmd: list[str] | str, *, shell: bool = False) -> str:
    try:
        out = subprocess.check_output(
            cmd,
            shell=shell,
            stderr=subprocess.DEVNULL,
            timeout=3,
        )
        return out.decode(errors="ignore").strip()
    except Exception:
        return ""
def _filter_tokens(tokens: Iterable[str]) -> list[str]:
    junk_substrings = (
        "To Be Filled",
        "Default string",
        "None",
        "00000000",
        "0000000000000000",
        "Not Specified",
        "System Serial Number",
    )
    clean: list[str] = []
    seen: set[str] = set()
    for token in tokens:
        if not token:
            continue
        token = token.strip()
        if not token:
            continue
        if any(junk.lower() in token.lower() for junk in junk_substrings):
            continue
        if token in seen:
            continue
        seen.add(token)
        clean.append(token)
    return clean
def _get_windows_tokens() -> list[str]:
    tokens: list[str] = []
    try:
        import winreg  # type: ignore
    except ImportError:
        winreg = None
    uuid_out = _safe_run([
        "powershell",
        "-NoProfile",
        "-Command",
        "(Get-CimInstance -ClassName Win32_ComputerSystemProduct).UUID",
    ])
    if uuid_out:
        tokens.append(uuid_out)
    if winreg is not None:
        def reg_read(root, path: str, value: str) -> str:
            try:
                with winreg.OpenKey(root, path) as key:
                    v, _ = winreg.QueryValueEx(key, value)
                    return str(v)
            except OSError:
                return ""
        bios_path = r"HARDWARE\\DESCRIPTION\\System\\BIOS"
        cpu_path = r"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"
        tokens.extend([
            reg_read(winreg.HKEY_LOCAL_MACHINE, bios_path, "BaseBoardSerialNumber"),
            reg_read(winreg.HKEY_LOCAL_MACHINE, bios_path, "BIOSSerialNumber"),
            reg_read(winreg.HKEY_LOCAL_MACHINE, cpu_path, "ProcessorNameString"),
            reg_read(winreg.HKEY_LOCAL_MACHINE, cpu_path, "Identifier"),
        ])
    disk_serials = _safe_run([
        "powershell",
        "-NoProfile",
        "-Command",
        "Get-PhysicalDisk | Where-Object {$_.BusType -ne 'USB'} | Select-Object -ExpandProperty SerialNumber",
    ])
    if disk_serials:
        tokens.extend(line.strip() for line in disk_serials.splitlines() if line.strip())
    return tokens
def _read_text_file(path: str) -> str:
    try:
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            return f.read().strip()
    except Exception:
        return ""
def _get_linux_tokens() -> list[str]:
    tokens: list[str] = []
    for path in (
        "/sys/class/dmi/id/product_uuid",
        "/sys/class/dmi/id/board_serial",
        "/sys/class/dmi/id/product_serial",
        "/etc/machine-id",
        "/var/lib/dbus/machine-id",
    ):
        value = _read_text_file(path)
        if value:
            tokens.append(value)
    dmidecode = _safe_run(["dmidecode", "-s", "system-uuid"])
    if dmidecode:
        tokens.append(dmidecode)
    cpuinfo = _read_text_file("/proc/cpuinfo")
    if cpuinfo:
        match = re.search(r"^Serial\\s*:\\s*(.+)$", cpuinfo, flags=re.MULTILINE)
        if match:
            tokens.append(match.group(1).strip())
    return tokens
def _get_darwin_tokens() -> list[str]:
    tokens: list[str] = []
    out = _safe_run(
        "ioreg -l -d 1 -w 0 | grep -E 'IOPlatformUUID|IOPlatformSerialNumber'",
        shell=True,
    )
    if out:
        for line in out.splitlines():
            match = re.search(r'"([^"]+)"\\s*=\\s*"([^"]+)"', line)
            if match:
                key, value = match.groups()
                if key in ("IOPlatformUUID", "IOPlatformSerialNumber"):
                    tokens.append(value.strip())
    sp_out = _safe_run(["system_profiler", "SPHardwareDataType"])
    if sp_out:
        uuid_match = re.search(r"Hardware UUID:\\s*([A-F0-9-]+)", sp_out)
        serial_match = re.search(r"Serial Number \\(system\\):\\s*(\\S+)", sp_out)
        if uuid_match:
            tokens.append(uuid_match.group(1).strip())
        if serial_match:
            tokens.append(serial_match.group(1).strip())
    return tokens
def _get_bsd_tokens() -> list[str]:
    tokens: list[str] = []
    out = _safe_run(["kenv", "smbios.system.uuid"])
    if out:
        tokens.append(out)
    hostuuid = _safe_run(["sysctl", "-n", "kern.hostuuid"])
    if hostuuid:
        tokens.append(hostuuid)
    return tokens
def _collect_tokens() -> list[str]:
    os_type = platform.system()
    if os_type == "Windows":
        return _filter_tokens(_get_windows_tokens())
    if os_type == "Linux":
        return _filter_tokens(_get_linux_tokens())
    if os_type == "Darwin":
        return _filter_tokens(_get_darwin_tokens())
    return _filter_tokens(_get_bsd_tokens())
@lru_cache(maxsize=1)
def get_hwid(*, algorithm: str = "sha256") -> str:
    tokens = _collect_tokens()
    if not tokens:
        raise HWIDError("No hardware identifiers found for this device.")
    normalized = "\\x1f".join(tokens).encode("utf-8", errors="ignore")
    try:
        hasher = hashlib.new(algorithm)
    except ValueError as e:
        raise ValueError(f"Unsupported hash algorithm: {algorithm}") from e
    hasher.update(normalized)
    return hasher.hexdigest()
def get_hwid_components() -> list[str]:
    tokens = _collect_tokens()
    if not tokens:
        raise HWIDError("No hardware identifiers found for this device.")
    return tokens