/**
 * Function/Diagnostics/crash-summary.js
 * ---------------------------------------------------------------------------
 * BSOD / CRASH SUMMARY
 *
 * Recent blue screens with their bug check code, the memory dump files on
 * disk and application crash events. Diagnostic only - no dump is deleted or
 * uploaded anywhere.
 * ---------------------------------------------------------------------------
 */

'use strict';

const kit = require('../../core/kit');
const system = require('../../core/system');

/** The most common bug check codes in plain language. */
const BUGCHECKS = {
  '0x0000000a': 'IRQL_NOT_LESS_OR_EQUAL - a driver touched invalid memory',
  '0x0000001a': 'MEMORY_MANAGEMENT - memory or RAM problem',
  '0x0000001e': 'KMODE_EXCEPTION_NOT_HANDLED - driver exception',
  '0x00000024': 'NTFS_FILE_SYSTEM - file system or disk problem',
  '0x0000003b': 'SYSTEM_SERVICE_EXCEPTION - driver or system service',
  '0x00000050': 'PAGE_FAULT_IN_NONPAGED_AREA - faulty RAM or driver',
  '0x0000007e': 'SYSTEM_THREAD_EXCEPTION_NOT_HANDLED - driver crash',
  '0x0000009f': 'DRIVER_POWER_STATE_FAILURE - driver failed during sleep',
  '0x000000c2': 'BAD_POOL_CALLER - driver memory misuse',
  '0x000000d1': 'DRIVER_IRQL_NOT_LESS_OR_EQUAL - network or storage driver',
  '0x000000ef': 'CRITICAL_PROCESS_DIED - a required Windows process stopped',
  '0x00000133': 'DPC_WATCHDOG_VIOLATION - storage driver or firmware',
  '0x00000139': 'KERNEL_SECURITY_CHECK_FAILURE - corruption or driver bug',
  '0x0000012b': 'FAULTY_HARDWARE_CORRUPTED_PAGE - failing RAM',
  '0x00000119': 'VIDEO_SCHEDULER_INTERNAL_ERROR - graphics driver',
  '0x00000116': 'VIDEO_TDR_ERROR - graphics driver stopped responding',
};

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '$start = (Get-Date).AddDays(-180)',
  '[ordered]@{',
  '  bugchecks = @(Get-WinEvent -FilterHashtable @{ LogName = "System"; ProviderName = "Microsoft-Windows-WER-SystemErrorReporting"; StartTime = $start } -MaxEvents 40 | Select-Object TimeCreated, Id, @{n="Message";e={ $_.Message }});',
  '  kernelPower = @(Get-WinEvent -FilterHashtable @{ LogName = "System"; Id = 41; StartTime = $start } -MaxEvents 40 | Select-Object TimeCreated, Id, @{n="Message";e={ $_.Message }});',
  '  appCrashes = @(Get-WinEvent -FilterHashtable @{ LogName = "Application"; ProviderName = "Application Error"; StartTime = $start } -MaxEvents 40 | Select-Object TimeCreated, Id, @{n="Message";e={ $_.Message }});',
  '  dumps = @(Get-ChildItem -Path "$env:SystemRoot\\Minidump\\*.dmp", "$env:SystemRoot\\MEMORY.DMP" -ErrorAction SilentlyContinue | Select-Object FullName, Length, LastWriteTime);',
  '  settings = (Get-CimInstance Win32_OSRecoveryConfiguration | Select-Object -First 1 DebugInfoType, DebugFilePath, MiniDumpDirectory, AutoReboot)',
  '} | ConvertTo-Json -Depth 5 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 120000 });
  if (!result.ok || !result.data) return null;
  return {
    bugchecks: system.asArray(result.data.bugchecks),
    kernelPower: system.asArray(result.data.kernelPower),
    appCrashes: system.asArray(result.data.appCrashes),
    dumps: system.asArray(result.data.dumps),
    settings: result.data.settings || {},
  };
}

