const { execFile } = require('child_process');
const os = require('os');

function runCommand(command, args = [], timeout = 8000) {
  return new Promise((resolve) => {
    execFile(command, args, { timeout, windowsHide: true }, (error, stdout, stderr) => {
      resolve({
        ok: !error,
        command: [command, ...args].join(' '),
        output: String(stdout || '').trim(),
        error: String(stderr || error?.message || '').trim()
      });
    });
  });
}

function bytesToGb(value) {
  return Math.round((Number(value || 0) / 1024 / 1024 / 1024) * 10) / 10;
}

function cleanLine(value) {
  return String(value || '').replace(/\s+\n/g, '\n').trim();
}

function evidenceCheck({ id, title, severity = 'ok', detail, evidence = [], recommendation, category = 'System' }) {
  return {
    id,
    title,
    severity,
    category,
    detail: cleanLine(detail),
    evidence: Array.isArray(evidence) ? evidence.map(cleanLine).filter(Boolean).slice(0, 8) : [],
    recommendation: cleanLine(recommendation)
  };
}

function parseMacDisk(dfOutput) {
  const line = dfOutput.split('\n').find((row) => row.includes(' /System/Volumes/Data')) || dfOutput.split('\n').find((row) => row.includes(' /')) || '';
  const parts = line.trim().split(/\s+/);
  if (parts.length < 5) return null;
  return {
    filesystem: parts[0],
    totalGb: Math.round((Number(parts[1] || 0) / 1024 / 1024) * 10) / 10,
    usedGb: Math.round((Number(parts[2] || 0) / 1024 / 1024) * 10) / 10,
    freeGb: Math.round((Number(parts[3] || 0) / 1024 / 1024) * 10) / 10,
    usedPercent: parts[4]
  };
}

function parseDiskutilInfo(output) {
  const valueFor = (label) => {
    const line = output.split('\n').find((row) => row.trim().startsWith(`${label}:`));
    return line ? line.split(':').slice(1).join(':').trim() : '';
  };
  return {
    smartStatus: valueFor('SMART Status'),
    fileVault: valueFor('FileVault'),
    volumeName: valueFor('Volume Name'),
    fileSystem: valueFor('File System Personality'),
    freeSpace: valueFor('Container Free Space'),
    usedSpace: valueFor('Volume Used Space'),
    totalSpace: valueFor('Container Total Space') || valueFor('Disk Size')
  };
}

function parseMacPower(output) {
  const valueFor = (label) => {
    const line = output.split('\n').find((row) => row.trim().startsWith(`${label}:`));
    return line ? line.split(':').slice(1).join(':').trim() : '';
  };
  return {
    cycleCount: valueFor('Cycle Count'),
    condition: valueFor('Condition'),
    maximumCapacity: valueFor('Maximum Capacity')
  };
}

function parseMemoryPressure(output) {
  const match = output.match(/System-wide memory free percentage:\s*(\d+)%/i);
  return match ? Number(match[1]) : null;
}

function parseBatteryPercent(output) {
  const match = output.match(/(\d{1,3})%;/);
  return match ? `${match[1]}%` : '';
}

function parseWifiValue(output, label) {
  const pattern = new RegExp(`${label}:\\s*(.+)`, 'i');
  const match = output.match(pattern);
  return match ? match[1].trim() : '';
}

function parseRouteValue(output, label) {
  const pattern = new RegExp(`${label}:\\s*(.+)`, 'i');
  const match = output.match(pattern);
  return match ? match[1].trim() : '';
}

function parseProcessRows(output) {
  return output
    .split('\n')
    .slice(1)
    .map((line) => line.trim().split(/\s+/))
    .filter((parts) => parts.length >= 4)
    .slice(0, 8)
    .map((parts) => ({
      pid: parts[0],
      cpu: parts[1],
      memory: parts[2],
      command: parts.slice(3).join(' ')
    }));
}

function parseListeningPorts(output) {
  const ports = new Map();
  output
    .split('\n')
    .slice(1)
    .map((line) => line.trim())
    .filter(Boolean)
    .forEach((line) => {
      const parts = line.split(/\s+/);
      const command = parts[0] || 'unknown';
      const name = parts.slice(8).join(' ') || line;
      const match = name.match(/(?:TCP\s+)?(.+):(\d+)\s+\(LISTEN\)$/i);
      if (!match) return;
      const address = match[1];
      const port = Number(match[2]);
      if (!Number.isFinite(port)) return;
      const key = `${port}:${command}`;
      const localOnly = /\[?::1\]?|127\.0\.0\.1|localhost/i.test(address);
      const existing = ports.get(key);
      ports.set(key, {
        port,
        process: command,
        address,
        localOnly: existing ? existing.localOnly && localOnly : localOnly
      });
    });
  return [...ports.values()].sort((a, b) => a.port - b.port || a.process.localeCompare(b.process));
}

function parseWindowsListeningPorts(output) {
  return asArray(parseWindowsJson(output, []))
    .map((item) => ({
      port: Number(item.LocalPort),
      process: String(item.OwningProcess || 'unknown'),
      address: String(item.LocalAddress || ''),
      localOnly: /^(127\.|::1|localhost)$/i.test(String(item.LocalAddress || ''))
    }))
    .filter((item) => Number.isFinite(item.port))
    .filter((item, index, list) => list.findIndex((other) => other.port === item.port && other.process === item.process) === index)
    .sort((a, b) => a.port - b.port || a.process.localeCompare(b.process));
}

function scoreFinding(severity) {
  if (severity === 'critical') return 18;
  if (severity === 'warning') return 9;
  return 0;
}

