/**
 * Function/Diagnostics/resource-alert.js
 * ---------------------------------------------------------------------------
 * HIGH RESOURCE ALERT
 *
 * Watches CPU, memory and disk usage against thresholds and raises an alert
 * when one of them is exceeded, with the processes responsible.
 * Read-only: nothing is ever terminated.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

const SAMPLE = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '$cpu = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average',
  '$osi = Get-CimInstance Win32_OperatingSystem',
  '$disk = Get-CimInstance Win32_PerfFormattedData_PerfDisk_PhysicalDisk | Where-Object { $_.Name -eq "_Total" } | Select-Object -First 1 PercentDiskTime',
  '$vol = @(Get-CimInstance Win32_LogicalDisk -Filter "DriveType = 3" | Select-Object DeviceID, Size, FreeSpace)',
  '$topCpu = @(Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 @{n="Name";e={$_.ProcessName}}, Id, @{n="Cpu";e={[math]::Round($_.CPU,1)}}, @{n="Memory";e={$_.WorkingSet64}})',
  '$topMem = @(Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 @{n="Name";e={$_.ProcessName}}, Id, @{n="Memory";e={$_.WorkingSet64}})',
  '[ordered]@{',
  '  cpu = $cpu;',
  '  memTotal = $osi.TotalVisibleMemorySize;',
  '  memFree = $osi.FreePhysicalMemory;',
  '  diskBusy = $disk.PercentDiskTime;',
  '  volumes = $vol;',
  '  topCpu = $topCpu;',
  '  topMem = $topMem',
  '} | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

async function sample() {
  const result = await system.runPowerShellJson(SAMPLE, { timeoutMs: 40000 });
  if (!result.ok || !result.data) return null;
  const data = result.data;
  const memTotal = Number(data.memTotal) * 1024 || os.totalmem();
  const memFree = Number(data.memFree) * 1024 || os.freemem();
  return {
    cpu: Number(data.cpu),
    memTotal,
    memFree,
    memPercent: memTotal ? Math.round(((memTotal - memFree) / memTotal) * 100) : 0,
    diskBusy: Number(data.diskBusy),
    volumes: system.asArray(data.volumes),
    topCpu: system.asArray(data.topCpu),
    topMem: system.asArray(data.topMem),
  };
}

/** Compare one sample against the thresholds. */
function evaluate(data, thresholds) {
  const alerts = [];

  if (Number.isFinite(data.cpu) && data.cpu > thresholds.cpu) {
    alerts.push({
      resource: 'CPU',
      value: `${Math.round(data.cpu)} %`,
      threshold: `${thresholds.cpu} %`,
      detail: data.topCpu.slice(0, 3).map((p) => `${p.Name} (${p.Cpu}s CPU)`).join(', '),
    });
  }

  if (data.memPercent > thresholds.memory) {
    alerts.push({
      resource: 'Memory',
      value: `${data.memPercent} %`,
      threshold: `${thresholds.memory} %`,
      detail: data.topMem.slice(0, 3).map((p) => `${p.Name} (${system.formatBytes(p.Memory)})`).join(', '),
    });
  }

  if (Number.isFinite(data.diskBusy) && data.diskBusy > thresholds.diskBusy) {
    alerts.push({
      resource: 'Disk activity',
      value: `${Math.round(data.diskBusy)} %`,
      threshold: `${thresholds.diskBusy} %`,
      detail: 'the disk is saturated - copies and installs will feel slow',
    });
  }

  for (const volume of data.volumes) {
    const size = Number(volume.Size) || 0;
    const free = Number(volume.FreeSpace) || 0;
    const usedPercent = size ? Math.round(((size - free) / size) * 100) : 0;
    if (usedPercent > thresholds.diskUsage) {
      alerts.push({
        resource: `Disk ${volume.DeviceID}`,
        value: `${usedPercent} % used`,
        threshold: `${thresholds.diskUsage} %`,
        detail: `${system.formatBytes(free)} free of ${system.formatBytes(size)}`,
      });
    }
  }

  return alerts;
}

function renderSample(ui, data, thresholds) {
  ui.section('CURRENT USAGE');
  ui.blank();
  ui.println(
    '  ' + ui.c.brightWhite(ui.padEnd('CPU', 12)) +
      ui.progressBar(Number.isFinite(data.cpu) ? data.cpu : 0, 100, 24) +
      ui.c.gray(`  limit ${thresholds.cpu}%`)
  );
  ui.println(
    '  ' + ui.c.brightWhite(ui.padEnd('Memory', 12)) +
      ui.progressBar(data.memPercent, 100, 24) +
      ui.c.gray(`  limit ${thresholds.memory}%`)
  );
  if (Number.isFinite(data.diskBusy)) {
    ui.println(
      '  ' + ui.c.brightWhite(ui.padEnd('Disk busy', 12)) +
        ui.progressBar(Math.min(100, data.diskBusy), 100, 24) +
        ui.c.gray(`  limit ${thresholds.diskBusy}%`)
    );
  }
  ui.blank();
  for (const volume of data.volumes) {
    const size = Number(volume.Size) || 0;
    const free = Number(volume.FreeSpace) || 0;
    ui.println(
      '  ' + ui.c.brightWhite(ui.padEnd(String(volume.DeviceID), 12)) +
        ui.progressBar(size - free, size || 1, 24) +
        ui.c.gray(`  ${system.formatBytes(free)} free`)
    );
  }
}

