// cli/fingerprint.js
// Collects stable hardware identifiers and derives HMAC tokens
// Works on Windows (primary) + Linux/macOS (fallback)

import { execSync } from "child_process";
import { createHmac, createHash } from "crypto";
import os from "os";
import fs from "fs";
import path from "path";

// ============================================================
// Machine ID collection
// ============================================================

// Cache path — avoids re-querying WMI/CIM on every daemon start.
// This eliminates the spawnSync cmd.exe ETIMEDOUT crash that occurs
// when PowerShell/WMI is slow on first call after boot.
const CACHE_FILE = path.join(os.tmpdir(), "clauth-machine.cache");

function readCache() {
  try {
    const raw = fs.readFileSync(CACHE_FILE, "utf8").trim();
    // Validate: must be two non-empty lines (primary:secondary)
    const [primary, secondary] = raw.split("\n");
    if (primary && secondary) return { primary: primary.trim(), secondary: secondary.trim() };
  } catch { /* cache miss */ }
  return null;
}

function writeCache(primary, secondary) {
  try { fs.writeFileSync(CACHE_FILE, `${primary}\n${secondary}`, "utf8"); } catch { /* best effort */ }
}

function getMachineId() {
  // Fast path: use cached IDs if available (avoids WMI/PowerShell on every restart)
  const cached = readCache();
  if (cached) return { ...cached, platform: os.platform() };

  const platform = os.platform();

  try {
    if (platform === "win32") {
      const psPath = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
      // Increased timeout to 15s — CimInstance/WMI can be slow after boot or under load.
      // Each call is wrapped independently so a partial failure still yields a result.
      let uuid = "";
      try {
        uuid = execSync(
          `${psPath} -NoProfile -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"`,
          { encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }
        ).trim();
      } catch { /* fall through to registry-only path */ }

      let machineGuid = "";
      try {
        machineGuid = execSync(
          `${psPath} -NoProfile -Command "(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid"`,
          { encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }
        ).trim();
      } catch { /* fall through */ }

      // Require at least one identifier
      if (!uuid && !machineGuid) throw new Error("Could not read any Windows machine ID");

      // Use hostname as fallback secondary if registry query failed
      const primary = uuid || machineGuid;
      const secondary = machineGuid || os.hostname();

      writeCache(primary, secondary);
      return { primary, secondary, platform: "win32" };
    }

    if (platform === "darwin") {
      const uuid = execSync(
        "ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { print $3 }'",
        { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
      ).replace(/['"]/g, "").trim();
      writeCache(uuid, os.hostname());
      return { primary: uuid, secondary: os.hostname(), platform: "darwin" };
    }

    // Linux
    let uuid = "";
    try { uuid = execSync("cat /etc/machine-id", { encoding: "utf8", timeout: 2000 }).trim(); }
    catch { uuid = execSync("cat /var/lib/dbus/machine-id", { encoding: "utf8", timeout: 2000 }).trim(); }
    writeCache(uuid, os.hostname());
    return { primary: uuid, secondary: os.hostname(), platform: "linux" };

  } catch (err) {
    throw new Error(`Machine ID collection failed: ${err.message}`);
  }
}

// ============================================================
// Derive stable machine hash (what gets stored in Supabase)
// ============================================================

export function getMachineHash() {
  const { primary, secondary } = getMachineId();
  return createHash("sha256")
    .update(`${primary}:${secondary}`)
    .digest("hex");
}

// ============================================================
// Derive HMAC token for a given password + timestamp
// ============================================================

export function deriveToken(password, machineHash) {
  const windowMs = 5 * 60 * 1000;
  const window   = Math.floor(Date.now() / windowMs);
  const message  = `${machineHash}:${window}`;
  
  // Server reconstructs this — password + CLAUTH_HMAC_SALT
  // Client sends token; server adds its SALT to the password before verifying
  const token = createHmac("sha256", password)
    .update(message)
    .digest("hex");

  return { token, timestamp: window * windowMs, machineHash };
}

// ============================================================
// Derive HMAC seed hash (stored during machine registration)
// ============================================================

export function deriveSeedHash(machineHash, password) {
  return createHash("sha256")
    .update(`seed:${machineHash}:${password}`)
    .digest("hex");
}

export default { getMachineHash, deriveToken, deriveSeedHash };
