#!/usr/bin/env node

/**
 * Config-Audit Fix CLI
 * Standalone entry point for running fixes without the command.
 * Usage: node fix-cli.mjs <path> [--apply] [--global] [--json]
 * Dry-run by default — must pass --apply to write changes.
 * Zero external dependencies.
 */

import { resolve } from 'node:path';
import { runAllScanners } from './scan-orchestrator.mjs';
import { planFixes, applyFixes, verifyFixes } from './fix-engine.mjs';
import { createBackup } from './lib/backup.mjs';
import { humanizeFinding } from './lib/humanizer.mjs';

async function main() {
  const args = process.argv.slice(2);
  let targetPath = '.';
  let apply = false;
  let jsonMode = false;
  let rawMode = false;
  let includeGlobal = false;

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--apply') {
      apply = true;
    } else if (args[i] === '--json') {
      jsonMode = true;
    } else if (args[i] === '--raw') {
      rawMode = true;
    } else if (args[i] === '--global') {
      includeGlobal = true;
    } else if (!args[i].startsWith('-')) {
      targetPath = args[i];
    }
  }

  // Whether to suppress prose stderr (true for both --json and --raw machine paths).
  const machineMode = jsonMode || rawMode;

  const resolvedPath = resolve(targetPath);

  if (!machineMode) {
    process.stderr.write(`Config-Audit Fix CLI v2.1.0\n`);
    process.stderr.write(`Target: ${resolvedPath}\n`);
    process.stderr.write(`Mode: ${apply ? 'APPLY' : 'DRY-RUN'}\n\n`);
    process.stderr.write(`Scanning...\n`);
  }

  // 1. Run all scanners
  const envelope = await runAllScanners(targetPath, {
    includeGlobal,
    humanizedProgress: !machineMode,
  });

  // 2. Plan fixes
  const { fixes, skipped, manual } = planFixes(envelope);

  if (!machineMode) {
    process.stderr.write(`\n`);
    process.stderr.write(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
    process.stderr.write(` Config-Audit Fix Plan\n`);
    process.stderr.write(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`);

    if (fixes.length > 0) {
      process.stderr.write(` Auto-fixable (${fixes.length}):\n`);
      for (let i = 0; i < fixes.length; i++) {
        process.stderr.write(`  ${i + 1}. [${fixes[i].findingId}] ${fixes[i].description}\n`);
      }
    } else {
      process.stderr.write(` No auto-fixable issues found.\n`);
    }

    if (manual.length > 0) {
      // Default mode humanizes the manual-finding titles for the prose render.
      // The JSON `manual` array (later in this function) keeps v5.0.0 verbatim.
      process.stderr.write(`\n Manual (${manual.length}):\n`);
      for (let i = 0; i < manual.length; i++) {
        const m = manual[i];
        const title = humanizeFinding({
          id: m.findingId,
          scanner: typeof m.findingId === 'string' ? m.findingId.split('-')[1] || '' : '',
          severity: m.severity || 'info',
          title: m.title,
          description: m.description || '',
          recommendation: m.recommendation || '',
        }).title;
        process.stderr.write(`  ${fixes.length + i + 1}. [${m.findingId}] ${title}\n`);
      }
    }

    if (skipped.length > 0) {
      process.stderr.write(`\n Skipped (${skipped.length}): could not generate fix plan\n`);
    }

    process.stderr.write(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
  }

  // 3. Apply or dry-run
  let applied = [];
  let failed = [];
  let verified = [];
  let regressions = [];
  let backupId = null;

  if (fixes.length === 0) {
    if (machineMode) {
      const output = { planned: [], applied: [], failed: [], verified: [], regressions: [], manual, backupId: null };
      process.stdout.write(JSON.stringify(output, null, 2) + '\n');
    }
    process.exit(0);
  }

  if (apply) {
    // Create backup first
    const filesToBackup = [...new Set(fixes.filter(f => f.type !== 'file-rename').map(f => f.file))];
    const backup = createBackup(filesToBackup);
    backupId = backup.backupId;

    if (!machineMode) {
      process.stderr.write(`\n Backup created: ${backup.backupPath}\n`);
      process.stderr.write(` Applying ${fixes.length} fixes...\n\n`);
    }

    const result = await applyFixes(fixes, { dryRun: false, backupDir: backup.backupPath });
    applied = result.applied;
    failed = result.failed;

    if (!machineMode) {
      process.stderr.write(` Results: ${applied.length} applied, ${failed.length} failed\n`);
      if (failed.length > 0) {
        for (const f of failed) {
          process.stderr.write(`  FAILED: [${f.findingId}] ${f.error}\n`);
        }
      }
    }

    // 4. Verify
    if (applied.length > 0) {
      if (!machineMode) {
        process.stderr.write(`\n Verifying...\n`);
      }

      const verification = await verifyFixes(envelope, applied);
      verified = verification.verified;
      regressions = verification.regressions;

      if (!machineMode) {
        process.stderr.write(` Verified: ${verified.length}/${applied.length}\n`);
        if (regressions.length > 0) {
          process.stderr.write(` Regressions: ${regressions.join(', ')}\n`);
        }
        process.stderr.write(`\n Rollback: node scanners/rollback-cli.mjs ${backupId}\n`);
      }
    }
  } else {
    // Dry-run mode
    const result = await applyFixes(fixes, { dryRun: true });
    applied = result.applied;

    if (!machineMode) {
      process.stderr.write(`\n Dry-run complete. Pass --apply to execute.\n`);
    }
  }

  // JSON output (both --json and --raw write byte-equal v5.0.0-shape stdout)
  if (machineMode) {
    const output = {
      planned: fixes.map(f => ({
        findingId: f.findingId,
        file: f.file,
        type: f.type,
        description: f.description,
      })),
      applied: applied.map(a => ({
        findingId: a.findingId,
        file: a.file,
        status: a.status,
      })),
      failed: failed.map(f => ({
        findingId: f.findingId,
        file: f.file,
        status: f.status,
        error: f.error,
      })),
      verified,
      regressions,
      manual: manual.map(m => ({
        findingId: m.findingId,
        title: m.title,
        recommendation: m.recommendation,
      })),
      backupId,
    };
    process.stdout.write(JSON.stringify(output, null, 2) + '\n');
  }
}

// Only run CLI if invoked directly
const isDirectRun = process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname);
if (isDirectRun) {
  main().catch(err => {
    process.stderr.write(`Fatal: ${err.message}\n`);
    process.exit(3);
  });
}
