/**
 * Function/Windows/environment-viewer.js
 * ---------------------------------------------------------------------------
 * ENVIRONMENT VARIABLES VIEWER
 *
 * User and system environment variables with search.
 *
 * Values whose NAME suggests a secret (TOKEN, SECRET, PASSWORD, KEY, ...) are
 * masked on screen and in exports, and are never written to the log.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

/** A variable whose name matches this is never shown in full. */
const SECRET_PATTERN = /(TOKEN|SECRET|PASSWORD|PASSWD|PWD|APIKEY|API_KEY|_KEY|KEY_|CREDENTIAL|AUTH|PRIVATE|SESSION|COOKIE|SIGNATURE|LICENSE)/i;

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  'function Collect($scope) {',
  '  $table = [Environment]::GetEnvironmentVariables($scope)',
  '  $items = @()',
  '  foreach ($key in $table.Keys) { $items += [ordered]@{ Name = $key; Value = $table[$key] } }',
  '  ,@($items)',
  '}',
  '[ordered]@{',
  '  user = (Collect "User");',
  '  machine = (Collect "Machine");',
  '  process = (Collect "Process")',
  '} | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 45000 });
  if (!result.ok || !result.data) return null;
  const normalise = (list) =>
    system.asArray(list)
      .map((entry) => ({ name: String(entry.Name || ''), value: String(entry.Value == null ? '' : entry.Value) }))
      .filter((entry) => entry.name)
      .sort((a, b) => a.name.localeCompare(b.name));
  return {
    user: normalise(result.data.user),
    machine: normalise(result.data.machine),
    process: normalise(result.data.process),
  };
}

function isSecret(name) {
  return SECRET_PATTERN.test(String(name));
}

/** Mask a secret value; long non-secret values are only shortened. */
function displayValue(entry, width) {
  if (isSecret(entry.name)) {
    return entry.value ? `${'*'.repeat(Math.min(12, entry.value.length))}  (masked, ${entry.value.length} chars)` : '(empty)';
  }
  return entry.value.length > width ? `${entry.value.slice(0, width - 3)}...` : entry.value;
}

