#!/usr/bin/env node
/**
 * Generates the agent-side clone command catalog in oracle-proxy.py from the
 * single source of truth in services/ssh-executor.js COMMAND_WHITELIST.
 *
 * Why generated rather than written twice: the agent is the security boundary
 * and MUST own its own copy of every command it will run — the cloud sends only
 * a key. But two hand-maintained copies of 32 shell templates drift, and a
 * drifted clone step is one that does something different from what the UI says
 * it does. So the Python copy is derived, and CI (--check) fails if it is stale.
 *
 * Usage:
 *   node scripts/gen-clone-catalog.js           # rewrite the generated block
 *   node scripts/gen-clone-catalog.js --check   # exit 1 if the block is stale
 */
'use strict';

process.env.DATABASE_URL = process.env.DATABASE_URL || 'postgres://generator/unused';

const fs   = require('fs');
const path = require('path');
const { COMMAND_WHITELIST } = require('../services/ssh-executor');

const PROXY = path.join(__dirname, '..', 'oracle-proxy.py');
const BEGIN = '# ── BEGIN GENERATED CLONE CATALOG — scripts/gen-clone-catalog.js ──';
const END   = '# ── END GENERATED CLONE CATALOG ──';

function pyLiteral(v, indent) {
  const pad = ' '.repeat(indent);
  if (typeof v === 'string') return JSON.stringify(v);
  // Booleans BEFORE the object fallback. Without this a `true` fell through to
  // Object.keys(true) === [] and emitted `{}` — which is FALSY in Python, so a
  // flag meaning "do this" silently became "don't".
  if (typeof v === 'boolean') return v ? 'True' : 'False';
  if (Array.isArray(v)) return '[' + v.map(x => JSON.stringify(x)).join(', ') + ']';
  const keys = Object.keys(v);
  if (!keys.length) return '{}';
  return '{\n' + keys.map(k =>
    `${pad}    ${JSON.stringify(k)}: ${pyLiteral(v[k], indent + 4)},`).join('\n') + `\n${pad}}`;
}

function build() {
  const entries = Object.entries(COMMAND_WHITELIST)
    .filter(([k]) => k.includes('.clone.'))
    .sort(([a], [b]) => a.localeCompare(b));

  if (!entries.length) {
    console.error('gen-clone-catalog: no .clone. commands found — refusing to write an empty catalog.');
    process.exit(2);
  }

  const obj = {};
  for (const [key, def] of entries) {
    obj[key] = {
      label:    def.label || key,
      roles:    def.allowedRoles || [],
      template: def.template,
    };
    // Only EBS DB-tier steps live under $ORACLE_HOME/appsutil/scripts/$CONTEXT_NAME.
    // The agent used to resolve CONTEXT_NAME for EVERY db-tier background step,
    // which aborted `db.clone.rman.duplicate` before rman ran on exactly the hosts
    // DB Clone exists for: a pure-Oracle server has no appsutil directory at all.
    // Emitted only when true so the generated block stays diffable.
    if (def.needsContext) obj[key].needs_context = true;
  }

  return [
    BEGIN,
    '# DO NOT EDIT BY HAND. Regenerate with: node scripts/gen-clone-catalog.js',
    `# Source: services/ssh-executor.js COMMAND_WHITELIST (${entries.length} clone commands)`,
    'CLONE_COMMANDS = ' + pyLiteral(obj, 0),
    END,
  ].join('\n');
}

function main() {
  const check = process.argv.includes('--check');
  const src   = fs.readFileSync(PROXY, 'utf8');
  const block = build();

  const b = src.indexOf(BEGIN);
  const e = src.indexOf(END);

  if (b === -1 || e === -1) {
    if (check) {
      console.error('gen-clone-catalog: markers not found in oracle-proxy.py — run the generator.');
      process.exit(1);
    }
    console.error('gen-clone-catalog: markers not found in oracle-proxy.py. Add them first:\n' +
                  `  ${BEGIN}\n  ${END}`);
    process.exit(2);
  }

  const current = src.slice(b, e + END.length);
  if (current === block) {
    console.log(`gen-clone-catalog: up to date (${(block.match(/\n {4}"/g) || []).length} entries).`);
    return;
  }
  if (check) {
    console.error('gen-clone-catalog: oracle-proxy.py clone catalog is STALE.');
    console.error('  A clone command changed in services/ssh-executor.js but the agent copy did not.');
    console.error('  The agent would run the OLD shell for a step the UI describes with the NEW one.');
    console.error('  Fix: node scripts/gen-clone-catalog.js  (then commit oracle-proxy.py)');
    process.exit(1);
  }
  fs.writeFileSync(PROXY, src.slice(0, b) + block + src.slice(e + END.length), 'utf8');
  console.log('gen-clone-catalog: oracle-proxy.py catalog regenerated.');
}

main();
