#!/usr/bin/env node
/**
 * Fail the build when a HEALTH-DATA page makes a failed load look like an empty one.
 *
 * WHY
 * ---
 * On a monitoring product, "nothing to show" and "the loader broke" must never
 * render the same. They did, repeatedly:
 *   - Deep EBS sanity + 12.2 module checks: `r.ok ? r.json() : null` plus a silent
 *     catch, so a 500 rendered the same empty state as "never run".
 *   - SQL tuning: `if (!r.ok) return` left an empty table, reading as "no findings"
 *     — indistinguishable from a healthy database.
 *   - db-ops/ebs-ops: a failed agent-status call printed a confident "Agent
 *     offline" for what was really "we could not ask".
 *   - tvConn.parseList returned [] for an unrecognised payload, blanking the whole
 *     Deep EBS page for a user who had two connections.
 * Every one of these is a FALSE ALL-CLEAR. That is the worst direction to fail in.
 *
 * WHAT COUNTS
 * -----------
 * Only pages that render health/check/finding data (HEALTH_PAGES). A silent catch
 * on a cosmetic affordance is fine and is not flagged.
 *
 * ALLOWLIST
 * ---------
 * Keyed by file + the exact trimmed source line, NOT by line number, so unrelated
 * edits above do not silently re-approve a changed line. Change the code and it
 * re-flags — deliberately. Every entry needs a reason.
 */
const fs = require('fs');
const path = require('path');

const PUBLIC_DIR = path.resolve(__dirname, '..', '..', 'public');
const HEALTH_PAGES = /^(ebs-deep|ebs-12-2-checks|sanity-check|addm|patches-status|performance|performance-advisor|sql-tuning|validation-suite|report|report-ebs|db-ops|ebs-ops|ebs-concurrent|ebs-middleware|fleet|agent|clients-dashboard|vulnerability)\.html$/;

// file -> { 'exact trimmed line': 'why this one is genuinely harmless' }
const ALLOW = {
  'report-ebs.html': {
    '} catch (e) { /* ignore */ }':
      'AI-overlay POLL loop: it retries on the next tick, so swallowing one attempt is correct.',
  },
  'performance-advisor.html': {
    ".catch(function() { /* non-critical, Show SQL just won't open */ });":
      'Populates a "Show SQL" affordance only. No health data is hidden.',
  },
  'db-ops.html': {
    '.then(r => r.ok ? r.json() : {})':
      'SSH-targets banner: absence of the banner is not a health claim.',
  },
  'clients-dashboard.html': {
    '.then(function (r) { return r.ok ? r.json() : []; })':
      'Connection checklist inside a modal — visibly empty and immediately retryable.',
  },
  'sanity-check.html': {
    '.then(function(r) { return r.ok ? r.json() : []; })':
      'Connection dropdown; tvConn.parseList now warns loudly on an unusable payload.',
  },
  'validation-suite.html': {
    '} catch (_) { /* non-fatal */ }':
      'Share-link issuance. Failing to mint a link hides no results.',
  },
};

const SIGS = [
  { re: /\.ok\s*\?\s*[a-z]*\.?json\(\)\s*:\s*(null|\[\]|\{\})/, name: 'HTTP error collapsed to an empty value' },
  { re: /\.catch\(\s*function\s*\(\s*\w*\s*\)\s*\{\s*\/\*/, name: 'fully swallowed catch' },
  { re: /catch\s*\(\s*\w*\s*\)\s*\{\s*\/\*\s*(silent|ignore|non-critical|non-fatal)/i, name: 'fully swallowed catch' },
];

let scanned = 0;
const problems = [];

for (const f of fs.readdirSync(PUBLIC_DIR).filter(x => x.endsWith('.html')).sort()) {
  if (!HEALTH_PAGES.test(f)) continue;
  scanned++;
  const lines = fs.readFileSync(path.join(PUBLIC_DIR, f), 'utf8').split(/\r?\n/);
  lines.forEach((raw, i) => {
    const line = raw.trim();
    for (const s of SIGS) {
      if (!s.re.test(line)) continue;
      if (ALLOW[f] && Object.prototype.hasOwnProperty.call(ALLOW[f], line)) return;
      problems.push({ f, n: i + 1, sig: s.name, line: line.slice(0, 100) });
      return;
    }
  });
}

// ── Backend half: the Tune advisors ─────────────────────────────────────────
// Same false all-clear, server-side. Every advisor swallowed per-query failures
//     sgaAdvice = mapSga(await queryViaAgent(...).catch(() => []));
// so a missing grant or an unlicensed pack became [] -> "nothing to optimise".
// Data queries must route through lib/advisor-errors makeCollector().catcher()
// so the failure reaches the response as check_errors[]. connection.close() is
// exempt: failing to close a handle hides no health data.
const ADVISOR_DIR = path.resolve(__dirname, '..', '..', 'routes');
let advisorsScanned = 0;
for (const f of fs.readdirSync(ADVISOR_DIR).filter(x => /-advisor\.js$/.test(x)).sort()) {
  advisorsScanned++;
  const lines = fs.readFileSync(path.join(ADVISOR_DIR, f), 'utf8').split(new RegExp('\\r?\\n'));
  lines.forEach((raw, i) => {
    const line = raw.trim();
    if (!/\.catch\(\s*\(\s*\)\s*=>/.test(line)) return;
    if (/connection\.close\(\)/.test(line)) return;   // exempt
    problems.push({ f: 'routes/' + f, n: i + 1, sig: 'advisor query failure swallowed',
                    line: line.slice(0, 100) });
  });
}
if (advisorsScanned === 0) {
  console.error('FATAL: matched 0 advisor routes — the -advisor.js pattern is broken.');
  process.exit(2);
}
console.log('Scanned ' + advisorsScanned + ' advisor routes for swallowed query failures.');

if (scanned === 0) {
  console.error('FATAL: matched 0 health pages — the HEALTH_PAGES pattern is broken, not the code.');
  process.exit(2);
}

console.log('Scanned ' + scanned + ' health-data pages for silent-empty failure handling.');

if (problems.length) {
  console.error('\n' + problems.length + ' place(s) where a FAILED load can look like an EMPTY one:\n');
  for (const p of problems) {
    console.error('  public/' + p.f + ':' + p.n);
    console.error('    [' + p.sig + ']  ' + p.line);
  }
  console.error('\nOn a monitoring product this renders as ALL CLEAR when the loader is broken.');
  console.error('Fix: throw on !ok, then tvLoadError.show(container, err, {what, retry}).');
  console.error('If it genuinely hides no health data, add it to ALLOW in this file with a reason.\n');
  process.exit(1);
}

console.log('silent-empty: clean.');
