"""Hardware ID binding and activation token -- Sprint 8.

The 6-byte ESP32 MAC (read via Modbus INPUT_HWID_MAC_01/23/45) is used to
bind a project license to a specific hardware unit.

Activation token format
-----------------------
The factory generates a token by HMAC-SHA256(key=SECRET, msg=hwid_hex),
where ``hwid_hex`` is the 12-character lower-case hex representation of the
6-byte MAC.  The token is the first 16 bytes (32 hex chars) of the HMAC
output.

The SECRET is a per-product deployment key embedded in the Studio binary
(not in the open-source firmware).  This file ships the *verification* side
only; token generation happens offline in the factory tooling.

For development / local testing a deterministic stub secret is used
(STUB_SECRET_KEY).  The production key must be injected via the environment
variable ``AR_ACTIVATION_KEY``.
"""

from __future__ import annotations

import hashlib
import hmac
import os

STUB_SECRET_KEY = b"AR-Autopilot-Dev-Key-2026"
TOKEN_BYTES = 16  # 32 hex chars


def _get_secret() -> bytes:
    key = os.environ.get("AR_ACTIVATION_KEY", "").encode()
    return key if key else STUB_SECRET_KEY


def hwid_from_mac_words(mac01: int, mac23: int, mac45: int) -> str:
    """Convert three 16-bit Modbus words to a 12-char lower-case hex HWID string."""
    mac = bytes([
        (mac01 >> 8) & 0xFF, mac01 & 0xFF,
        (mac23 >> 8) & 0xFF, mac23 & 0xFF,
        (mac45 >> 8) & 0xFF, mac45 & 0xFF,
    ])
    return mac.hex()


def generate_token(hwid_hex: str) -> str:
    """Generate the activation token for a given HWID.

    Should only be called by factory tooling.  In the Studio this is
    called only during development / bench testing with the stub key.
    """
    h = hmac.new(_get_secret(), hwid_hex.lower().encode(), hashlib.sha256)
    return h.hexdigest()[:TOKEN_BYTES * 2]


def verify_token(hwid_hex: str, token: str) -> bool:
    """Return True iff the token is valid for the given HWID.

    Uses constant-time comparison to resist timing attacks.
    """
    expected = generate_token(hwid_hex)
    return hmac.compare_digest(expected.lower(), token.lower())


def format_hwid(hwid_hex: str) -> str:
    """Format a 12-char hex HWID as 'AA:BB:CC:DD:EE:FF'."""
    if len(hwid_hex) != 12:
        raise ValueError(f"HWID must be 12 hex chars, got {len(hwid_hex)}")
    h = hwid_hex.upper()
    return ":".join(h[i:i+2] for i in range(0, 12, 2))