module.exports = {
  name: 'High Resource Alert',
  menuName: 'Resource Alerts',
  description: 'Watches CPU, memory and disk against thresholds and reports what is responsible.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Diagnostics',
  order: 60,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'wmi:query'],
  tags: ['alert', 'cpu', 'memory', 'disk', 'threshold', 'monitor'],
  config: { cpu: 90, memory: 90, diskBusy: 90, diskUsage: 95, watchSeconds: 5 },
  documentation:
    'Compares the current CPU load, memory usage, disk activity and disk fill level against ' +
    'configurable thresholds (by default CPU > 90%, RAM > 90%, disk > 95% full) and raises an alert ' +
    'listing the processes responsible. It can run once or keep watching until ENTER is pressed. ' +
    'It only observes - no process is ever stopped.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const data = await sample();
    return data ? { ok: true, message: 'resource counters available' } : { ok: false, message: 'counters unavailable' };
  },

  async execute(ctx) {
    const { ui, prompt, config } = ctx;
    kit.header('Resource Alerts', ['Warns when CPU, memory or disk cross a threshold.']);
    if (!kit.requireWindows('Resource alerts')) return { ok: false, reason: 'unsupported-platform' };

    const thresholds = {
      cpu: Number(config.cpu) || 90,
      memory: Number(config.memory) || 90,
      diskBusy: Number(config.diskBusy) || 90,
      diskUsage: Number(config.diskUsage) || 95,
    };

    ui.section('THRESHOLDS');
    ui.blank();
    kit.kv('CPU Usage', `> ${thresholds.cpu} %`);
    kit.kv('Memory Usage', `> ${thresholds.memory} %`);
    kit.kv('Disk Activity', `> ${thresholds.diskBusy} %`);
    kit.kv('Disk Fill Level', `> ${thresholds.diskUsage} %`);
    ui.blank();

    const mode = await prompt.choose('How should the check run?', [
      'Check once now',
      'Watch continuously (until ENTER)',
      'Change the thresholds for this run',
    ]);
    if (mode === null) return kit.done('Cancelled.');

    if (mode === 2) {
      const cpu = await prompt.number('CPU threshold in %', 10, 100);
      const memory = await prompt.number('Memory threshold in %', 10, 100);
      const disk = await prompt.number('Disk fill threshold in %', 10, 100);
      if (cpu !== null) thresholds.cpu = cpu;
      if (memory !== null) thresholds.memory = memory;
      if (disk !== null) thresholds.diskUsage = disk;
      ui.blank();
      ui.ok(`Thresholds for this run: CPU ${thresholds.cpu}%, RAM ${thresholds.memory}%, disk ${thresholds.diskUsage}%.`);
    }

    const watch = mode === 1;

    if (!watch) {
      const spin = ui.spinner('Sampling system resources...');
      const data = await sample();
      spin.stop();
      if (!data) return kit.noData('resource information');

      ui.blank();
      renderSample(ui, data, thresholds);

      const alerts = evaluate(data, thresholds);
      ui.blank();
      if (!alerts.length) {
        ui.completed('No threshold was exceeded - the system has headroom.');
      } else {
        ui.section('ALERTS', 'brightRed');
        ui.blank();
        for (const alert of alerts) {
          ui.status('WARNING', ui.c.brightWhite(`${alert.resource}: ${alert.value} (limit ${alert.threshold})`));
          if (alert.detail) ui.println('             ' + ui.c.gray(ui.truncate(alert.detail, ui.getWidth() - 8)));
        }
        ctx.logger.warn('resource-alert', `${alerts.length} threshold(s) exceeded`, alerts.map((a) => a.resource).join(', '));
      }

      ui.blank();
      ui.section('TOP PROCESSES');
      ui.blank();
      kit.table(
        ['PROCESS', 'PID', 'MEMORY'],
        data.topMem.map((process) => [
          ui.c.brightWhite(ui.truncate(String(process.Name), 26)),
          ui.c.gray(String(process.Id)),
          ui.c.brightCyan(system.formatBytes(process.Memory)),
        ]),
        { align: ['left', 'right', 'right'] }
      );

      return kit.done('Resource check finished.');
    }

    // ---- watch mode -----------------------------------------------------
    let stop = false;
    let ticks = 0;
    let alertCount = 0;
    ui.blank();
    ui.info('Watching - press ENTER to stop.');
    prompt.ask('').then(() => {
      stop = true;
    });

    while (!stop) {
      ticks += 1;
      // eslint-disable-next-line no-await-in-loop
      const data = await sample();
      if (stop) break;
      if (data) {
        const alerts = evaluate(data, thresholds);
        ui.clear();
        ui.blank();
        ui.box({ title: 'RESOURCE ALERTS', lines: [ui.c.gray(`sample ${ticks} - press ENTER to stop`)] });
        ui.blank();
        renderSample(ui, data, thresholds);
        ui.blank();
        if (alerts.length) {
          alertCount += 1;
          ui.section('ALERTS', 'brightRed');
          ui.blank();
          for (const alert of alerts) {
            ui.status('WARNING', ui.c.brightWhite(`${alert.resource}: ${alert.value} (limit ${alert.threshold})`));
            if (alert.detail) ui.println('             ' + ui.c.gray(ui.truncate(alert.detail, ui.getWidth() - 8)));
          }
        } else {
          ui.ok('All resources are within their limits.');
        }
      }

      const until = Date.now() + Math.max(1, Number(config.watchSeconds) || 5) * 1000;
      while (!stop && Date.now() < until) {
        // eslint-disable-next-line no-await-in-loop
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    }

    ui.blank();
    return kit.done(`Watch stopped after ${ticks} sample(s), ${alertCount} with alerts.`);
  },
};