function buildScanSummary({ platform, checks }) {
  const findings = checks.filter((check) => check.severity !== 'ok');
  const penalty = findings.reduce((sum, finding) => sum + scoreFinding(finding.severity), 0);
  const score = Math.max(0, 100 - penalty);
  return {
    score,
    status: score >= 85 ? 'Good health' : score >= 65 ? 'Needs attention' : 'At risk',
    platform,
    findings: findings.map((item) => item.title),
    recommendations: findings.slice(0, 6).map((item) => item.recommendation).filter(Boolean)
  };
}

function parseWindowsJson(output, fallback = null) {
  try {
    if (!output) return fallback;
    return JSON.parse(output);
  } catch {
    return fallback;
  }
}

function asArray(value) {
  if (!value) return [];
  return Array.isArray(value) ? value : [value];
}

function normalizeWindowsStatus(value) {
  return String(value || '').trim().toLowerCase();
}

function isWindowsServiceRunning(item) {
  return normalizeWindowsStatus(item?.Status) === 'running' || Number(item?.Status) === 4;
}

function parseWindowsWifi(output) {
  const valueFor = (label) => {
    const match = output.match(new RegExp(`^\\s*${label}\\s*:\\s*(.+)$`, 'im'));
    return match ? match[1].trim() : '';
  };
  return {
    ssid: valueFor('SSID'),
    state: valueFor('State'),
    signal: valueFor('Signal'),
    radioType: valueFor('Radio type'),
    receiveRate: valueFor('Receive rate \\(Mbps\\)'),
    transmitRate: valueFor('Transmit rate \\(Mbps\\)')
  };
}

function extractLines(output, patterns) {
  return output
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => patterns.some((pattern) => pattern.test(line)));
}

