const fs = require('fs');
const cp = require('child_process');
const tmp = require('tmp');
const path = require('path');

let logger = require('../../logger');

module.exports = {
  generateLockFile,
  getLockFile,
  maintainLockFile,
};

async function generateLockFile(newPackageJson, npmrcContent) {
  logger.debug('Generating new package-lock.json file');
  const tmpDir = tmp.dirSync({ unsafeCleanup: true });
  let packageLock;
  try {
    fs.writeFileSync(path.join(tmpDir.name, 'package.json'), newPackageJson);
    if (npmrcContent) {
      fs.writeFileSync(path.join(tmpDir.name, '.npmrc'), npmrcContent);
    }
    logger.debug('Spawning npm install');
    const result = cp.spawnSync('npm', ['install', '--ignore-scripts'], {
      cwd: tmpDir.name,
      shell: true,
    });
    logger.debug(
      { stdout: String(result.stdout), stderr: String(result.stderr) },
      'npm install complete'
    );
    packageLock = fs.readFileSync(path.join(tmpDir.name, 'package-lock.json'));
  } catch (error) /* istanbul ignore next */ {
    try {
      tmpDir.removeCallback();
    } catch (err2) {
      logger.warn(`Failed to remove tmpDir ${tmpDir.name}`);
    }
    throw error;
  }
  try {
    tmpDir.removeCallback();
  } catch (err2) {
    logger.warn(`Failed to remove tmpDir ${tmpDir.name}`);
  }
  return packageLock;
}

async function getLockFile(
  packageFile,
  packageContent,
  api,
  npmVersion,
  parentLogger
) {
  logger = parentLogger || logger;
  // Detect if a package-lock.json file is in use
  const packageLockFileName = path.join(
    path.dirname(packageFile),
    'package-lock.json'
  );
  if (!await api.getFileContent(packageLockFileName)) {
    return null;
  }
  if (npmVersion === '') {
    throw new Error(
      'Need to generate package-lock.json but npm is not installed'
    );
  }
  // TODO: have a more forwards-compatible check
  if (npmVersion[0] !== '5') {
    throw new Error(
      `Need to generate package-lock.json but npm version is "${npmVersion}"`
    );
  }
  // Copy over custom config commitFiles
  const npmrcContent = await api.getFileContent('.npmrc');
  // Generate package-lock.json using shell command
  const newPackageLockContent = await module.exports.generateLockFile(
    packageContent,
    npmrcContent
  );
  // Return file object
  return {
    name: packageLockFileName,
    contents: newPackageLockContent,
  };
}

async function maintainLockFile(inputConfig) {
  logger = inputConfig.logger || logger;
  logger.trace({ config: inputConfig }, `maintainLockFile`);
  const packageContent = await inputConfig.api.getFileContent(
    inputConfig.packageFile
  );
  const packageLockFileName = path.join(
    path.dirname(inputConfig.packageFile),
    'package-lock.json'
  );
  logger.debug(`Checking for ${packageLockFileName}`);
  const existingPackageLock = await inputConfig.api.getFileContent(
    packageLockFileName
  );
  logger.trace(`existingPackageLock:\n${existingPackageLock}`);
  if (!existingPackageLock) {
    return null;
  }
  logger.debug('Found existing package-lock.json file');
  const newPackageLock = await module.exports.getLockFile(
    inputConfig.packageFile,
    packageContent,
    inputConfig.api
  );
  logger.trace(`newPackageLock:\n${newPackageLock.contents}`);
  if (existingPackageLock.toString() === newPackageLock.contents.toString()) {
    logger.debug('npm lock file does not need updating');
    return null;
  }
  logger.debug('npm lock needs updating');
  return newPackageLock;
}