/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
import path from 'path';
import execa from 'execa';
import {onNodeVersions} from '@jest/test-utils';
import type {ReadJestConfigOptions, readInitialOptions} from 'jest-config';

function resolveFixture(...pathSegments: Array<string>) {
  return path.resolve(__dirname, '..', 'read-initial-options', ...pathSegments);
}

interface ProxyReadJestConfigOptions extends ReadJestConfigOptions {
  cwd?: string;
}

/**
 * These e2e tests are running via a child process, because we're running in a VM and are not allowed to `import` directly
 * It also represents a more real-world example of how to run.
 */
async function proxyReadInitialOptions(
  configFile: string | undefined,
  options: ProxyReadJestConfigOptions,
): ReturnType<typeof readInitialOptions> {
  const {stdout} = await execa(
    'node',
    [
      require.resolve('../read-initial-options/readOptions.js'),
      configFile ?? '',
      JSON.stringify(options),
    ],
    {cwd: options?.cwd},
  );
  return JSON.parse(stdout);
}

describe('readInitialOptions', () => {
  test('should read from the cwd by default', async () => {
    const configFile = resolveFixture('js-config', 'jest.config.js');
    const rootDir = resolveFixture('js-config');
    const {config, configPath} = await proxyReadInitialOptions(undefined, {
      cwd: rootDir,
    });
    expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
    expect(configPath).toEqual(configFile);
  });

  test.each([
    ['js-config', 'jest.config.js', 'jest.config.js'],
    ['pkg-config', 'package.json', 'package.json'],
    ['ts-node-config', 'jest.config.ts', 'jest.config.ts'],
    ['ts-esbuild-register-config', 'jest.config.ts', 'jest.config.ts'],
    ['mjs-config', 'jest.config.mjs', 'jest.config.mjs'],
    ['json-config', 'jest.config.json', 'jest.config.json'],
    ['async-config', 'jest.config.js', 'async-config'],
  ])(
    'should read %s/%s file',
    async (directory: string, filename: string, configString: string) => {
      const configFile = resolveFixture(directory, filename);
      const rootDir = resolveFixture(directory);
      const {config, configPath} = await proxyReadInitialOptions(undefined, {
        cwd: rootDir,
      });
      expect(config).toEqual({jestConfig: configString, rootDir});
      expect(configPath).toEqual(configFile);
    },
  );

  onNodeVersions('^22.18 || >=23.6', () => {
    test('should read mts-config/jest.config.mts file', async () => {
      const configFile = resolveFixture('mts-config', 'jest.config.mts');
      const rootDir = resolveFixture('mts-config');
      const {config, configPath} = await proxyReadInitialOptions(undefined, {
        cwd: rootDir,
      });
      expect(config).toEqual({jestConfig: 'jest.config.mts', rootDir});
      expect(configPath).toEqual(configFile);
    });
  });

  onNodeVersions('<22.18', () => {
    test('should fail to read mts-config/jest.config.mts with a clear error', async () => {
      const rootDir = resolveFixture('mts-config');
      await expect(
        proxyReadInitialOptions(undefined, {cwd: rootDir}),
      ).rejects.toThrow(/jest\.config\.mts requires native TypeScript support/);
    });
  });

  test('should be able to skip config reading, instead read from cwd', async () => {
    const expectedConfigFile = resolveFixture(
      'json-config',
      'jest.config.json',
    );
    const {config, configPath} = await proxyReadInitialOptions(
      resolveFixture('js-config', 'jest.config.js'),
      {
        cwd: resolveFixture('json-config'),
        readFromCwd: true,
      },
    );

    expect(config).toEqual({
      jestConfig: 'jest.config.json',
      rootDir: path.dirname(expectedConfigFile),
    });
    expect(configPath).toEqual(expectedConfigFile);
  });

  onNodeVersions('<22.6', () => {
    test('should give an error when using unsupported loader', async () => {
      const cwd = resolveFixture('ts-loader-config');
      const error: Error = await proxyReadInitialOptions(undefined, {
        cwd,
      }).catch(error => error);
      expect(error.message).toContain(
        "Jest: 'ts-loader' is not a valid TypeScript configuration loader.",
      );
    });
  });

  test('should give an error when there are multiple config files', async () => {
    const cwd = resolveFixture('multiple-config-files');
    const error: Error = await proxyReadInitialOptions(undefined, {cwd}).catch(
      error => error,
    );
    expect(error.message).toContain('Multiple configurations found');
    expect(error.message).toContain('multiple-config-files/jest.config.js');
    expect(error.message).toContain('multiple-config-files/jest.config.json');
  });

  test('should be able to ignore multiple config files error', async () => {
    const cwd = resolveFixture('multiple-config-files');
    const {config, configPath} = await proxyReadInitialOptions(undefined, {
      cwd,
      skipMultipleConfigError: true,
    });
    expect(config).toEqual({
      jestConfig: 'jest.config.js',
      rootDir: resolveFixture('multiple-config-files'),
    });
    expect(configPath).toEqual(
      resolveFixture('multiple-config-files', 'jest.config.js'),
    );
  });
});