async function scanMac() {
  const [
    swVers,
    kernel,
    hardware,
    diskData,
    diskInfoRaw,
    filevault,
    firewall,
    battery,
    power,
    uptime,
    memoryPressure,
    gatekeeper,
    sip,
    dns,
    route,
    internet,
    oncall,
    processes,
    updates,
    wifi,
    loginItems,
    launchAgents,
    m365Apps,
    profiles,
    listeningPortsRaw
  ] = await Promise.all([
    runCommand('/usr/bin/sw_vers'),
    runCommand('/usr/bin/uname', ['-a']),
    runCommand('/usr/sbin/system_profiler', ['SPHardwareDataType', '-detailLevel', 'mini'], 12000),
    runCommand('/bin/df', ['-k', '/System/Volumes/Data']),
    runCommand('/usr/sbin/diskutil', ['info', '/System/Volumes/Data']),
    runCommand('/usr/bin/fdesetup', ['status']),
    runCommand('/usr/libexec/ApplicationFirewall/socketfilterfw', ['--getglobalstate']),
    runCommand('/usr/bin/pmset', ['-g', 'batt']),
    runCommand('/usr/sbin/system_profiler', ['SPPowerDataType', '-detailLevel', 'mini'], 12000),
    runCommand('/usr/bin/uptime'),
    runCommand('/usr/bin/memory_pressure'),
    runCommand('/usr/sbin/spctl', ['--status']),
    runCommand('/usr/bin/csrutil', ['status']),
    runCommand('/usr/sbin/scutil', ['--dns']),
    runCommand('/sbin/route', ['-n', 'get', 'default']),
    runCommand('/usr/bin/nc', ['-vz', '-G', '5', '1.1.1.1', '443']),
    runCommand('/usr/bin/nc', ['-vz', '-G', '5', 'oncalltechsa.co.za', '443']),
    runCommand('/bin/ps', ['ax', '-o', 'pid,%cpu,%mem,comm', '-r']),
    runCommand('/usr/sbin/softwareupdate', ['-l'], 20000),
    runCommand('/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport', ['-I']),
    runCommand('/usr/bin/osascript', ['-e', 'tell application "System Events" to get the name of every login item'], 10000),
    runCommand('/bin/ls', ['/Library/LaunchAgents', '/Library/LaunchDaemons'], 8000),
    runCommand('/bin/sh', ['-lc', 'for app in "/Applications/Microsoft Outlook.app" "/Applications/Microsoft Teams.app" "/Applications/OneDrive.app" "/Applications/Microsoft Word.app" "/Applications/Microsoft Excel.app" "/Applications/Microsoft PowerPoint.app"; do [ -d "$app" ] && basename "$app"; done'], 8000),
    runCommand('/usr/bin/profiles', ['status', '-type', 'enrollment'], 8000),
    runCommand('/usr/sbin/lsof', ['-nP', '-iTCP', '-sTCP:LISTEN'], 8000)
  ]);

  const totalMemGb = bytesToGb(os.totalmem());
  const freeMemGb = bytesToGb(os.freemem());
  const diskInfo = parseMacDisk(diskData.output);
  const diskMeta = parseDiskutilInfo(diskInfoRaw.output || '');
  const diskUsed = Number(String(diskInfo?.usedPercent || '0').replace('%', ''));
  const fileVaultOn = filevault.ok && /is on/i.test(filevault.output);
  const firewallOn = /enabled/i.test(firewall.output);
  const batteryText = battery.output || 'No battery information available';
  const powerInfo = parseMacPower(power.output || '');
  const batteryPercent = parseBatteryPercent(batteryText);
  const memoryFreePercent = parseMemoryPressure(memoryPressure.output || '');
  const gatekeeperOn = /enabled/i.test(gatekeeper.output);
  const sipOn = /enabled/i.test(sip.output);
  const dnsReady = /nameserver/i.test(dns.output);
  const routeReady = /gateway:/i.test(route.output) && /interface:/i.test(route.output);
  const internetReady = internet.ok || /succeeded/i.test(`${internet.output} ${internet.error}`);
  const oncallReady = oncall.ok || /succeeded/i.test(`${oncall.output} ${oncall.error}`);
  const topProcesses = parseProcessRows(processes.output || '');
  const updateDetail = updates.output || updates.error;
  const updatesOk = /No new software available/i.test(updateDetail);
  const updatesPending = /Software Update found|recommended|Title:/i.test(updateDetail);
  const wifiLines = extractLines(wifi.output || '', [/SSID:/, /agrCtlRSSI:/, /lastTxRate:/, /channel:/]);
  const wifiSummary = [
    parseWifiValue(wifi.output || '', 'SSID') && `SSID ${parseWifiValue(wifi.output || '', 'SSID')}`,
    parseWifiValue(wifi.output || '', 'agrCtlRSSI') && `RSSI ${parseWifiValue(wifi.output || '', 'agrCtlRSSI')} dBm`,
    parseWifiValue(wifi.output || '', 'lastTxRate') && `TX ${parseWifiValue(wifi.output || '', 'lastTxRate')} Mbps`
  ].filter(Boolean).join(' · ');
  const routeSummary = [
    parseRouteValue(route.output || '', 'gateway') && `Gateway ${parseRouteValue(route.output || '', 'gateway')}`,
    parseRouteValue(route.output || '', 'interface') && `Interface ${parseRouteValue(route.output || '', 'interface')}`
  ].filter(Boolean).join(' · ');
  const loginCount = loginItems.ok && loginItems.output ? loginItems.output.split(',').map((item) => item.trim()).filter(Boolean).length : 0;
  const launchCount = launchAgents.output ? launchAgents.output.split('\n').map((item) => item.trim()).filter(Boolean).length : 0;
  const installedM365 = (m365Apps.output || '').split('\n').filter((line) => /\.app$/i.test(line)).map((line) => line.trim());
  const enrolled = /enrolled: yes/i.test(profiles.output);
  const listeningPorts = parseListeningPorts(listeningPortsRaw.output || '');
  const exposedListeningPorts = listeningPorts.filter((item) => !item.localOnly);
  const hardwareSummary = hardware.output
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => /Model Name|Model Identifier|Chip|Total Number of Cores|Memory:/.test(line))
    .join(' · ');

  const checks = [
    evidenceCheck({
      id: 'os',
      title: 'macOS version detected',
      severity: 'ok',
      category: 'System',
      detail: swVers.output || kernel.output,
      evidence: [kernel.output],
      recommendation: 'Keep macOS current with supported security updates.'
    }),
    evidenceCheck({
      id: 'updates',
      title: updatesOk ? 'No macOS updates pending' : updatesPending ? 'macOS updates appear pending' : 'macOS update status could not be confirmed',
      severity: updatesOk ? 'ok' : updatesPending ? 'warning' : 'warning',
      category: 'System',
      detail: updateDetail || 'Software update command returned no details.',
      evidence: extractLines(updateDetail || '', [/Title:/, /recommended/i, /No new software/i]).slice(0, 5),
      recommendation: 'Install supported macOS security updates before handling sensitive business work.'
    }),
    evidenceCheck({
      id: 'cpu-memory',
      title: memoryFreePercent !== null && memoryFreePercent < 15 ? 'Memory pressure needs attention' : 'Hardware and memory collected',
      severity: totalMemGb < 8 || (memoryFreePercent !== null && memoryFreePercent < 15) ? 'warning' : 'ok',
      category: 'Performance',
      detail: `${hardwareSummary || os.cpus()[0]?.model || 'Hardware detected'} · ${totalMemGb}GB RAM · ${freeMemGb}GB free memory`,
      evidence: [
        memoryFreePercent !== null ? `System-wide memory free percentage: ${memoryFreePercent}%` : '',
        memoryPressure.output.split('\n').find((line) => line.includes('Swapouts')) || ''
      ],
      recommendation: 'Close unused apps and review startup/background apps if the device feels slow.'
    }),
    evidenceCheck({
      id: 'disk',
      title: diskUsed >= 90 ? 'Disk space critically low' : diskUsed >= 80 ? 'Disk space needs attention' : 'Disk space healthy',
      severity: diskUsed >= 90 ? 'critical' : diskUsed >= 80 ? 'warning' : 'ok',
      category: 'Storage',
      detail: diskInfo ? `${diskInfo.usedPercent} used · ${diskInfo.freeGb}GB free on ${diskInfo.filesystem}` : 'Disk usage unavailable',
      evidence: [
        diskData.output,
        diskMeta.smartStatus ? `SMART Status: ${diskMeta.smartStatus}` : '',
        diskMeta.totalSpace ? `Container total: ${diskMeta.totalSpace}` : '',
        diskMeta.freeSpace ? `Container free: ${diskMeta.freeSpace}` : ''
      ],
      recommendation: diskUsed >= 90
        ? 'Free at least 30-50GB before major updates or remote work. Review Downloads, videos, old installers, cache, and OneDrive local sync.'
        : 'Keep at least 15-20% storage free for updates, app stability, and performance.'
    }),
    evidenceCheck({
      id: 'filevault',
      title: fileVaultOn ? 'FileVault encryption enabled' : filevault.ok ? 'FileVault encryption disabled' : 'FileVault status could not be verified',
      severity: fileVaultOn ? 'ok' : 'warning',
      category: 'Security',
      detail: filevault.output || filevault.error,
      evidence: [diskMeta.fileVault ? `Disk FileVault: ${diskMeta.fileVault}` : ''],
      recommendation: 'Enable FileVault to protect business data if the Mac is lost or stolen.'
    }),
    evidenceCheck({
      id: 'firewall',
      title: firewallOn ? 'macOS firewall enabled' : 'macOS firewall disabled',
      severity: firewallOn ? 'ok' : 'warning',
      category: 'Security',
      detail: firewall.output || firewall.error,
      recommendation: 'Enable the macOS firewall for better protection on untrusted networks.'
    }),
    evidenceCheck({
      id: 'gatekeeper',
      title: gatekeeperOn ? 'Gatekeeper assessments enabled' : 'Gatekeeper assessments disabled',
      severity: gatekeeperOn ? 'ok' : 'critical',
      category: 'Security',
      detail: gatekeeper.output || gatekeeper.error,
      recommendation: 'Keep Gatekeeper enabled so macOS can block untrusted or unsigned apps.'
    }),
    evidenceCheck({
      id: 'sip',
      title: sipOn ? 'System Integrity Protection enabled' : 'System Integrity Protection disabled',
      severity: sipOn ? 'ok' : 'critical',
      category: 'Security',
      detail: sip.output || sip.error,
      recommendation: 'Keep SIP enabled unless a specialist-approved workflow requires otherwise.'
    }),
    evidenceCheck({
      id: 'mdm',
      title: enrolled ? 'Device management enrolment detected' : 'No MDM enrolment detected',
      severity: enrolled ? 'ok' : 'warning',
      category: 'Security',
      detail: profiles.output || profiles.error,
      recommendation: 'For business devices, consider MDM enrolment through Intune or Apple Business Manager for policy enforcement.'
    }),
    evidenceCheck({
      id: 'listening-ports',
      title: listeningPorts.length > 5 ? `Listening port exposure exceeds policy (${listeningPorts.length}/5)` : `Listening port exposure within policy (${listeningPorts.length}/5)`,
      severity: exposedListeningPorts.length > 5 ? 'critical' : listeningPorts.length > 5 ? 'warning' : 'ok',
      category: 'Security',
      detail: listeningPorts.length
        ? `${listeningPorts.length} unique listening TCP port(s) detected; ${exposedListeningPorts.length} not loopback-only.`
        : 'No listening TCP ports detected by lsof.',
      evidence: listeningPorts.slice(0, 8).map((item) => `${item.process} ${item.localOnly ? 'local' : 'exposed'} ${item.address}:${item.port}`),
      recommendation: 'Keep listening services to 5 or fewer. Disable unused sharing, remote access, development servers, or vendor background services if they are not required.'
    }),
    evidenceCheck({
      id: 'battery',
      title: /Normal/i.test(powerInfo.condition) || !powerInfo.condition ? `Battery ${batteryPercent || 'status'} collected` : 'Battery health needs review',
      severity: /Service Battery|Replace/i.test(`${batteryText} ${powerInfo.condition}`) ? 'warning' : 'ok',
      category: 'Hardware',
      detail: batteryText,
      evidence: [
        batteryPercent ? `Battery charge: ${batteryPercent}` : '',
        powerInfo.cycleCount ? `Cycle Count: ${powerInfo.cycleCount}` : '',
        powerInfo.condition ? `Condition: ${powerInfo.condition}` : '',
        powerInfo.maximumCapacity ? `Maximum Capacity: ${powerInfo.maximumCapacity}` : ''
      ],
      recommendation: 'Review battery condition if macOS reports service or replacement warnings.'
    }),
    evidenceCheck({
      id: 'network',
      title: internetReady && oncallReady ? 'Network and service connectivity verified' : 'Network connectivity needs review',
      severity: internetReady && oncallReady && dnsReady && routeReady ? 'ok' : 'warning',
      category: 'Network',
      detail: `DNS ${dnsReady ? 'reachable' : 'not confirmed'} · default route ${routeReady ? 'detected' : 'not confirmed'} · internet ${internetReady ? 'ok' : 'failed'} · OnCallTechSA ${oncallReady ? 'ok' : 'failed'}`,
      evidence: [
        routeSummary || route.output.split('\n').filter((line) => /gateway|interface/.test(line)).join(' · '),
        dns.output.split('\n').find((line) => line.includes('nameserver')) || '',
        wifiSummary || wifiLines.join(' · '),
        internet.output || internet.error,
        oncall.output || oncall.error
      ],
      recommendation: 'If web apps are slow, compare this result with another network before changing device settings.'
    }),
    evidenceCheck({
      id: 'startup',
      title: loginCount + launchCount > 45 ? 'Startup/background load is high' : 'Startup/background load reviewed',
      severity: loginCount + launchCount > 45 ? 'warning' : 'ok',
      category: 'Performance',
      detail: `${loginCount} login item(s) · ${launchCount} system launch agent/daemon entries detected`,
      evidence: [
        loginItems.output ? `Login items: ${loginItems.output}` : loginItems.error,
        launchAgents.output ? launchAgents.output.split('\n').slice(0, 6).join(' · ') : launchAgents.error
      ],
      recommendation: 'Remove unnecessary login items and background tools if startup or sign-in feels slow.'
    }),
    evidenceCheck({
      id: 'm365',
      title: installedM365.length ? 'Microsoft 365 desktop apps detected' : 'Microsoft 365 desktop apps not detected',
      severity: installedM365.length ? 'ok' : 'warning',
      category: 'Microsoft 365',
      detail: installedM365.length ? installedM365.join(', ') : 'Outlook, Teams, OneDrive, or Word were not found in /Applications.',
      recommendation: 'Install and sign in to the required Microsoft 365 apps for business support readiness.'
    }),
    evidenceCheck({
      id: 'uptime',
      title: 'System uptime collected',
      severity: 'ok',
      category: 'Performance',
      detail: uptime.output,
      recommendation: 'Restart periodically when troubleshooting performance or update issues.'
    }),
    evidenceCheck({
      id: 'top-processes',
      title: 'Top resource usage captured',
      severity: topProcesses.some((item) => Number(item.cpu) > 80) ? 'warning' : 'ok',
      category: 'Performance',
      detail: topProcesses.length ? topProcesses.map((item) => `${item.command} (${item.cpu}% CPU, ${item.memory}% MEM)`).join('\n') : 'Process usage unavailable',
      evidence: topProcesses.map((item) => `PID ${item.pid}: ${item.command}`),
      recommendation: 'If the Mac feels slow, close the heaviest apps and rerun the scan after a restart.'
    })
  ];

  return {
    generatedAt: new Date().toISOString(),
    platform: 'macOS',
    device: {
      hostname: os.hostname(),
      arch: os.arch(),
      release: os.release(),
      cpu: hardwareSummary || os.cpus()[0]?.model || '',
      totalMemoryGb: totalMemGb,
      freeMemoryGb: freeMemGb
    },
    disk: { ...diskInfo, ...diskMeta },
    proof: {
      battery: batteryPercent || powerInfo.maximumCapacity || 'Battery checked',
      network: [routeSummary, wifiSummary].filter(Boolean).join(' · ') || (internetReady ? 'Internet reachable' : 'Network needs review'),
      security: [
        fileVaultOn ? 'FileVault on' : 'FileVault off',
        firewallOn ? 'Firewall on' : 'Firewall off',
        gatekeeperOn ? 'Gatekeeper on' : 'Gatekeeper off',
        sipOn ? 'SIP on' : 'SIP off',
        `${listeningPorts.length}/5 listening ports`
      ].join(' · '),
      updates: updatesOk ? 'No updates pending' : updatesPending ? 'Updates pending' : 'Update status checked',
      apps: installedM365.length ? installedM365.join(', ') : 'Microsoft 365 apps not detected'
    },
    evidence: {
      scanType: 'read_only_native_desktop_scan',
      privacy: 'No private files, passwords, browser history, messages, emails, or document contents were read.',
      commands: [swVers, hardware, diskData, diskInfoRaw, filevault, firewall, battery, power, uptime, memoryPressure, gatekeeper, sip, dns, route, internet, oncall, processes, updates, wifi, loginItems, launchAgents, m365Apps, profiles, listeningPortsRaw]
        .map((item) => item.command)
        .filter(Boolean)
    },
    checks,
    summary: buildScanSummary({ platform: 'macOS', checks })
  };
}

