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';
import { runMxCli, parseLastJson, assertExitCode } from '../helpers/cli-harness.js';

function createLifecyclePackage(rootDir: string, packageName: string, scriptBody: string): string {
  const packageDir = path.join(rootDir, packageName.replace(/^@/, '').replace('/', '-'));
  fs.mkdirSync(path.join(packageDir, 'dist'), { recursive: true });
  fs.writeFileSync(
    path.join(packageDir, 'matrix.json'),
    JSON.stringify(
      {
        name: packageName,
        version: '1.0.0',
        runtime: { language: 'javascript', entry: 'dist/index.js' },
        components: [],
        install: {
          seed: {
            script: 'dist/seed.js',
            description: 'seed test hook',
          },
        },
        permissions: { fsPolicy: 'none' },
      },
      null,
      2,
    ),
    'utf8',
  );
  fs.writeFileSync(
    path.join(packageDir, 'package.json'),
    JSON.stringify(
      {
        name: packageName,
        version: '1.0.0',
        type: 'module',
        main: 'dist/index.js',
      },
      null,
      2,
    ),
    'utf8',
  );
  fs.writeFileSync(path.join(packageDir, 'dist', 'index.js'), 'export const ready = true;\n', 'utf8');
  fs.writeFileSync(path.join(packageDir, 'dist', 'seed.js'), scriptBody, 'utf8');
  return packageDir;
}

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

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

  it('runs install lifecycle hooks and exposes install env', async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-lifecycle-'));
    tempDirs.push(root);
    const workspaceDir = path.join(root, 'workspace');
    fs.mkdirSync(workspaceDir, { recursive: true });
    const packagesDir = path.join(root, '.matrix', 'packages');
    const markerPath = path.join(root, 'seed-marker.json');
    process.env.MX_TEST_MARKER = markerPath;

    const packageDir = createLifecyclePackage(
      root,
      '@acme/install-lifecycle',
      `import { writeFileSync } from 'node:fs';
const marker = process.env.MX_TEST_MARKER;
if (!marker) throw new Error('missing marker');
writeFileSync(marker, JSON.stringify({
  phase: process.env.MATRIX_INSTALL_PHASE,
  packagesDir: process.env.MATRIX_PACKAGES_DIR,
  packageDir: process.env.MATRIX_PACKAGE_DIR,
  packageName: process.env.MATRIX_PACKAGE_NAME
}), 'utf8');
`,
    );

    const result = await runMxCli([
      'node',
      'mx',
      'install',
      packageDir,
      '--packages-dir',
      packagesDir,
      '--json',
    ], { cwd: workspaceDir });
    assertExitCode(result, 0);

    const payload = parseLastJson(result.logs) as {
      targetDir: string;
      lifecyclePhases: string[];
    };
    assert.deepEqual(payload.lifecyclePhases, ['seed']);
    assert.equal(fs.existsSync(payload.targetDir), true);
    assert.equal(fs.existsSync(markerPath), true);

    const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as Record<string, string>;
    assert.equal(marker.phase, 'seed');
    assert.equal(path.resolve(marker.packagesDir), path.resolve(packagesDir));
    assert.equal(path.resolve(marker.packageDir), path.resolve(payload.targetDir));
    assert.equal(marker.packageName, '@acme/install-lifecycle');
  });

  it('rolls back package install when a lifecycle hook fails', async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-lifecycle-fail-'));
    tempDirs.push(root);
    const workspaceDir = path.join(root, 'workspace');
    fs.mkdirSync(workspaceDir, { recursive: true });
    const packagesDir = path.join(root, '.matrix', 'packages');

    const packageDir = createLifecyclePackage(
      root,
      '@acme/install-lifecycle-fail',
      `throw new Error('seed failed');\n`,
    );

    const result = await runMxCli([
      'node',
      'mx',
      'install',
      packageDir,
      '--packages-dir',
      packagesDir,
      '--json',
    ], { cwd: workspaceDir });
    assertExitCode(result, 1);
    assert.equal(
      result.errors.some((line) => line.includes('install.seed failed')),
      true,
      `Expected lifecycle failure in stderr, got:\n${result.errors.join('\n')}`,
    );

    const installedPath = path.join(packagesDir, 'node_modules', '@acme', 'install-lifecycle-fail');
    assert.equal(fs.existsSync(installedPath), false);
  });
});
