/**
 * Licence worker — hosts the .NET CLR (node-api-dotnet) and runs all
 * TreeksLicensingLibraryCore calls OFF the module's main event loop.
 *
 * Forked as a CHILD PROCESS by src/licensing.ts: TLL calls are synchronous
 * .NET invocations (ActivateSerial blocks on an HTTPS round-trip to the
 * Treeks server; the Windows hardware ID reads WMI), and a native CLR crash
 * must not take the Companion module down with it. The parent enforces
 * timeouts and recycles a hung child.
 *
 * This file ships UNBUNDLED in the package (extraFiles) — it resolves
 * node-api-dotnet from the package's own node_modules at runtime.
 *
 * Environment:
 *   PF_MODULE_DIR   package root (contains node_modules/ and tll/)
 *   PF_LICENCE_FILE stable per-machine licence store path (survives upgrades)
 *
 * Protocol: { id, cmd: 'status'|'activate'|'trial'|'deactivate', serial? } in,
 * { id, result: { licensed, helperAvailable, detail } } out. Id 0 = ready.
 */
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join } from 'node:path';
import { CHUNK_KEY, INIT_CHUNK } from './init-chunk.mjs';

const log = (m) => console.error(`[tll] ${m}`); // stderr -> module logger

const moduleDir = process.env.PF_MODULE_DIR ?? process.cwd();
const licenceFile = process.env.PF_LICENCE_FILE ?? join(moduleDir, 'licence', 'licence.bin');

let tll;
let initError;
let initialised = false;

function init() {
  if (initialised) return tll;
  initialised = true;
  try {
    const tllDir = join(moduleDir, 'tll');
    log(`init: moduleDir=${moduleDir} platform=${process.platform}/${process.arch} node=${process.version}`);
    // TLL's path-tag converter dereferences %windir% and %ProgramFiles%
    // unconditionally and NPEs where they're unset (non-Windows hosts).
    // Must be set BEFORE the CLR is hosted — .NET snapshots the environment.
    if (!process.env.windir) process.env.windir = 'C:\\Windows';
    if (!process.env['ProgramFiles(x86)']) process.env['ProgramFiles(x86)'] = 'C:\\Program Files (x86)';
    if (!process.env.ProgramFiles) process.env.ProgramFiles = 'C:\\Program Files';
    const req = createRequire(join(moduleDir, 'noop.js'));
    const dotnet = req('node-api-dotnet/net8.0');
    log('init: CLR hosted');

    // Windows needs the real WMI implementation, not the reference stub.
    const sysManagement =
      process.platform === 'win32' && existsSync(join(tllDir, 'win', 'System.Management.dll'))
        ? join(tllDir, 'win', 'System.Management.dll')
        : join(tllDir, 'System.Management.dll');
    dotnet.load(sysManagement);
    log(`init: loaded ${sysManagement}`);
    dotnet.load(join(tllDir, 'TreeksLicensingLibraryCore.dll'));
    log('init: loaded TreeksLicensingLibraryCore.dll');

    const iface = new dotnet.TreeksLicensingLibraryCore.TLLInterface.TLLInterface(
      INIT_CHUNK, CHUNK_KEY, false);
    log('init: TLLInterface constructed');

    // Store the licence as a file in a STABLE per-machine path, not the
    // chunk's registry scope (HKLM — unwritable unelevated) and not beside
    // the bundle (Companion re-imports each module version into a fresh
    // folder, which would orphan the licence on every upgrade). With
    // LicenseFilePath set, TLL's Register and IsLicenseValid use the file
    // and skip the registry entirely.
    iface.InitChunk.LicenseFilePath = licenceFile;
    iface.IsLicenseValid(); // reload MyLicense from the file store
    log(`init: licence store = ${licenceFile} (present: ${existsSync(licenceFile)})`);

    // Offline licence file shipped beside the DLLs (Airlock/PDS convention).
    if (!iface.MyLicense) {
      const lic = join(tllDir, 'licence.lic');
      if (existsSync(lic)) iface.Register(lic);
    }
    tll = iface;
    log('init: OK');
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    // hostfxr failures: no Microsoft.NETCore.App 8.x on the machine (or the
    // wrong architecture, e.g. x64 runtime with arm64 Node).
    const arch =
      process.platform === 'darwin'
        ? (process.arch === 'arm64' ? 'macOS Arm64 (Apple Silicon)' : 'macOS x64 (Intel)')
        : process.platform === 'win32' ? `Windows ${process.arch}` : `${process.platform} ${process.arch}`;
    initError = /hostfxr|dotnet|runtime|libcoreclr|CLR host|FrameworkMissing|CoreHostLib/i.test(msg)
      ? `.NET 8 runtime not installed — download the ".NET Runtime 8.0.x" ${arch} installer from ` +
        `https://dotnet.microsoft.com/download/dotnet/8.0, then restart Companion (${msg})`
      : 'licensing library unavailable: ' + msg;
    tll = undefined;
    log(`init FAILED: ${msg}`);
  }
  return tll;
}

