// +------------------------------------------------------------------------------------------------
// | this script is executed post packaging to verify that experimental modules in aws-cdk-lib includes **only**  L1 autogenerated files.
// | The purpose is to avoid publishing L2 of experimental modules with aws-cdk-lib
// |
import { spawnSync } from 'child_process';
import * as console from 'console';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs-extra';

async function main(tempDir: string) {
  console.log('🧐 Verifying all experimental modules includes only L1s files...');
  const cwd = process.cwd();
  const awsCdkModulesRepoPath = path.join(findWorkspacePath(), 'packages', '@aws-cdk');
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  const version = require('./../package.json').version;
  const tarFullPath = path.join(cwd, 'dist', 'js', `aws-cdk-lib@${version}.jsii.tgz`);

  const invalidCfnModules = new Map<string, Array<String>>();
  const invalidModules = new Array<string>();

  // install the tarball in a temp directory
  console.log(`installing aws-cdk-lib from dist/js into ${tempDir}`);
  exec('npm', ['install', '--prefix', tempDir, tarFullPath]);
  const installedAwsCdkLibPath = path.join(tempDir, 'node_modules', 'aws-cdk-lib', 'lib');

  for (const module of fs.readdirSync(awsCdkModulesRepoPath)) {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const pkgJson = require(path.join(awsCdkModulesRepoPath, module, 'package.json'));
    if (pkgJson.stability !== 'experimental') {
      continue;
    }
    if (pkgJson['cdk-build']?.cloudformation) {
      // if a cfn module, verify only the allowed files exists
      const files = await listAllFiles(path.join(installedAwsCdkLibPath, module));
      const invalidFiles = new Array();
      files.forEach(file => {
        if (!isAllowedFile(file)) {
          invalidFiles.push(file);
        }
      });
      if (invalidFiles.length > 0) {
        invalidCfnModules.set(module, invalidFiles);
      }
    } else {
      // not a cfn module, verify it was entirely removed
      if (fs.existsSync(path.join(installedAwsCdkLibPath, module))) {
        invalidModules.push(module);
      }
    }
  }

  if (invalidCfnModules.size > 0 || invalidModules.length > 0) {
    if (invalidCfnModules.size > 0 ) {
      console.log('cfn module with invalid files:');
      for (let [module, files] of invalidCfnModules.entries()) {
        console.log(`${module}:`);
        files.forEach(file => console.log(`\t ${file}`));
      }
    }
    console.log('---------------------------------------------');
    if (invalidModules.length > 0) {
      console.log('non-cfn experimental modules:');
      invalidModules.forEach(m => console.log(`\t ${m}`));
    }
    throw new Error('Verification Error');
  }
}

const tempDir = fs.mkdtempSync(os.tmpdir());

main(tempDir).then(
  () => {
    fs.removeSync(tempDir);
    console.log('✅ All experimental modules includes only L1s files!');
    process.exit(0);
  },
  (err) => {
    process.stderr.write(`${err}\n`);
    process.stderr.write(`❌ Verification failed, Some experimental modules includes non L1 files, see details above. Inspect working directory: '${tempDir}'`);
    process.exit(1);
  },
);


/**
 * Spawn sync with error handling
 */
function exec(cmd: string, args: string[]) {
  const proc = spawnSync(cmd, args);

  if (proc.error) {
    throw proc.error;
  }

  if (proc.status !== 0) {
    if (proc.stdout || proc.stderr) {
      throw new Error(`${cmd} exited with status ${proc.status}; stdout: ${proc.stdout?.toString().trim()}\n\n\nstderr: ${proc.stderr?.toString().trim()}`);
    }
    throw new Error(`${cmd} exited with status ${proc.status}`);
  }

  return proc;
}

const GENERATED_SUFFIX_REGEX = new RegExp(/generated\.(js|d\.ts)$/);
const ALLOWED_FILES = ['.jsiirc.json', 'index.ts', 'index.js', 'index.d.ts'];

/**
 * Recursively collect all files in dir
 */
async function listAllFiles(dir: string) {
  const ret = new Array();

  async function recurse(part: string) {
    const files = await fs.readdir(part);
    for (const file of files) {
      const fullPath = path.join(part, file);
      if ((await fs.stat(fullPath)).isDirectory()) {
        await recurse(fullPath);
      } else {
        ret.push(file);
      }
    }
  }
  await recurse(dir);
  return ret;
}

/**
 * Find the workspace root path. Walk up the directory tree until you find lerna.json
 */
function findWorkspacePath() {

  return _findRootPath(process.cwd());

  function _findRootPath(part: string): string {
    if (part === path.resolve(part, '..')) {
      throw new Error('couldn\'t find a \'lerna.json\' file when walking up the directory tree, are you in a aws-cdk project?');
    }

    if (fs.existsSync(path.resolve(part, 'lerna.json'))) {
      return part;
    }
    return _findRootPath(path.resolve(part, '..'));
  }
}

/**
 * @param file
 * @returns true if the file allowed in an L1 only modules, otherwise false
 */
function isAllowedFile(file: string) {
  if (GENERATED_SUFFIX_REGEX.test(file)) {
    return true;
  }
  return ALLOWED_FILES.includes(file);
}