/**
 * `madarx update` + the shared applyUpdate() used by every human/system-
 * authorized update path (CLI, dashboard button, opted-in weekly job).
 *
 * applyUpdate runs `npm i -g <pkg>@latest`. The actual spawn is injectable so
 * the gating/flow logic is unit-testable without shelling out or hitting the
 * network. Replacing a global package while the server runs is safe on POSIX
 * (the running process keeps its open file handles); the new version is picked
 * up on the next `madarx` start, which is why we always print a restart hint.
 */

import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline/promises';
import { PKG_NAME, currentVersion, runUpdateCheck } from '../update-check.js';
import { applyHomeEnv } from './home.js';

/**
 * npm install args for a global update. Exported for testing.
 * `--prefer-online` is load-bearing: without it, npm can resolve `@latest` to a
 * just-published version that isn't in its stale local metadata cache yet and
 * fail with `notarget No matching version found`. Always refetch metadata.
 */
export function npmInstallArgs(pkgSpec) {
  return ['install', '-g', pkgSpec, '--prefer-online', '--no-audit', '--no-fund'];
}

/** Default runner: `npm i -g madarx@latest`, capturing combined output. */
function npmGlobalInstall(pkgSpec) {
  return new Promise((resolve) => {
    let out = '';
    const child = spawn('npm', npmInstallArgs(pkgSpec), {
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    child.stdout.on('data', (d) => { out += d; process.stdout.write(d); });
    child.stderr.on('data', (d) => { out += d; process.stderr.write(d); });
    child.on('error', (err) => resolve({ ok: false, code: -1, output: String(err.message) }));
    child.on('exit', (code) => resolve({ ok: code === 0, code, output: out }));
  });
}

/**
 * Apply the update. Writes an audit row (best-effort). `runner` is injectable
 * for tests. Returns { ok, from, to, code, output }.
 *
 * @param {{ runner?: (pkgSpec:string)=>Promise<{ok:boolean,code:number,output:string}>,
 *           to?: string, initiatedBy?: string }} [opts]
 */
export async function applyUpdate({ runner = npmGlobalInstall, to = 'latest', initiatedBy = 'cli' } = {}) {
  const from = currentVersion();
  const pkgSpec = `${PKG_NAME}@${to}`;
  const result = await runner(pkgSpec);
  // Best-effort audit — never let an audit failure mask the update result.
  try {
    const { appendAudit } = await import('../runtime.js');
    appendAudit({
      type: 'system.self_update',
      summary: `madarx update ${from} → ${to} (${result.ok ? 'ok' : 'failed'})`,
      status: result.ok ? 'ok' : 'failed',
      meta: { from, to, code: result.code, initiated_by: initiatedBy },
    });
  } catch { /* audit is observation-only */ }
  return { ok: result.ok, from, to, code: result.code, output: result.output };
}

export async function update({ flags = {} } = {}) {
  const layout = applyHomeEnv();
  process.env.MADARX_DISABLE_INPROC_DAEMON = '1';

  console.log('madarx update\n');
  const check = await runUpdateCheck({ file: layout.updateCheckFile });
  console.log(`  current: ${check.current}`);
  console.log(`  latest:  ${check.latest || '(registry unreachable)'}\n`);

  if (!check.reachable) {
    console.error('Could not reach the npm registry. Try again later.');
    return 1;
  }
  if (!check.behind) {
    console.log('Already up to date.');
    return 0;
  }

  if (!flags.yes && !flags.y) {
    if (!(process.stdin.isTTY && process.stdout.isTTY)) {
      console.error(`v${check.latest} is available. Re-run with --yes to apply non-interactively.`);
      return 1;
    }
    const rl = createInterface({ input: process.stdin, output: process.stdout });
    const ans = (await rl.question(`Update to v${check.latest} now? [y/N]: `)).trim().toLowerCase();
    rl.close();
    if (ans !== 'y' && ans !== 'yes') {
      console.log('Cancelled.');
      return 0;
    }
  }

  console.log(`\nUpdating to v${check.latest}…`);
  const res = await applyUpdate({ initiatedBy: 'cli' });
  if (res.ok) {
    console.log(`\nUpdated ${res.from} → v${check.latest}. Restart madarx to run the new version.`);
    return 0;
  }
  console.error(`\nUpdate failed (exit ${res.code}). The current install is unchanged.`);
  return 1;
}
