"""General-purpose helpers for MuraveyChina.

Includes stable device ID generation used for license binding.
"""

from __future__ import annotations

import hashlib
import logging
import platform
import subprocess
import sys
import uuid

log = logging.getLogger(__name__)

# Cached value so repeated calls are fast
_device_id_cache: str | None = None


def get_device_id() -> str:
    """Return a stable, privacy-preserving device identifier.

    The ID is:
    - Unique per physical machine (survives reboots, app reinstalls)
    - SHA-256 hash of a platform-specific hardware serial → not reversible
    - Consistent across calls within the same installation

    Platform sources (in order of preference):
    - macOS:   IOPlatformSerialNumber from ioreg
    - Windows: MachineGuid from HKLM\\SOFTWARE\\Microsoft\\Cryptography
    - Fallback: hashed UUID based on MAC address (uuid.getnode)
    """
    global _device_id_cache
    if _device_id_cache is not None:
        return _device_id_cache

    raw = _get_hardware_id()
    # SHA-256 → first 32 hex chars (128-bit → plenty of entropy)
    device_id = hashlib.sha256(f"MuraveyChina:{raw}".encode()).hexdigest()[:32]
    _device_id_cache = device_id
    log.debug("Device ID: %s…", device_id[:8])
    return device_id


def _get_hardware_id() -> str:
    """Return a raw hardware identifier string (not yet hashed)."""
    if sys.platform == "darwin":
        serial = _macos_serial()
        if serial:
            return serial

    elif sys.platform == "win32":
        guid = _windows_machine_guid()
        if guid:
            return guid

    # Cross-platform fallback: hashed MAC + platform node
    mac = uuid.getnode()
    node = platform.node()
    return f"{mac}:{node}"


def _macos_serial() -> str | None:
    """Read IOPlatformSerialNumber from ioreg (macOS)."""
    try:
        out = subprocess.check_output(
            ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
            stderr=subprocess.DEVNULL,
            timeout=5,
        ).decode(errors="replace")
        for line in out.splitlines():
            if "IOPlatformSerialNumber" in line:
                # Line: "IOPlatformSerialNumber" = "C02XG2JHJGH5"
                parts = line.split("=", 1)
                if len(parts) == 2:
                    return parts[1].strip().strip('"')
    except Exception as exc:
        log.debug("ioreg failed: %s", exc)
    return None


def _windows_machine_guid() -> str | None:
    """Read MachineGuid from Windows registry."""
    try:
        import winreg  # type: ignore[import]

        key = winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE,
            r"SOFTWARE\Microsoft\Cryptography",
        )
        value, _ = winreg.QueryValueEx(key, "MachineGuid")
        winreg.CloseKey(key)
        return str(value)
    except Exception as exc:
        log.debug("Registry MachineGuid read failed: %s", exc)
    return None