function evaluate(activationResult) {
  const t = init();
  if (!t) {
    return { licensed: false, helperAvailable: false, detail: initError ?? 'licensing library unavailable' };
  }
  try {
    const lic = t.MyLicense;
    if (!lic) {
      return {
        licensed: false,
        helperAvailable: true,
        detail: activationResult
          ? `activation did not register a licence: ${activationResult}`
          : 'no licence registered — apply a serial in the connection settings to enable control',
      };
    }
    const valid = t.IsLicenseValid();
    const owner = lic.OwnerName || 'unknown owner';
    const expiry = lic.ExpirationDateEnabled ? ` — expires ${String(lic.ExpirationDate)}` : '';
    let why = '';
    if (!valid) {
      why = ': ' + (t.LastVerificationResult || 'unknown reason');
      log(`invalid licence — verifier says: ${t.LastVerificationResult}`);
      // Hardware-ID mismatch: show both sides so the fix is obvious.
      if (/computer/i.test(t.LastVerificationResult || '')) {
        try {
          const licIds = Array.from(lic.HardwareID ?? []).join(', ');
          why += ` (licence hwid: ${licIds || 'none'}; this machine: ${t.GetActualHardwareID()})`;
        } catch { /* best effort */ }
      }
    }
    return {
      licensed: valid,
      helperAvailable: true,
      detail: valid
        ? `licensed to ${owner}${lic.IsDemo ? ' (demo)' : ''}${expiry}`
        : `licence for ${owner} is not valid${why}${expiry}`,
    };
  } catch (err) {
    return {
      licensed: false,
      helperAvailable: true,
      detail: 'licence check failed: ' + (err instanceof Error ? err.message : String(err)),
    };
  }
}

process.on('message', (m) => {
  log(`request: ${m.cmd} (id ${m.id})`);
  let result;
  try {
    if (m.cmd === 'trial' && m.serial) {
      const t = init();
      let trial = '';
      if (t) {
        log('trial: calling Treeks server…');
        trial = t.ActivateTrial(m.serial); // email; blocking HTTPS
        log(`trial: server replied: ${trial}`);
      }
      result = evaluate(trial);
      if (trial && !result.licensed) result.detail = trial; // e.g. "confirm your e-mail" — verbatim
    } else if (m.cmd === 'activate' && m.serial) {
      const t = init();
      let activation = '';
      if (t) {
        log('activate: calling Treeks server…');
        activation = t.ActivateSerial(m.serial); // blocking HTTPS to the Treeks server
        log(`activate: server replied: ${activation}`);
      }
      result = evaluate(activation);
      if (activation) result.detail += result.licensed ? '' : ` (server: ${activation})`;
    } else if (m.cmd === 'deactivate') {
      const t = init();
      if (t) {
        try { t.DeactivateLicense(); } catch { /* offline deactivation still clears below */ }
        try { t.DeleteLicenseInRegistry(); } catch { /* registry may be empty */ }
      }
      result = evaluate();
    } else {
      result = evaluate();
    }
  } catch (err) {
    result = {
      licensed: false,
      helperAvailable: false,
      detail: 'licence worker error: ' + (err instanceof Error ? err.message : String(err)),
    };
  }
  log(`reply: ${m.cmd} licensed=${result.licensed}`);
  process.send({ id: m.id, result });
});

process.send({ id: 0, ready: true });
log('child up');