async function scanWindows() {
  const ps = 'powershell.exe';
  const runPs = (command, timeout) => runCommand(ps, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], timeout);
  const [
    osInfo,
    computer,
    disk,
    defender,
    hotfixes,
    battery,
    firewall,
    bitlocker,
    startup,
    processes,
    network,
    dns,
    m365Apps,
    updates,
    updateServices,
    tpm,
    secureBoot,
    physicalDisks,
    wifi,
    pendingRestart,
    listeningPortsRaw
  ] = await Promise.all([
    runPs('Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber,LastBootUpTime,FreePhysicalMemory,TotalVisibleMemorySize | ConvertTo-Json -Compress'),
    runPs('Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer,Model,TotalPhysicalMemory,PartOfDomain,Domain,Workgroup | ConvertTo-Json -Compress'),
    runPs("Get-CimInstance Win32_LogicalDisk | Where-Object {$_.DeviceID -eq 'C:'} | Select-Object DeviceID,Size,FreeSpace,FileSystem,VolumeName | ConvertTo-Json -Compress"),
    runPs('Get-MpComputerStatus | Select-Object AMServiceEnabled,AntivirusEnabled,RealTimeProtectionEnabled,AntivirusSignatureLastUpdated,QuickScanAge,FullScanAge,NISEnabled,IoavProtectionEnabled | ConvertTo-Json -Compress'),
    runPs('Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1 HotFixID,InstalledOn,Description | ConvertTo-Json -Compress'),
    runPs('Get-CimInstance Win32_Battery | Select-Object EstimatedChargeRemaining,BatteryStatus,Status | ConvertTo-Json -Compress'),
    runPs('Get-NetFirewallProfile | Select-Object Name,Enabled | ConvertTo-Json -Compress'),
    runPs('Get-BitLockerVolume -MountPoint C: -ErrorAction SilentlyContinue | Select-Object ProtectionStatus,VolumeStatus,EncryptionPercentage | ConvertTo-Json -Compress'),
    runPs('Get-CimInstance Win32_StartupCommand | Select-Object Name,Command,Location | ConvertTo-Json -Compress'),
    runPs('Get-Process | Sort-Object CPU -Descending | Select-Object -First 8 ProcessName,CPU,WS | ConvertTo-Json -Compress'),
    runPs('Test-NetConnection 1.1.1.1 -Port 443 | Select-Object ComputerName,TcpTestSucceeded,RemoteAddress | ConvertTo-Json -Compress'),
    runPs('Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -First 4 InterfaceAlias,ServerAddresses | ConvertTo-Json -Compress'),
    runPs('Get-ChildItem "C:\\\\Program Files\\\\Microsoft Office","$env:LOCALAPPDATA\\\\Microsoft\\\\Teams","$env:ProgramFiles\\\\Microsoft OneDrive" -ErrorAction SilentlyContinue | Select-Object -First 8 Name,FullName | ConvertTo-Json -Compress'),
    runPs('Get-CimInstance -Namespace root/ccm/ClientSDK -ClassName CCM_SoftwareUpdate -ErrorAction SilentlyContinue | Measure-Object | Select-Object Count | ConvertTo-Json -Compress'),
    runPs('Get-Service wuauserv,bits -ErrorAction SilentlyContinue | Select-Object Name,Status,StartType | ConvertTo-Json -Compress'),
    runPs('Get-Tpm -ErrorAction SilentlyContinue | Select-Object TpmPresent,TpmReady,TpmEnabled,TpmActivated | ConvertTo-Json -Compress'),
    runPs('try { [pscustomobject]@{SecureBootEnabled = Confirm-SecureBootUEFI} } catch { [pscustomobject]@{SecureBootEnabled = $null; Error = $_.Exception.Message} } | ConvertTo-Json -Compress'),
    runPs('Get-PhysicalDisk -ErrorAction SilentlyContinue | Select-Object FriendlyName,HealthStatus,OperationalStatus,Size | ConvertTo-Json -Compress'),
    runCommand('netsh.exe', ['wlan', 'show', 'interfaces']),
    runPs("[pscustomobject]@{ComponentBasedServicing = Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending'; WindowsUpdate = Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired'} | ConvertTo-Json -Compress"),
    runPs('Get-NetTCPConnection -State Listen | Select-Object LocalAddress,LocalPort,OwningProcess | Sort-Object LocalPort,OwningProcess -Unique | ConvertTo-Json -Compress')
  ]);

  const osJson = parseWindowsJson(osInfo.output, {});
  const computerJson = parseWindowsJson(computer.output, {});
  const diskJson = parseWindowsJson(disk.output, {});
  const defenderJson = parseWindowsJson(defender.output, {});
  const hotfixJson = parseWindowsJson(hotfixes.output, {});
  const batteryJson = parseWindowsJson(battery.output, null);
  const firewallJson = parseWindowsJson(firewall.output, []);
  const bitlockerJson = parseWindowsJson(bitlocker.output, {});
  const startupJson = parseWindowsJson(startup.output, []);
  const processJson = parseWindowsJson(processes.output, []);
  const networkJson = parseWindowsJson(network.output, {});
  const m365Json = parseWindowsJson(m365Apps.output, []);
  const updateJson = parseWindowsJson(updates.output, {});
  const updateServicesJson = parseWindowsJson(updateServices.output, []);
  const tpmJson = parseWindowsJson(tpm.output, {});
  const secureBootJson = parseWindowsJson(secureBoot.output, {});
  const physicalDiskJson = parseWindowsJson(physicalDisks.output, []);
  const pendingRestartJson = parseWindowsJson(pendingRestart.output, {});
  const listeningPorts = parseWindowsListeningPorts(listeningPortsRaw.output || '');
  const exposedListeningPorts = listeningPorts.filter((item) => !item.localOnly);
  const startupItems = asArray(startupJson);
  const firewallItems = asArray(firewallJson);
  const updateServiceItems = asArray(updateServicesJson);
  const physicalDiskItems = asArray(physicalDiskJson);
  const processItems = asArray(processJson);
  const m365Items = asArray(m365Json);
  const startupCount = startupItems.length;
  const firewallOk = firewallItems.length ? firewallItems.every((item) => Boolean(item.Enabled)) : Boolean(firewallJson.Enabled);
  const bitlockerStatus = String(bitlockerJson.ProtectionStatus || '');
  const bitlockerProtected = /On|1/i.test(bitlockerStatus);
  const defenderOk = Boolean(defenderJson.AntivirusEnabled && defenderJson.RealTimeProtectionEnabled);
  const totalGb = bytesToGb(diskJson.Size);
  const freeGb = bytesToGb(diskJson.FreeSpace);
  const usedPercent = totalGb ? Math.round(((totalGb - freeGb) / totalGb) * 100) : 0;
  const m365Count = m365Items.length;
  const batteryPercent = batteryJson?.EstimatedChargeRemaining !== undefined ? `${batteryJson.EstimatedChargeRemaining}%` : '';
  const freeMemoryGb = bytesToGb(Number(osJson.FreePhysicalMemory || 0) * 1024);
  const totalVisibleMemoryGb = bytesToGb(Number(osJson.TotalVisibleMemorySize || 0) * 1024) || bytesToGb(computerJson.TotalPhysicalMemory || os.totalmem());
  const freeMemoryPercent = totalVisibleMemoryGb ? Math.round((freeMemoryGb / totalVisibleMemoryGb) * 100) : null;
  const updateServicesRunning = updateServiceItems.length ? updateServiceItems.every(isWindowsServiceRunning) : false;
  const tpmReady = Boolean(tpmJson.TpmPresent && tpmJson.TpmReady);
  const secureBootReady = secureBootJson.SecureBootEnabled === true;
  const rebootPending = Boolean(pendingRestartJson.ComponentBasedServicing || pendingRestartJson.WindowsUpdate);
  const diskHealthOk = physicalDiskItems.length ? physicalDiskItems.every((item) => /healthy|ok/i.test(String(item.HealthStatus)) && !/lost|degraded|stopping/i.test(String(item.OperationalStatus))) : true;
  const wifiInfo = parseWindowsWifi(wifi.output || '');
  const networkProof = [
    networkJson.TcpTestSucceeded ? `Internet reachable · ${networkJson.RemoteAddress || '1.1.1.1'}` : 'Internet reachability failed',
    wifiInfo.ssid ? `Wi-Fi ${wifiInfo.ssid}` : '',
    wifiInfo.signal ? `Signal ${wifiInfo.signal}` : ''
  ].filter(Boolean).join(' · ');
  const domainProof = computerJson.PartOfDomain ? `Domain joined: ${computerJson.Domain || 'domain detected'}` : `Workgroup: ${computerJson.Workgroup || 'not domain joined'}`;

  const checks = [
    evidenceCheck({
      id: 'os',
      title: 'Windows version detected',
      category: 'System',
      detail: `${osJson.Caption || 'Windows'} ${osJson.Version || ''} build ${osJson.BuildNumber || ''}`,
      evidence: [
        osJson.LastBootUpTime ? `Last boot: ${osJson.LastBootUpTime}` : '',
        domainProof
      ],
      recommendation: 'Keep Windows current with supported security updates.'
    }),
    evidenceCheck({
      id: 'cpu-memory',
      title: freeMemoryPercent !== null && freeMemoryPercent < 15 ? 'Memory pressure needs attention' : 'Hardware and memory collected',
      severity: totalVisibleMemoryGb < 8 || (freeMemoryPercent !== null && freeMemoryPercent < 15) ? 'warning' : 'ok',
      category: 'Performance',
      detail: `${computerJson.Manufacturer || ''} ${computerJson.Model || ''} · ${totalVisibleMemoryGb}GB RAM · ${freeMemoryGb || bytesToGb(os.freemem())}GB free memory`,
      evidence: [
        freeMemoryPercent !== null ? `Free memory: ${freeMemoryPercent}%` : '',
        domainProof
      ],
      recommendation: 'Review memory pressure and startup apps if the device feels slow.'
    }),
    evidenceCheck({
      id: 'disk',
      title: usedPercent >= 90 ? 'Disk space critically low' : usedPercent >= 80 ? 'Disk space needs attention' : 'Disk space healthy',
      severity: usedPercent >= 90 || !diskHealthOk ? 'critical' : usedPercent >= 80 ? 'warning' : 'ok',
      category: 'Storage',
      detail: `${usedPercent}% used · ${freeGb}GB free of ${totalGb}GB · ${diskJson.FileSystem || 'file system checked'}`,
      evidence: [
        diskJson.VolumeName ? `Volume: ${diskJson.VolumeName}` : '',
        physicalDiskItems.length ? physicalDiskItems.map((item) => `${item.FriendlyName || 'Disk'}: ${item.HealthStatus || 'health unknown'} / ${item.OperationalStatus || 'status unknown'}`).join(' · ') : physicalDisks.error
      ],
      recommendation: 'Free storage, remove temporary files, and review local sync/cache usage.'
    }),
    evidenceCheck({
      id: 'defender',
      title: defenderOk ? 'Antivirus protection active' : 'Antivirus protection needs review',
      severity: defenderOk ? 'ok' : 'critical',
      category: 'Security',
      detail: defender.ok ? JSON.stringify(defenderJson) : defender.error,
      evidence: [
        defenderJson.AntivirusSignatureLastUpdated ? `Signature updated: ${defenderJson.AntivirusSignatureLastUpdated}` : '',
        defenderJson.QuickScanAge !== undefined ? `Quick scan age: ${defenderJson.QuickScanAge}` : '',
        defenderJson.FullScanAge !== undefined ? `Full scan age: ${defenderJson.FullScanAge}` : ''
      ],
      recommendation: 'Enable Defender real-time protection or confirm approved endpoint security is active.'
    }),
    evidenceCheck({
      id: 'firewall',
      title: firewallOk ? 'Windows firewall enabled' : 'Windows firewall needs review',
      severity: firewallOk ? 'ok' : 'warning',
      category: 'Security',
      detail: firewall.ok ? JSON.stringify(firewallJson) : firewall.error,
      evidence: firewallItems.map((item) => `${item.Name}: ${item.Enabled ? 'enabled' : 'disabled'}`),
      recommendation: 'Enable Windows Firewall on domain, private, and public profiles.'
    }),
    evidenceCheck({
      id: 'bitlocker',
      title: bitlockerProtected ? 'BitLocker protection enabled' : 'BitLocker protection needs review',
      severity: bitlockerProtected ? 'ok' : 'warning',
      category: 'Security',
      detail: bitlocker.ok ? JSON.stringify(bitlockerJson) : bitlocker.error,
      evidence: [
        bitlockerJson.VolumeStatus ? `Volume status: ${bitlockerJson.VolumeStatus}` : '',
        bitlockerJson.EncryptionPercentage !== undefined ? `Encryption: ${bitlockerJson.EncryptionPercentage}%` : ''
      ],
      recommendation: 'Enable BitLocker on business laptops to protect data if the device is lost or stolen.'
    }),
    evidenceCheck({
      id: 'tpm-secureboot',
      title: tpmReady && secureBootReady ? 'TPM and Secure Boot ready' : 'TPM or Secure Boot needs review',
      severity: tpmReady && secureBootReady ? 'ok' : 'warning',
      category: 'Security',
      detail: JSON.stringify({ tpm: tpmJson, secureBoot: secureBootJson }),
      evidence: [
        tpm.ok ? `TPM present: ${Boolean(tpmJson.TpmPresent)} · ready: ${Boolean(tpmJson.TpmReady)}` : tpm.error,
        secureBoot.ok ? `Secure Boot: ${secureBootJson.SecureBootEnabled === true ? 'enabled' : 'not confirmed'}` : secureBoot.error
      ],
      recommendation: 'Enable TPM and Secure Boot where hardware supports it, especially for managed business laptops.'
    }),
    evidenceCheck({
      id: 'listening-ports',
      title: listeningPorts.length > 5 ? `Listening port exposure exceeds policy (${listeningPorts.length}/5)` : `Listening port exposure within policy (${listeningPorts.length}/5)`,
      severity: exposedListeningPorts.length > 5 ? 'critical' : listeningPorts.length > 5 ? 'warning' : 'ok',
      category: 'Security',
      detail: listeningPorts.length
        ? `${listeningPorts.length} unique listening TCP port(s) detected; ${exposedListeningPorts.length} not loopback-only.`
        : 'No listening TCP ports detected by PowerShell.',
      evidence: listeningPorts.slice(0, 8).map((item) => `PID ${item.process} ${item.localOnly ? 'local' : 'exposed'} ${item.address}:${item.port}`),
      recommendation: 'Keep listening services to 5 or fewer. Disable unused sharing, remote access, development servers, or vendor background services if they are not required.'
    }),
    evidenceCheck({
      id: 'updates',
      title: rebootPending ? 'Restart required after updates' : updateServicesRunning ? 'Windows update evidence collected' : 'Windows update services need review',
      severity: rebootPending || !updateServicesRunning || !hotfixes.ok ? 'warning' : 'ok',
      category: 'System',
      detail: hotfixes.ok ? JSON.stringify(hotfixJson) : hotfixes.error,
      evidence: [
        updateJson.Count !== undefined ? `Pending managed updates: ${updateJson.Count}` : '',
        updateServiceItems.map((item) => `${item.Name}: ${item.Status}`).join(' · '),
        rebootPending ? 'Pending restart detected' : 'No managed restart marker detected'
      ],
      recommendation: 'Run Windows Update if the latest hotfix is old or pending updates are reported.'
    }),
    evidenceCheck({
      id: 'startup',
      title: startupCount > 35 ? 'Startup load is high' : 'Startup load reviewed',
      severity: startupCount > 35 ? 'warning' : 'ok',
      category: 'Performance',
      detail: `${startupCount} startup command(s) detected`,
      recommendation: 'Disable unnecessary startup apps if sign-in or boot feels slow.'
    }),
    evidenceCheck({
      id: 'network',
      title: networkJson.TcpTestSucceeded ? 'Internet connectivity verified' : 'Network connectivity needs review',
      severity: networkJson.TcpTestSucceeded ? 'ok' : 'warning',
      category: 'Network',
      detail: network.ok ? JSON.stringify(networkJson) : network.error,
      evidence: [
        dns.output,
        wifiInfo.ssid ? `SSID ${wifiInfo.ssid} · ${wifiInfo.signal || 'signal unknown'} · ${wifiInfo.radioType || 'radio unknown'}` : wifi.error || 'No Wi-Fi interface evidence returned'
      ],
      recommendation: 'If cloud apps are slow, compare with another network and check DNS/VPN configuration.'
    }),
    evidenceCheck({
      id: 'm365',
      title: m365Count ? 'Microsoft application evidence detected' : 'Microsoft applications not detected',
      severity: m365Count ? 'ok' : 'warning',
      category: 'Microsoft 365',
      detail: m365Apps.ok ? JSON.stringify(m365Json) : m365Apps.error,
      recommendation: 'Install required Microsoft 365 desktop apps and OneDrive before onboarding users.'
    }),
    evidenceCheck({
      id: 'battery',
      title: batteryJson ? `Battery ${batteryPercent || 'status'} collected` : 'Battery status unavailable',
      severity: batteryJson ? 'ok' : 'warning',
      category: 'Hardware',
      detail: batteryJson ? JSON.stringify(batteryJson) : 'No battery object returned',
      recommendation: 'Review battery health if Windows reports low capacity or service warnings.'
    }),
    evidenceCheck({
      id: 'top-processes',
      title: 'Top process usage captured',
      severity: processItems.some((item) => Number(item.CPU) > 200) ? 'warning' : 'ok',
      category: 'Performance',
      detail: processes.ok ? JSON.stringify(processJson) : processes.error,
      evidence: processItems.map((item) => `${item.ProcessName}: CPU ${item.CPU || 0}, memory ${bytesToGb(item.WS)}GB`),
      recommendation: 'Close heavy unused apps and rerun the scan if performance is poor.'
    })
  ];

  return {
    generatedAt: new Date().toISOString(),
    platform: 'Windows',
    device: {
      hostname: os.hostname(),
      arch: os.arch(),
      release: os.release(),
      totalMemoryGb: totalVisibleMemoryGb,
      freeMemoryGb: freeMemoryGb || bytesToGb(os.freemem()),
      domain: computerJson.Domain || computerJson.Workgroup || ''
    },
    disk: { totalGb, freeGb, usedPercent },
    proof: {
      battery: batteryPercent || 'Battery checked',
      network: networkProof || 'Network checked',
      security: [
        defenderOk ? 'Defender on' : 'Defender needs review',
        firewallOk ? 'Firewall on' : 'Firewall needs review',
        bitlockerProtected ? 'BitLocker on' : 'BitLocker needs review',
        tpmReady ? 'TPM ready' : 'TPM needs review',
        secureBootReady ? 'Secure Boot on' : 'Secure Boot not confirmed',
        `${listeningPorts.length}/5 listening ports`
      ].join(' · '),
      updates: rebootPending ? 'Restart pending' : hotfixes.ok ? `Latest hotfix ${hotfixJson.HotFixID || 'checked'}` : 'Update status needs review',
      apps: m365Count ? 'Microsoft apps detected' : 'Microsoft apps not detected'
    },
    evidence: {
      scanType: 'read_only_native_desktop_scan',
      privacy: 'No private files, passwords, browser history, messages, emails, or document contents were read.',
      commands: [osInfo, computer, disk, defender, hotfixes, battery, firewall, bitlocker, startup, processes, network, dns, m365Apps, updates, updateServices, tpm, secureBoot, physicalDisks, wifi, pendingRestart, listeningPortsRaw]
        .map((item) => item.command)
        .filter(Boolean)
    },
    checks,
    summary: buildScanSummary({ platform: 'Windows', checks })
  };
}