/** Pull "0x000000d1 (0x..., ...)" out of a WER message. */
function parseBugCheck(message) {
  const text = String(message || '');
  const code = text.match(/0x[0-9a-fA-F]{8}/);
  const params = text.match(/\((0x[^)]*)\)/);
  const dump = text.match(/([A-Z]:\\[^\s.]+\.[Dd][Mm][Pp])/);
  const key = code ? code[0].toLowerCase() : null;
  return {
    code: code ? code[0] : null,
    meaning: key && BUGCHECKS[key] ? BUGCHECKS[key] : null,
    parameters: params ? params[1] : null,
    dumpFile: dump ? dump[1] : null,
  };
}

/** "faulting application X, faulting module Y" out of an Application Error. */
function parseAppCrash(message) {
  const text = String(message || '').replace(/\s+/g, ' ');
  const app = text.match(/Faulting application name:\s*([^,]+)/i);
  const module = text.match(/faulting module name:\s*([^,]+)/i);
  const exception = text.match(/exception code:\s*(0x[0-9a-fA-F]+)/i);
  return {
    application: app ? app[1].trim() : null,
    module: module ? module[1].trim() : null,
    exception: exception ? exception[1] : null,
  };
}

module.exports = {
  name: 'Crash Summary',
  menuName: 'BSOD / Crash Summary',
  description: 'Recent blue screens with bug check codes, memory dumps and application crashes.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Diagnostics',
  order: 30,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['eventlog:read', 'filesystem:read(dumps)', 'filesystem:write(reports)'],
  tags: ['bsod', 'crash', 'bugcheck', 'dump', 'bluescreen'],
  documentation:
    'Collects the blue screens Windows recorded in the last six months with their bug check code, ' +
    'translates the common codes into plain language, lists the memory dump files on disk with their ' +
    'size and date, and shows recent application crashes with the faulting module. ' +
    'Diagnostic only: no dump file is deleted, modified or sent anywhere.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const data = await collect();
    return data ? { ok: true, message: `${data.bugchecks.length} bug check(s) recorded` } : { ok: false, message: 'crash query failed' };
  },

  async execute(ctx) {
    const { ui } = ctx;
    kit.header('BSOD / Crash Summary', ['Blue screens, dump files and application crashes.', 'Diagnostic only.']);
    if (!kit.requireWindows('The crash summary')) return { ok: false, reason: 'unsupported-platform' };

    const spin = ui.spinner('Reading crash information...');
    const data = await collect();
    spin.stop();
    if (!data) return kit.noData('crash information');

    const crashes = data.bugchecks.map((event) => ({
      time: system.formatWmiDate(event.TimeCreated),
      ...parseBugCheck(event.Message),
    }));

    ui.blank();
    ui.section('SUMMARY');
    ui.blank();
    kit.kv('Blue Screens (180 days)', String(crashes.length), crashes.length ? 'brightRed' : 'brightGreen');
    kit.kv('Unexpected Power Events', String(data.kernelPower.length), data.kernelPower.length ? 'brightYellow' : 'brightGreen');
    kit.kv('Application Crashes', String(data.appCrashes.length));
    kit.kv('Dump Files On Disk', String(data.dumps.length));
    kit.kv(
      'Dump Configuration',
      system.clean(data.settings.DebugInfoType) !== null ? `type ${data.settings.DebugInfoType}` : null
    );
    kit.kv('Automatic Restart', data.settings.AutoReboot === undefined ? null : data.settings.AutoReboot ? 'Enabled' : 'Disabled');

    ui.blank();
    ui.section('RECENT SYSTEM CRASHES');
    ui.blank();
    if (!crashes.length) {
      ui.println('  ' + ui.c.brightGreen('No blue screen was recorded in the last 180 days.'));
    } else {
      for (const crash of crashes.slice(0, 10)) {
        ui.println(
          '  ' + ui.c.brightRed(ui.glyphs().bullet + ' ') +
            ui.c.brightWhite(ui.padEnd(crash.time || '?', 20)) +
            ui.c.brightYellow(crash.code || 'unknown code')
        );
        if (crash.meaning) ui.println('      ' + ui.c.gray(ui.truncate(crash.meaning, ui.getWidth() - 6)));
        if (crash.dumpFile) ui.println('      ' + ui.c.gray(`Dump: ${ui.truncate(crash.dumpFile, ui.getWidth() - 12)}`));
      }
      if (crashes.length > 10) {
        ui.println('  ' + ui.c.gray(`... and ${crashes.length - 10} more`));
      }
    }

    if (data.dumps.length) {
      ui.blank();
      ui.section('DUMP FILES');
      ui.blank();
      kit.table(
        ['FILE', 'SIZE', 'CREATED'],
        data.dumps.map((dump) => [
          ui.c.brightWhite(ui.truncate(String(dump.FullName).split('\\').pop(), 30)),
          ui.c.brightCyan(system.formatBytes(dump.Length)),
          ui.c.gray(system.formatWmiDate(dump.LastWriteTime) || '-'),
        ]),
        { align: ['left', 'right', 'left'], limit: 12 }
      );
      ui.blank();
      ui.println('  ' + ui.c.gray('Dump files can be opened with WinDbg or shared with support - they are kept as-is.'));
    }

    if (data.appCrashes.length) {
      ui.blank();
      ui.section('APPLICATION CRASHES');
      ui.blank();
      const parsed = data.appCrashes.map((event) => ({
        time: system.formatWmiDate(event.TimeCreated),
        ...parseAppCrash(event.Message),
      }));
      kit.table(
        ['APPLICATION', 'FAULTING MODULE', 'WHEN'],
        parsed.map((crash) => [
          ui.c.brightWhite(ui.truncate(crash.application || 'Unknown', 24)),
          ui.c.gray(ui.truncate(crash.module || '-', 24)),
          ui.c.gray(crash.time || '-'),
        ]),
        { limit: 12 }
      );
    }

    ui.blank();
    if (crashes.length >= 3) {
      ui.warning('Several blue screens were recorded - check the RAM, storage and graphics drivers.');
    } else if (crashes.length) {
      ui.info('An occasional blue screen usually points at a driver that has since been updated.');
    } else {
      ui.completed('This system has been stable.');
    }

    await kit.offerExport('Crash Summary Report', 'CrashSummary', (report) => {
      const section = report.section('SUMMARY');
      section.add('Blue Screens (180 days)', String(crashes.length));
      section.add('Unexpected Power Events', String(data.kernelPower.length));
      section.add('Application Crashes', String(data.appCrashes.length));
      section.add('Dump Files On Disk', String(data.dumps.length));

      const list = report.section('SYSTEM CRASHES');
      crashes.forEach((crash, index) => {
        const group = list.group(`${index + 1}. ${crash.time || 'unknown time'}`);
        group.add('Bug Check Code', crash.code);
        group.add('Meaning', crash.meaning);
        group.add('Parameters', crash.parameters);
        group.add('Dump File', crash.dumpFile);
      });

      const dumps = report.section('DUMP FILES');
      data.dumps.forEach((dump, index) => {
        dumps.add(`${index + 1}. ${dump.FullName}`, `${system.formatBytes(dump.Length)} - ${system.formatWmiDate(dump.LastWriteTime)}`);
      });

      const apps = report.section('APPLICATION CRASHES');
      data.appCrashes.slice(0, 25).forEach((event, index) => {
        const crash = parseAppCrash(event.Message);
        apps.add(
          `${index + 1}. ${crash.application || 'Unknown'}`,
          `${system.formatWmiDate(event.TimeCreated)} - module ${crash.module || '?'} - ${crash.exception || ''}`
        );
      });
    });

    return kit.done('Crash summary finished.');
  },
};
