const fs = require('fs-extra');
const path = require('path');
const npm = require('./npm');
const yarn = require('./yarn');

module.exports = {
  hasPackageLock,
  hasYarnLock,
  determineLockFileDirs,
  writeExistingFiles,
  writeUpdatedPackageFiles,
  getUpdatedLockFiles,
};

function hasPackageLock(config, packageFile) {
  const { logger } = config;
  logger.trace(
    { packageFiles: config.packageFiles, packageFile },
    'hasPackageLock'
  );
  for (const p of config.packageFiles) {
    if (p.packageFile === packageFile) {
      return p.hasPackageLock === true;
    }
  }
  throw new Error(`hasPackageLock cannot find ${packageFile}`);
}

function hasYarnLock(config, packageFile) {
  const { logger } = config;
  logger.trace(
    { packageFiles: config.packageFiles, packageFile },
    'hasYarnLock'
  );
  for (const p of config.packageFiles) {
    if (p.packageFile === packageFile) {
      return p.hasYarnLock === true;
    }
  }
  throw new Error(`hasYarnLock cannot find ${packageFile}`);
}

function determineLockFileDirs(config) {
  const packageLockFileDirs = [];
  const yarnLockFileDirs = [];

  for (const upgrade of config.upgrades) {
    if (upgrade.type === 'lockFileMaintenance') {
      // Return every direcotry that contains a lockfile
      for (const packageFile of config.packageFiles) {
        const dirname = path.dirname(packageFile.packageFile);
        if (packageFile.hasYarnLock) {
          yarnLockFileDirs.push(dirname);
        }
        if (packageFile.hasPackageLock) {
          packageLockFileDirs.push(dirname);
        }
      }
      return { packageLockFileDirs, yarnLockFileDirs };
    }
  }

  for (const packageFile of config.updatedPackageFiles) {
    if (module.exports.hasYarnLock(config, packageFile.name)) {
      yarnLockFileDirs.push(path.dirname(packageFile.name));
    }
    if (module.exports.hasPackageLock(config, packageFile.name)) {
      packageLockFileDirs.push(path.dirname(packageFile.name));
    }
  }

  // If yarn workspaces are in use, then we need to generate yarn.lock from root dir
  if (config.updatedPackageFiles.length && config.hasYarnWorkspaces) {
    yarnLockFileDirs.push(path.dirname('package.json'));
  }

  return { yarnLockFileDirs, packageLockFileDirs };
}

async function writeExistingFiles(config) {
  const { logger } = config;
  if (config.npmrc) {
    logger.debug('Writing repo .npmrc');
    await fs.outputFile(path.join(config.tmpDir.name, '.npmrc'), config.npmrc);
  }
  if (config.yarnrc) {
    logger.debug('Writing repo .yarnrc');
    await fs.outputFile(
      path.join(config.tmpDir.name, '.yarnrc'),
      config.yarnrc
    );
  }
  if (!config.packageFiles) {
    return;
  }
  for (const packageFile of config.packageFiles) {
    const basedir = path.join(
      config.tmpDir.name,
      path.dirname(packageFile.packageFile)
    );
    if (packageFile.packageFile.endsWith('package.json')) {
      logger.debug(`Writing package.json to ${basedir}`);
      // Massage the file to eliminate yarn errors
      const massagedFile = { ...packageFile.content };
      if (massagedFile.name && !massagedFile.name.match(/^[0-9a-z-_]+$/)) {
        massagedFile.name = 'dummy';
      }
      delete massagedFile.engines;
      delete massagedFile.scripts;
      await fs.outputFile(
        path.join(basedir, 'package.json'),
        JSON.stringify(massagedFile)
      );
    }
    if (packageFile.npmrc) {
      logger.debug(`Writing .npmrc to ${basedir}`);
      await fs.outputFile(path.join(basedir, '.npmrc'), packageFile.npmrc);
    }
    if (packageFile.yarnrc) {
      logger.debug(`Writing .yarnrc to ${basedir}`);
      await fs.outputFile(
        path.join(basedir, '.yarnrc'),
        packageFile.yarnrc.replace('--install.pure-lockfile true', '')
      );
    }
    await fs.remove(path.join(basedir, 'yarn.lock'));
    await fs.remove(path.join(basedir, 'package-lock.json'));
  }
}

async function writeUpdatedPackageFiles(config) {
  const { logger } = config;
  logger.trace({ config }, 'writeUpdatedPackageFiles');
  logger.debug('Writing any updated package files');
  if (!config.updatedPackageFiles) {
    logger.debug('No files found');
    return;
  }
  for (const packageFile of config.updatedPackageFiles) {
    logger.debug(`Writing ${packageFile.name}`);
    const massagedFile = JSON.parse(packageFile.contents);
    if (massagedFile.name && !massagedFile.name.match(/^[0-9a-z-_]+$/)) {
      massagedFile.name = 'dummy';
    }
    delete massagedFile.engines;
    delete massagedFile.scripts;
    await fs.outputFile(
      path.join(config.tmpDir.name, packageFile.name),
      JSON.stringify(massagedFile)
    );
  }
}

async function getUpdatedLockFiles(config) {
  const { logger } = config;
  logger.trace({ config }, 'getUpdatedLockFiles');
  logger.debug('Getting updated lock files');
  let lockFileError = false;
  const updatedLockFiles = [];
  try {
    const dirs = module.exports.determineLockFileDirs(config);
    logger.debug({ dirs }, 'lock file dirs');
    await module.exports.writeExistingFiles(config);
    await module.exports.writeUpdatedPackageFiles(config);

    for (const lockFileDir of dirs.packageLockFileDirs) {
      logger.debug(`Generating package-lock.json for ${lockFileDir}`);
      const newContent = await npm.generateLockFile(
        path.join(config.tmpDir.name, lockFileDir),
        logger
      );
      if (newContent) {
        const lockFileName = path.join(lockFileDir, 'package-lock.json');
        const existingContent = await config.api.getFileContent(
          lockFileName,
          config.parentBranch
        );
        if (newContent !== existingContent) {
          logger.debug('package-lock.json needs updating');
          updatedLockFiles.push({
            name: lockFileName,
            contents: newContent,
          });
        } else {
          logger.debug("package-lock.json hasn't changed");
        }
      } else {
        lockFileError = true;
      }
    }

    for (const lockFileDir of dirs.yarnLockFileDirs) {
      logger.debug(`Generating yarn.lock for ${lockFileDir}`);
      const newContent = await yarn.generateLockFile(
        path.join(config.tmpDir.name, lockFileDir),
        logger
      );
      if (newContent) {
        const lockFileName = path.join(lockFileDir, 'yarn.lock');
        const existingContent = await config.api.getFileContent(
          lockFileName,
          config.parentBranch
        );
        if (newContent !== existingContent) {
          logger.debug('yarn.lock needs updating');
          updatedLockFiles.push({
            name: lockFileName,
            contents: newContent,
          });
        } else {
          logger.debug("yarn.lock hasn't changed");
        }
      } else {
        lockFileError = true;
      }
    }
  } catch (err) {
    logger.error({ err }, 'getUpdatedLockFiles error');
    lockFileError = true;
  }
  return { lockFileError, updatedLockFiles };
}