import { afterEach, describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';

describe('FP-30 mx lifecycle commands', () => {
  const tempDirs: string[] = [];

  function createLocalPackage(rootDir: string): { packageDir: string; packageName: string } {
    const packageName = '@acme/weather-actor';
    const packageDir = path.join(rootDir, 'weather-actor');
    fs.mkdirSync(path.join(packageDir, 'src'), { recursive: true });
    fs.writeFileSync(
      path.join(packageDir, 'matrix.json'),
      JSON.stringify(
        {
          name: packageName,
          version: '1.0.0',
          runtime: { language: 'typescript', entry: 'src/index.ts' },
          components: [
            {
              type: 'WeatherActor',
              export: 'WeatherActor',
              mount: 'weather.actor',
              autoStart: true,
            },
          ],
          permissions: { fsPolicy: 'none' },
        },
        null,
        2
      ),
      'utf8'
    );
    fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify({ name: packageName, version: '1.0.0' }, null, 2));
    fs.writeFileSync(path.join(packageDir, 'src', 'index.ts'), 'export class WeatherActor {}\n', 'utf8');
    return { packageDir, packageName };
  }

  async function loadMxCli(): Promise<{ createProgram: () => import('commander').Command }> {
    const mxCliUrl = new URL('../../src/index.ts', import.meta.url).href;
    return await import(`${mxCliUrl}?test=${Date.now()}-${Math.random().toString(16).slice(2)}`);
  }

  async function runMxCli(argv: string[]): Promise<string[]> {
    const logs: string[] = [];
    const originalLog = console.log;
    const originalExit = process.exit;
    const { createProgram } = await loadMxCli();
    console.log = (...args: unknown[]) => {
      logs.push(args.map((arg) => String(arg)).join(' '));
    };
    (process as unknown as { exit: (code?: number) => never }).exit = ((code?: number) => {
      throw new Error(`process.exit(${code ?? 0})`);
    }) as never;

    try {
      const program = createProgram();
      await program.parseAsync(argv);
      return logs;
    } finally {
      console.log = originalLog;
      (process as unknown as { exit: typeof process.exit }).exit = originalExit;
    }
  }

  function parseLastJson(logs: string[]): unknown {
    for (let i = logs.length - 1; i >= 0; i--) {
      const value = logs[i].trim();
      if (!value) continue;
      try {
        return JSON.parse(value);
      } catch {
        // Continue scanning log lines until JSON is found.
      }
    }
    throw new Error(`No JSON payload found in logs:\n${logs.join('\n')}`);
  }

  afterEach(() => {
    while (tempDirs.length > 0) {
      const dir = tempDirs.pop();
      if (dir) {
        fs.rmSync(dir, { recursive: true, force: true });
      }
    }
  });

  it('installs, lists, inspects, validates, and uninstalls local packages', async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-lifecycle-'));
    tempDirs.push(root);
    const cwd = path.join(root, 'workspace');
    const packagesDir = path.join(cwd, '.matrix', 'packages');
    fs.mkdirSync(cwd, { recursive: true });

    const { packageDir, packageName } = createLocalPackage(root);

    await runMxCli([
      'node',
      'mx',
      'install',
      packageDir,
      '--packages-dir',
      packagesDir,
      '--json',
    ]);

    const installedPath = path.join(packagesDir, 'node_modules', '@acme', 'weather-actor');
    assert.equal(fs.existsSync(installedPath), true);

    const listedAfterInstall = parseLastJson(await runMxCli([
      'node',
      'mx',
      'list',
      '--packages-dir',
      packagesDir,
      '--json',
    ])) as string[];
    assert.equal(Array.isArray(listedAfterInstall), true);
    assert.equal(listedAfterInstall.includes(packageName), true);

    const infoPayload = parseLastJson(await runMxCli([
      'node',
      'mx',
      'info',
      packageName,
      '--packages-dir',
      packagesDir,
      '--json',
    ])) as { packageName: string; installPath: string };
    assert.equal(infoPayload.packageName, packageName);
    assert.equal(path.resolve(infoPayload.installPath), path.resolve(installedPath));

    const validatePayload = parseLastJson(await runMxCli([
      'node',
      'mx',
      'validate',
      path.join(packageDir, 'matrix.json'),
      '--json',
    ])) as { packageName: string; valid: boolean };
    assert.equal(validatePayload.packageName, packageName);
    assert.equal(validatePayload.valid, true);

    await runMxCli([
      'node',
      'mx',
      'uninstall',
      packageName,
      '--packages-dir',
      packagesDir,
      '--json',
    ]);
    assert.equal(fs.existsSync(installedPath), false);

    const listedAfterUninstall = parseLastJson(await runMxCli([
      'node',
      'mx',
      'list',
      '--packages-dir',
      packagesDir,
      '--json',
    ])) as string[];
    assert.equal(Array.isArray(listedAfterUninstall), true);
    assert.equal(listedAfterUninstall.includes(packageName), false);
  });
});