async function runHealthScan() {
  if (process.platform === 'darwin') return await scanMac();
  if (process.platform === 'win32') return await scanWindows();
  const checks = [
    evidenceCheck({
      id: 'platform',
      title: 'Unsupported desktop scan platform',
      severity: 'warning',
      detail: `${process.platform} ${os.release()}`,
      recommendation: 'Windows and macOS scans are supported first. Linux support is planned.'
    })
  ];
  return {
    generatedAt: new Date().toISOString(),
    platform: process.platform,
    device: { hostname: os.hostname(), arch: os.arch(), release: os.release() },
    evidence: {
      scanType: 'read_only_native_desktop_scan',
      privacy: 'No private files, passwords, browser history, messages, emails, or document contents were read.'
    },
    checks,
    summary: buildScanSummary({ platform: process.platform, checks })
  };
}

module.exports = {
  runHealthScan,
  scanMac,
  scanWindows,
  runCommand
};

if (require.main === module) {
  runHealthScan()
    .then((scan) => {
      if (process.argv.includes('--summary')) {
        console.log(`${scan.platform} score ${scan.summary.score}/100 (${scan.summary.status})`);
        scan.checks.forEach((check) => console.log(`${check.severity.toUpperCase()} ${check.category}: ${check.title}`));
        return;
      }
      console.log(JSON.stringify(scan, null, 2));
    })
    .catch((error) => {
      console.error(error);
      process.exit(1);
    });
}
