/**
 * @module
 *
 * This module implements auto-injected manual mocks that are cleaner than Jest's approach.
 *
 * See https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html
 */

import fs from 'fs';
import path from 'path';

import readdir from 'readdir-enhanced';

const MAX_DEPTH = 20;
const prefixMap = [
  // E.g. the mock ce/foo/bar maps to require path ~/foo/bar
  { mocksRoot: 'ce', requirePrefix: '~' },
  // { mocksRoot: 'ee', requirePrefix: 'ee' }, // We'll deal with EE-specific mocks later
  { mocksRoot: 'node', requirePrefix: '' },
  // { mocksRoot: 'virtual', requirePrefix: '' }, // We'll deal with virtual mocks later
];

const mockFileFilter = stats => stats.isFile() && stats.path.endsWith('.js');

const getMockFiles = root => readdir.sync(root, { deep: MAX_DEPTH, filter: mockFileFilter });

// Function that performs setting a mock. This has to be overridden by the unit test, because
// jest.setMock can't be overwritten across files.
// Use require() because jest.setMock expects the CommonJS exports object
const defaultSetMock = (srcPath, mockPath) =>
  jest.mock(srcPath, () => jest.requireActual(mockPath));

// eslint-disable-next-line import/prefer-default-export
export const setupManualMocks = function setupManualMocks(setMock = defaultSetMock) {
  prefixMap.forEach(({ mocksRoot, requirePrefix }) => {
    const mocksRootAbsolute = path.join(__dirname, mocksRoot);
    if (!fs.existsSync(mocksRootAbsolute)) {
      return;
    }

    getMockFiles(path.join(__dirname, mocksRoot)).forEach(mockPath => {
      const mockPathNoExt = mockPath.substring(0, mockPath.length - path.extname(mockPath).length);
      const sourcePath = path.join(requirePrefix, mockPathNoExt);
      const mockPathRelative = `./${path.join(mocksRoot, mockPathNoExt)}`;

      try {
        setMock(sourcePath, mockPathRelative);
      } catch (e) {
        if (e.message.includes('Could not locate module')) {
          // The corresponding mocked module doesn't exist. Raise a better error.
          // Eventualy, we may support virtual mocks (mocks whose path doesn't directly correspond
          // to a module, like with the `ee_else_ce` prefix).
          throw new Error(
            `A manual mock was defined for module ${sourcePath}, but the module doesn't exist!`,
          );
        }
      }
    });
  });
};
