import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
import { Command } from 'commander';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { registerMacpCommand } from './cli.js';

describe('registerMacpCommand', () => {
  function buildProgram(): Command {
    const program = new Command();
    program.exitOverride(); // prevent process.exit in tests
    registerMacpCommand(program);
    return program;
  }

  it('registers a "macp" command on the parent', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp');
    expect(macpCmd).toBeDefined();
  });

  it('registers "macp tasks" subcommand group', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const tasksCmd = macpCmd.commands.find((c) => c.name() === 'tasks');
    expect(tasksCmd).toBeDefined();
  });

  it('registers "macp tasks list" subcommand with --status and --type flags', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const tasksCmd = macpCmd.commands.find((c) => c.name() === 'tasks')!;
    const listCmd = tasksCmd.commands.find((c) => c.name() === 'list');
    expect(listCmd).toBeDefined();
    const optionNames = listCmd!.options.map((o) => o.long);
    expect(optionNames).toContain('--status');
    expect(optionNames).toContain('--type');
  });

  it('registers "macp submit" subcommand', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const submitCmd = macpCmd.commands.find((c) => c.name() === 'submit');
    expect(submitCmd).toBeDefined();
  });

  it('registers "macp gate" subcommand with --fail-on flag', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const gateCmd = macpCmd.commands.find((c) => c.name() === 'gate');
    expect(gateCmd).toBeDefined();
    const optionNames = gateCmd!.options.map((o) => o.long);
    expect(optionNames).toContain('--fail-on');
  });

  it('registers "macp events" subcommand group', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const eventsCmd = macpCmd.commands.find((c) => c.name() === 'events');
    expect(eventsCmd).toBeDefined();
  });

  it('registers "macp events tail" subcommand', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const eventsCmd = macpCmd.commands.find((c) => c.name() === 'events')!;
    const tailCmd = eventsCmd.commands.find((c) => c.name() === 'tail');
    expect(tailCmd).toBeDefined();
  });

  it('has all required top-level subcommands', () => {
    const program = buildProgram();
    const macpCmd = program.commands.find((c) => c.name() === 'macp')!;
    const topLevel = macpCmd.commands.map((c) => c.name());
    expect(topLevel).toContain('tasks');
    expect(topLevel).toContain('submit');
    expect(topLevel).toContain('gate');
    expect(topLevel).toContain('events');
  });
});

/**
 * RI-N2 fail-closed CLI behavior: an unimplemented capability is a failure,
 * never a success. Every stub exits nonzero with a typed message, and the
 * implemented `macp gate` mirrors the typed gate-runner states.
 */
describe('registerMacpCommand fail-closed (RI-N2)', () => {
  let tmpDir: string;

  function buildProgram(): Command {
    const program = new Command();
    program.exitOverride();
    program.configureOutput({ writeErr: () => {} });
    registerMacpCommand(program);
    return program;
  }

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'macp-cli-failclosed-'));
    process.exitCode = 0;
  });

  afterEach(() => {
    process.exitCode = 0;
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('macp tasks list exits nonzero (unimplemented capability)', async () => {
    const program = buildProgram();
    await program.parseAsync(['macp', 'tasks', 'list'], { from: 'user' });
    expect(process.exitCode).not.toBe(0);
  });

  it('macp submit exits nonzero with a typed MACP_NOT_IMPLEMENTED message', async () => {
    const program = buildProgram();
    const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    try {
      await program.parseAsync(['macp', 'submit', 'spec.json'], { from: 'user' });
      expect(process.exitCode).not.toBe(0);
      const errText = errSpy.mock.calls.map((c) => String(c[0])).join('\n');
      expect(errText).toContain('MACP_NOT_IMPLEMENTED');
    } finally {
      errSpy.mockRestore();
    }
  });

  it('macp events tail exits nonzero (unimplemented capability)', async () => {
    const program = buildProgram();
    await program.parseAsync(['macp', 'events', 'tail'], { from: 'user' });
    expect(process.exitCode).not.toBe(0);
  });

  it('macp gate runs a green inline command and exits 0', async () => {
    const program = buildProgram();
    await program.parseAsync(
      [
        'macp',
        'gate',
        'exit 0',
        '--cwd',
        tmpDir,
        '--log',
        path.join(tmpDir, 'g.log'),
        '--timeout',
        '10',
      ],
      { from: 'user' },
    );
    expect(process.exitCode).toBe(0);
  });

  it('macp gate exits nonzero on a failing command', async () => {
    const program = buildProgram();
    await program.parseAsync(
      [
        'macp',
        'gate',
        'exit 9',
        '--cwd',
        tmpDir,
        '--log',
        path.join(tmpDir, 'g.log'),
        '--timeout',
        '10',
      ],
      { from: 'user' },
    );
    expect(process.exitCode).not.toBe(0);
  });

  it('macp gate with an unimplemented ci-pipeline capability exits nonzero', async () => {
    const program = buildProgram();
    const specPath = path.join(tmpDir, 'gates.json');
    fs.writeFileSync(specPath, JSON.stringify([{ type: 'ci-pipeline' }]));
    await program.parseAsync(
      [
        'macp',
        'gate',
        specPath,
        '--cwd',
        tmpDir,
        '--log',
        path.join(tmpDir, 'g.log'),
        '--timeout',
        '10',
      ],
      { from: 'user' },
    );
    expect(process.exitCode).not.toBe(0);
  });

  it('macp gate --simulate completes (exit 0) but reports simulated results', async () => {
    const program = buildProgram();
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    try {
      await program.parseAsync(
        [
          'macp',
          'gate',
          'exit 0',
          '--simulate',
          '--cwd',
          tmpDir,
          '--log',
          path.join(tmpDir, 'g.log'),
          '--timeout',
          '10',
        ],
        { from: 'user' },
      );
      // completes only because the caller explicitly asked to simulate
      expect(process.exitCode).toBe(0);
      const outText = logSpy.mock.calls.map((c) => String(c[0])).join('\n');
      expect(outText).toContain('simulated');
      expect(outText).toContain('SIMULATED');
    } finally {
      logSpy.mockRestore();
    }
  });

  it('macp gate with an empty spec exits nonzero with a typed error', async () => {
    const program = buildProgram();
    await program.parseAsync(
      [
        'macp',
        'gate',
        '   ',
        '--cwd',
        tmpDir,
        '--log',
        path.join(tmpDir, 'g.log'),
        '--timeout',
        '10',
      ],
      { from: 'user' },
    );
    expect(process.exitCode).not.toBe(0);
  });
});
