import { existsSync, readFileSync } from 'node:fs';

import type { Command } from 'commander';

import { runGates } from './gate-runner.js';
import { MACPCapabilityError, type MacpErrorCode } from './errors.js';

/**
 * Load gates from a spec: an existing file (JSON gates array, a JSON object
 * with `quality_gates`, a JSON gate object, or one command per line) or an
 * inline command string. Fails closed with a typed capability error when the
 * spec contains no executable gate definition.
 */
function loadGateSpec(spec: string): unknown[] {
  if (existsSync(spec)) {
    const raw = readFileSync(spec, 'utf-8');
    try {
      const parsed = JSON.parse(raw) as unknown;
      if (Array.isArray(parsed)) {
        if (parsed.length === 0) {
          throw new MACPCapabilityError(
            'MACP_NO_COMMAND',
            'gate-spec',
            `gate spec file '${spec}' contains an empty gates array`,
          );
        }
        return parsed;
      }
      if (typeof parsed === 'object' && parsed !== null) {
        const obj = parsed as Record<string, unknown>;
        if (Array.isArray(obj['quality_gates'])) {
          return obj['quality_gates'];
        }
        return [parsed];
      }
      throw new MACPCapabilityError(
        'MACP_NO_COMMAND',
        'gate-spec',
        `gate spec file '${spec}' parsed to ${typeof parsed} — expected a gates array, a task with quality_gates, or a gate object`,
      );
    } catch (exc) {
      if (exc instanceof MACPCapabilityError) throw exc;
      // Not JSON — treat each non-empty line as a command gate.
      const lines = raw
        .split('\n')
        .map((l) => l.trim())
        .filter((l) => l.length > 0);
      if (lines.length > 0) return lines;
      throw new MACPCapabilityError(
        'MACP_NO_COMMAND',
        'gate-spec',
        `gate spec file '${spec}' contains no gates`,
      );
    }
  }
  if (spec.trim().length > 0) return [spec];
  throw new MACPCapabilityError('MACP_NO_COMMAND', 'gate-spec', 'gate spec is empty');
}

/** Print a typed not-implemented failure and exit nonzero (RI-N2 fail-closed). */
function notImplemented(subcommand: string, capability: string, hint: string): void {
  const err = new MACPCapabilityError(
    'MACP_NOT_IMPLEMENTED',
    capability,
    `${subcommand} is not implemented in @mosaicstack/macp yet (${capability} capability absent) — ${hint}`,
  );
  console.error(`[macp] ${subcommand}: ${err.message} [${err.code}]`);
  process.exitCode = 1;
}

/**
 * Register macp subcommands on an existing Commander program.
 * This avoids cross-package Commander version mismatches by using the
 * caller's Command instance directly.
 */
export function registerMacpCommand(parent: Command): void {
  const macp = parent.command('macp').description('MACP task and gate management');

  // ─── tasks ───────────────────────────────────────────────────────────────

  const tasks = macp.command('tasks').description('Manage MACP tasks');

  tasks
    .command('list')
    .description('List MACP tasks')
    .option(
      '--status <status>',
      'Filter by task status (pending|running|gated|completed|failed|escalated)',
    )
    .option(
      '--type <type>',
      'Filter by task type (coding|deploy|research|review|documentation|infrastructure)',
    )
    .action((opts: { status?: string; type?: string }) => {
      // unimplemented capability — a failure, never a success (RI-N2)
      if (opts.status) {
        console.log(`  status filter: ${opts.status}`);
      }
      if (opts.type) {
        console.log(`  type filter: ${opts.type}`);
      }
      notImplemented('tasks list', 'task-persistence', 'use the macp package programmatically');
    });

  // ─── submit ──────────────────────────────────────────────────────────────

  macp
    .command('submit <path>')
    .description('Submit a task from a JSON/YAML spec file')
    .action((specPath: string) => {
      // unimplemented capability — a failure, never a success (RI-N2)
      console.log(`  spec path: ${specPath}`);
      console.log('  task id:   (unavailable — no MACP server connected)');
      console.log('  status:    (unavailable — no MACP server connected)');
      notImplemented('submit', 'macp-server', 'use the macp package programmatically');
    });

  // ─── gate ────────────────────────────────────────────────────────────────

  macp
    .command('gate <spec>')
    .description('Run a gate from a spec string or file path (wraps runGate/runGates)')
    .option('--fail-on <mode>', 'Gate fail-on mode: ai|fail|both|none', 'fail')
    .option('--cwd <path>', 'Working directory for gate execution', process.cwd())
    .option('--log <path>', 'Path to write gate log output', '/tmp/macp-gate.log')
    .option('--timeout <seconds>', 'Gate timeout in seconds', '60')
    .option(
      '--simulate',
      'Simulate gates instead of executing them; results are typed simulated and never satisfy a check',
    )
    .action(
      (
        spec: string,
        opts: { failOn: string; cwd: string; log: string; timeout: string; simulate?: boolean },
      ) => {
        let gates: unknown[];
        try {
          gates = loadGateSpec(spec);
        } catch (exc) {
          if (exc instanceof MACPCapabilityError) {
            console.error(`[macp] gate: ${exc.message} [${exc.code}]`);
          } else {
            console.error(`[macp] gate: ${String(exc)}`);
          }
          process.exitCode = 1;
          return;
        }

        const timeoutSec = Number.parseInt(opts.timeout, 10) || 60;
        const eventsPath = `${opts.log}.events.ndjson`;
        const { state, gateResults } = runGates(
          gates,
          opts.cwd,
          opts.log,
          timeoutSec,
          eventsPath,
          'macp-cli-gate',
          {
            simulate: opts.simulate,
          },
        );

        for (const r of gateResults) {
          const label = r.command || r.type;
          const reason = r.reason ? ` — ${r.reason}` : '';
          console.log(`[macp] gate ${r.status}: ${label}${reason}`);
        }
        if (opts.simulate) {
          console.log(
            '[macp] SIMULATED run — every result is typed simulated and can never satisfy a gate, dependency, or release check',
          );
        }

        // Simulated runs may complete (exit 0) only because the caller
        // explicitly passed --simulate; the typed state stays 'simulated'.
        process.exitCode = state === 'passed' || state === 'simulated' ? 0 : 1;
      },
    );

  // ─── events ──────────────────────────────────────────────────────────────

  const events = macp.command('events').description('Stream MACP events');

  events
    .command('tail')
    .description('Tail MACP events from the event log (wraps event emitter)')
    .option('--file <path>', 'Path to the MACP events NDJSON file')
    .option('--follow', 'Follow the file for new events (like tail -f)')
    .action((opts: { file?: string; follow?: boolean }) => {
      // unimplemented capability — a failure, never a success (RI-N2)
      if (opts.file) {
        console.log(`  file:   ${opts.file}`);
      }
      if (opts.follow) {
        console.log('  mode:   follow');
      }
      notImplemented('events tail', 'event-source', 'use the macp package programmatically');
    });
}

// Re-export so CLI consumers can surface typed capability codes.
export type { MacpErrorCode };
