const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const { execFileSync } = require('child_process');

function argument(name) {
  const prefix = `--${name}=`;
  const row = process.argv.slice(2).find((value) => value.startsWith(prefix));
  return row ? row.slice(prefix.length).trim() : '';
}

function safeLabel(value, fallback = '') {
  const normalized = String(value || fallback).trim();
  if (!normalized) return '';
  if (!/^[A-Za-z0-9._-]{1,80}$/.test(normalized)) {
    throw new Error('Inventory labels may contain only letters, digits, dot, underscore, and hyphen.');
  }
  return normalized;
}

function privateRange(address) {
  const value = String(address || '');
  const parts = value.split('.').map(Number);
  if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return '';
  const isPrivate = parts[0] === 10
    || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
    || (parts[0] === 192 && parts[1] === 168)
    || (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127);
  return isPrivate ? `${parts[0]}.${parts[1]}.x.x` : '';
}

function localNetworkRanges() {
  const ranges = Object.values(os.networkInterfaces()).flatMap((rows) => (rows || [])
    .filter((row) => row.family === 'IPv4' && !row.internal)
    .map((row) => privateRange(row.address))
    .filter(Boolean));
  return [...new Set(ranges)].sort();
}

function runPowerShellJson(script) {
  if (process.platform !== 'win32') return null;
  try {
    const output = execFileSync('powershell.exe', [
      '-NoLogo',
      '-NoProfile',
      '-NonInteractive',
      '-Command',
      script
    ], { encoding: 'utf8', timeout: 15000, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
    return output ? JSON.parse(output) : null;
  } catch (_) {
    return null;
  }
}

function windowsStorageInventory() {
  const volumes = [];
  for (let code = 65; code <= 90; code += 1) {
    const drive = String.fromCharCode(code);
    const root = `${drive}:\\`;
    if (!fs.existsSync(root)) continue;
    try {
      const stat = fs.statfsSync(root, { bigint: true });
      volumes.push({
        drive,
        filesystem: '',
        sizeBytes: Number(stat.blocks * stat.bsize),
        freeBytes: Number(stat.bavail * stat.bsize),
        health: 'UNKNOWN_NON_ADMIN'
      });
    } catch (_) {
      volumes.push({ drive, filesystem: '', sizeBytes: 0, freeBytes: 0, health: 'READ_FAILED' });
    }
  }
  const physicalDisks = runPowerShellJson(`
    @(Get-CimInstance Win32_DiskDrive -ErrorAction SilentlyContinue | ForEach-Object {
      [pscustomobject]@{
        model = [string]$_.Model
        mediaType = [string]$_.MediaType
        sizeBytes = [int64]$_.Size
        health = [string]$_.Status
        operationalStatus = [string]$_.Status
      }
    }) | ConvertTo-Json -Compress
  `);
  return {
    volumes,
    physicalDisks: Array.isArray(physicalDisks) ? physicalDisks : (physicalDisks ? [physicalDisks] : [])
  };
}

function canonicalJson(value) {
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
  if (value && typeof value === 'object') {
    return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
  }
  return JSON.stringify(value);
}

function main() {
  const cpus = os.cpus();
  const inventory = {
    schemaVersion: 'plai-resilience-pc-local-inventory/v1',
    mode: 'LOCAL_READ_ONLY_NO_TRANSMISSION',
    collectedAt: new Date().toISOString(),
    identity: {
      pcName: safeLabel(argument('pc-name'), os.hostname()),
      ownerLabel: safeLabel(argument('owner-label')),
      siteLabel: safeLabel(argument('site-label')),
      networkFailureDomain: safeLabel(argument('network-domain'))
    },
    system: {
      platform: os.platform(),
      release: os.release(),
      architecture: os.arch(),
      cpuModel: cpus[0]?.model || '',
      logicalCpuVisibleToProcess: cpus.length,
      totalMemoryBytes: os.totalmem(),
      freeMemoryBytes: os.freemem(),
      uptimeSeconds: Math.floor(os.uptime()),
      bootTimeApproximate: new Date(Date.now() - os.uptime() * 1000).toISOString()
    },
    network: {
      privateRanges: localNetworkRanges(),
      rawAddressesIncluded: false,
      publicEgressLookupPerformed: false
    },
    storage: windowsStorageInventory(),
    operatorAssertionsRequired: {
      upsPresent: null,
      automaticBootVerified: null,
      physicalSiteIndependent: null,
      ispIndependent: null,
      natOrVpnReachabilityVerified: null,
      maintenanceOwnerConfirmed: null
    },
    security: {
      environmentRead: false,
      secretValuesCollected: false,
      serialNumbersCollected: false,
      networkRequestsMade: false,
      filesWritten: false,
      signaturePresent: false,
      authenticationPlan: 'Transmit later only through the existing authenticated PC Sync heartbeat/update gate.'
    }
  };
  inventory.inventorySha256 = crypto.createHash('sha256').update(canonicalJson(inventory)).digest('hex');
  console.log(JSON.stringify(inventory, null, 2));
}

try {
  main();
} catch (error) {
  console.error(JSON.stringify({ ok: false, error: error.message }, null, 2));
  process.exitCode = 1;
}
