import { execFile } from 'node:child_process';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';

const here = path.dirname(fileURLToPath(import.meta.url));
const scriptPath = path.resolve(here, '..', 'check-module-boundaries.mjs');
const fixturesDir = path.resolve(here, 'fixtures');

function runChecker(rootDir: string): Promise<{ code: number; output: string }> {
  return new Promise((resolve) => {
    execFile(
      process.execPath,
      [scriptPath, rootDir],
      { encoding: 'utf8' },
      (error, stdout, stderr) => {
        const code = error !== null && 'code' in error ? (error.code as number) : 0;
        resolve({ code, output: `${stdout}\n${stderr}` });
      },
    );
  });
}

describe('check-module-boundaries', () => {
  it('passes on a clean tree', async () => {
    const { code, output } = await runChecker(path.join(fixturesDir, 'ok'));
    expect(output).toContain('Boundary check OK');
    expect(code).toBe(0);
  });

  it('fails when a module imports another module internal file (R1)', async () => {
    const { code, output } = await runChecker(path.join(fixturesDir, 'cross-module-internal'));
    expect(output).toContain('R1 violation');
    expect(code).toBe(1);
  });

  it('fails when outside code deep-imports a module internal file (R2)', async () => {
    const { code, output } = await runChecker(path.join(fixturesDir, 'deep-from-app'));
    expect(output).toContain('R2 violation');
    expect(code).toBe(1);
  });

  it('allows outside code to import a module index (R2 ok)', async () => {
    const { code, output } = await runChecker(path.join(fixturesDir, 'ok'));
    expect(output).toContain('Boundary check OK');
    expect(code).toBe(0);
  });
});
