#!/usr/bin/env node
/**
 * cli:uat-plan — Generate a `.plantest.yml` test plan for a generated SmartStack app.
 *
 * Pipeline: validate spec → resolve app structure + DB connection → DISCOVER live
 * (SQL nav/RBAC + componentRegistry views + controllers, scoped to `path`) → PROJECT
 * onto roles → assemble + serialise the plan → write `{name}.plantest.yml` + `.signature`.
 *
 * Discovery (SQL/filesystem) is the only non-deterministic step; everything from the
 * DiscoveryResult onward is pure (see generate.ts), so the plan is reproducible.
 */
import { parseArgs } from 'node:util';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { validate } from './validate.js';
import { discover } from './discover.js';
import { generatePlan } from './generate.js';
import { generateEnvelope, failGenerate, printEnvelope } from '../../../lib/output.js';

const COMMAND = 'uat-plan';

async function main(): Promise<void> {
  let values: { spec?: string; dry_run?: boolean };
  try {
    values = parseArgs({
      options: {
        spec: { type: 'string' },
        dry_run: { type: 'boolean', default: false },
      },
      strict: true,
    }).values;
  } catch (e) {
    printEnvelope(failGenerate(COMMAND, [`Invalid arguments: ${(e as Error).message}`]));
    process.exit(1);
    return;
  }

  if (!values.spec) {
    printEnvelope(failGenerate(COMMAND, ['--spec is required']));
    process.exit(1);
  }

  let raw: unknown;
  try {
    raw = JSON.parse(values.spec);
  } catch {
    printEnvelope(failGenerate(COMMAND, ['Invalid JSON in --spec']));
    process.exit(1);
  }

  const v = await validate(raw);
  if (!v.valid || !v.spec || !v.layout || !v.connection) {
    printEnvelope(failGenerate(COMMAND, v.errors));
    process.exit(1);
  }

  const spec = v.spec;
  const layout = v.layout;
  const projectRoot = resolve(spec.projectPath);
  const warnings = [...v.warnings];

  // Live discovery (SQL + filesystem) — the only step that can fail at runtime.
  let outcome;
  try {
    outcome = await discover({
      apiDir: layout.apiDir,
      webDir: layout.webDir,
      path: spec.path,
      connection: v.connection,
      rolesOverride: spec.roles,
      includeApi: spec.includeApi,
    });
  } catch (e) {
    printEnvelope(failGenerate(COMMAND, [`Discovery failed: ${(e as Error).message}`]));
    process.exit(1);
    return;
  }
  warnings.push(...outcome.warnings);

  const gen = generatePlan(outcome.result, {
    name: layout.name,
    outDir: layout.outDir,
    modes: spec.modes,
    caps: spec.caps,
    includeApi: spec.includeApi,
    generatedAt: spec.generatedAt,
  });

  // Structural invariant violations are advisory (surface them; do not hard-gate).
  const violations = gen.violations.map((x) => `[inv ${x.invariant}] ${x.where}: ${x.message}`);
  warnings.push(...violations);

  const summary = {
    application: outcome.result.application,
    path: spec.path,
    roles: gen.plan.roles,
    routes: gen.plan.routes.length,
    endpoints: gen.plan.endpoints.length,
    connectionSource: v.connectionSource,
    signature: gen.signature,
  };

  if (values.dry_run) {
    printEnvelope(
      generateEnvelope(COMMAND, {
        data: { dryRun: true, ...summary, files: gen.files.map((f) => f.path) },
        warnings,
      }),
    );
    process.exit(0);
  }

  const filesCreated: string[] = [];
  const filesModified: string[] = [];
  for (const file of gen.files) {
    const abs = resolve(join(projectRoot, file.path));
    const exists = existsSync(abs);
    mkdirSync(dirname(abs), { recursive: true });
    writeFileSync(abs, file.content, 'utf-8');
    (exists ? filesModified : filesCreated).push(abs);
  }

  const nextSteps = [
    `Review the plan: ${layout.outDir}/${layout.name}.plantest.yml`,
    'Provision tenant + 1 user/role with `/uat-provision`, then run the UI agent + `/uat-api`.',
  ];
  if (violations.length > 0) {
    nextSteps.unshift(
      `${violations.length} structural invariant violation(s) — the plan may be malformed; inspect the warnings before running.`,
    );
  }

  printEnvelope(
    generateEnvelope(COMMAND, {
      data: summary,
      filesCreated,
      filesModified,
      warnings,
      nextSteps,
    }),
  );
}

void main();