module.exports = {
  name: 'Environment Variables',
  menuName: 'Environment Variables',
  description: 'User and system environment variables with search. Secret-looking values are masked.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Windows',
  order: 60,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'registry:read', 'filesystem:write(reports)'],
  tags: ['environment', 'variables', 'path', 'env'],
  documentation:
    'Lists the environment variables of the current user, of the machine, and of this process, with ' +
    'a search across names and values. Any variable whose NAME contains TOKEN, SECRET, PASSWORD, ' +
    'KEY, CREDENTIAL, AUTH and similar words is masked on screen, masked in the exported report and ' +
    'never written to the application log - so a report can be shared without leaking credentials.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const data = await collect();
    return data ? { ok: true, message: `${data.user.length + data.machine.length} variable(s)` } : { ok: false, message: 'query failed' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('Environment Variables', ['User and system variables.', 'Secret-looking values are masked.']);
    if (!kit.requireWindows('The environment viewer')) return { ok: false, reason: 'unsupported-platform' };

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

    const secretCount = [...data.user, ...data.machine].filter((entry) => isSecret(entry.name)).length;

    const render = (title, list) => {
      ui.blank();
      ui.section(title);
      ui.blank();
      if (!list.length) {
        ui.println('  ' + ui.c.gray('None.'));
        return;
      }
      for (const entry of list) {
        const masked = isSecret(entry.name);
        ui.println(
          '  ' + (masked ? ui.c.brightYellow(ui.padEnd(entry.name, 28)) : ui.c.brightWhite(ui.padEnd(entry.name, 28))) +
            ui.c.gray(': ') +
            (masked ? ui.c.brightYellow(displayValue(entry, 30)) : ui.c.white(displayValue(entry, 30)))
        );
      }
    };

    for (;;) {
      ui.blank();
      ui.section('SUMMARY');
      ui.blank();
      kit.kv('User Variables', String(data.user.length));
      kit.kv('System Variables', String(data.machine.length));
      kit.kv('Process Variables', String(data.process.length));
      kit.kv('Masked (secret-looking)', String(secretCount), secretCount ? 'brightYellow' : 'brightGreen');

      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'User environment variables' },
          { key: '2', label: 'System environment variables' },
          { key: '3', label: 'Search all variables' },
          { key: '4', label: 'Show one variable in full' },
          { key: '5', label: 'Export as a TXT report' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'ENVIRONMENT VARIABLES' }
      );
      ui.blank();

      const answer = await prompt.select();
      if (!answer && prompt.isClosed()) break;
      if (answer === '0') break;

      switch (answer) {
        case '1':
          render('USER ENVIRONMENT VARIABLES', data.user);
          await prompt.pause();
          break;

        case '2':
          render('SYSTEM ENVIRONMENT VARIABLES', data.machine.slice(0, 40));
          if (data.machine.length > 40) ui.println('  ' + ui.c.gray(`... and ${data.machine.length - 40} more`));
          await prompt.pause();
          break;

        case '3': {
          const term = (await prompt.text('Search for')).trim().toLowerCase();
          if (!term) break;
          const matches = [...data.user, ...data.machine].filter(
            (entry) =>
              entry.name.toLowerCase().includes(term) ||
              (!isSecret(entry.name) && entry.value.toLowerCase().includes(term))
          );
          render(`SEARCH: ${term.toUpperCase()} (${matches.length})`, matches.slice(0, 30));
          await prompt.pause();
          break;
        }

        case '4': {
          const name = (await prompt.text('Variable name')).trim();
          if (!name) break;
          const entry =
            [...data.process, ...data.user, ...data.machine].find(
              (item) => item.name.toLowerCase() === name.toLowerCase()
            ) || null;

          ui.blank();
          if (!entry) {
            ui.warning(`There is no variable called "${name}".`);
          } else if (isSecret(entry.name)) {
            ui.section(entry.name.toUpperCase());
            ui.blank();
            ui.warning('This variable looks like a secret and is not printed.');
            kit.kv('Length', `${entry.value.length} characters`);
            kit.kv('Starts with', entry.value.slice(0, 2) + '...');
          } else {
            ui.section(entry.name.toUpperCase());
            ui.blank();
            // PATH-like values are far more readable one entry per line.
            if (entry.value.includes(';') && entry.value.length > 60) {
              entry.value
                .split(';')
                .filter(Boolean)
                .forEach((part, index) => ui.println('  ' + ui.c.gray(ui.padStart(String(index + 1), 3) + '. ') + ui.c.white(ui.truncate(part, ui.getWidth() - 8))));
            } else {
              for (const line of ui.wrap(entry.value, ui.getWidth() - 4)) ui.println('  ' + ui.c.white(line));
            }
          }
          await prompt.pause();
          break;
        }

        case '5': {
          const report = kit.report('Environment Variables Report');
          const summary = report.section('SUMMARY');
          summary.add('User Variables', String(data.user.length));
          summary.add('System Variables', String(data.machine.length));
          summary.add('Masked Variables', String(secretCount));
          summary.note('Values of secret-looking variables are masked in this report.');

          const user = report.section('USER VARIABLES');
          for (const entry of data.user) {
            user.add(entry.name, isSecret(entry.name) ? `(masked, ${entry.value.length} chars)` : entry.value);
          }
          const machine = report.section('SYSTEM VARIABLES');
          for (const entry of data.machine) {
            machine.add(entry.name, isSecret(entry.name) ? `(masked, ${entry.value.length} chars)` : entry.value);
          }
          kit.saveReport(report, 'Environment');
          await prompt.pause();
          break;
        }

        default:
          ui.warning('Enter one of the numbers shown above, or 0 to go back.');
          await prompt.pause();
      }
    }

    return kit.done('Environment viewer closed.');
  },
};
