import { beforeAll, describe, expect, it } from 'vitest';
import { execa } from 'execa';
import fs from 'node:fs';
import path from 'node:path';
import { Readable } from 'node:stream';
import { createPackageTarball } from './utils/createPackageTarball';
import { createTmpDir } from './utils/createTmpDir';
import { parseTar } from './utils/parseTar';
import { streamToBuffer } from './utils/streamToBuffer';

async function getPackageJsonOfTarball(tarballPath: string) {
  for await (const entry of parseTar(await fs.promises.readFile(tarballPath))) {
    if (entry.path === 'package/package.json') {
      const json = (await streamToBuffer(entry as unknown as Readable)).toString('utf-8');

      return JSON.parse(json);
    }
  }

  throw new Error(`package.json not found.`);
}

const ENTRYPOINTS = [
  '.',
  './array',
  './bigint',
  './compat',
  './compat/*',
  './server',
  './error',
  './fp',
  './function',
  './map',
  './math',
  './object',
  './predicate',
  './promise',
  './set',
  './string',
  './types',
  './util',
];

describe(`es-toolkit's package tarball`, () => {
  let tarball: { path: string };

  beforeAll(async () => {
    tarball = await createPackageTarball();
  }, 300000);

  it('configures all entrypoints correctly', { timeout: 240_000 }, async () => {
    const packageJson = await getPackageJsonOfTarball(tarball.path);
    const entrypoints = Object.keys(packageJson.exports);

    expect(entrypoints).toEqual([...ENTRYPOINTS, './package.json']);
  });

  it('exports identical functions in CJS and ESM', { timeout: 120_000 }, async () => {
    const tmpdir = await createTmpDir();

    const packageJson = {
      dependencies: {
        'es-toolkit': tarball.path,
      },
    };

    await fs.promises.writeFile(path.join(tmpdir, 'package.json'), JSON.stringify(packageJson, null, 2));
    await execa('npm', ['install'], { cwd: tmpdir });

    for (const entrypoint of ENTRYPOINTS) {
      // `./types` is declaration-only (no runtime export), so it can't be required or imported.
      if (entrypoint.includes('*') || entrypoint === './types') {
        continue;
      }

      const cjsScript = `
const toolkit = require("${path.join('es-toolkit', entrypoint)}");

const exported = Object.entries(toolkit)
  .map(([k, v]) => [k, typeof v])
  .sort((x, y) => x[0].localeCompare(y[0]));

console.log("${path.join('es-toolkit', entrypoint)}");
console.log(exported);
      `.trim();
      const cjsScriptPath = path.join(tmpdir, 'script.cjs');

      const esmScript = `
const toolkit = await import("${path.join('es-toolkit', entrypoint)}");

const exported = Object.entries(toolkit)
  .map(([k, v]) => [k, typeof v])
  .sort((x, y) => x[0].localeCompare(y[0]));

console.log("${path.join('es-toolkit', entrypoint)}");
console.log(exported);
      `.trim();
      const esmScriptPath = path.join(tmpdir, 'script.mjs');

      await fs.promises.writeFile(cjsScriptPath, cjsScript);
      await fs.promises.writeFile(esmScriptPath, esmScript);

      const cjsResult = await execa('node', [cjsScriptPath]);
      const esmResult = await execa('node', [esmScriptPath]);

      expect(cjsResult.stdout).toEqual(esmResult.stdout);
    }
  });
});
