const convertHrTime = require('convert-hrtime');
const tmp = require('tmp-promise');
const manager = require('../../manager');
// Workers
const branchWorker = require('../branch');
// children
const apis = require('./apis');
const onboarding = require('./onboarding');
const upgrades = require('./upgrades');
const cleanup = require('./cleanup');

const { checkMonorepos } = require('../../manager/npm/monorepos');
const { ensureOnboardingPr } = require('./onboarding/pr');

module.exports = {
  pinDependenciesFirst,
  renovateRepository,
};

function pinDependenciesFirst(a, b) {
  if (a.type === 'pin') {
    return false;
  }
  if (b.type === 'pin') {
    return true;
  }
  return a.branchName > b.branchName;
}

async function renovateRepository(repoConfig, token) {
  let config = { ...repoConfig };
  const { logger } = config;
  logger.trace({ config }, 'renovateRepository');
  config.tmpDir = await tmp.dir({ unsafeCleanup: true });
  config.errors = [];
  config.warnings = [];
  async function renovateRepositoryInner(count = 1) {
    // istanbul ignore if
    if (count > 5) {
      // This is an arbitrary number added in to cut short any unintended infinite recursion
      throw new Error('Existing renovateRepositoryInner after 5 loops');
    }
    logger.info(`renovateRepository loop ${count}`);
    let branchList = [];
    config = await apis.initApis(config, token);
    config = await apis.mergeRenovateJson(config);
    if (config.enabled === false) {
      logger.debug('repository is disabled');
      await cleanup.pruneStaleBranches(config, []);
      return null;
    }
    if (config.isFork) {
      if (config.renovateFork) {
        logger.info('Processing forked repository');
      } else {
        logger.debug('repository is a fork and not manually configured');
        await cleanup.pruneStaleBranches(config, []);
        return null;
      }
    }
    if (config.baseBranch) {
      // Renovate should read content and target PRs here
      if (await config.api.branchExists(config.baseBranch)) {
        config.api.setBaseBranch(config.baseBranch);
      } else {
        // Warn and ignore setting (use default branch)
        const message = `The configured baseBranch "${config.baseBranch}" is not present. Ignoring`;
        config.errors.push({
          depName: 'baseBranch',
          message,
        });
        logger.warn(message);
      }
    }
    config = await onboarding.getOnboardingStatus(config);
    // Detect package files in default branch if not manually provisioned
    if (config.packageFiles.length === 0) {
      logger.debug('Detecting package files');
      config.packageFiles = await manager.detectPackageFiles(config);
      // If we can't detect any package.json then return
      if (config.packageFiles.length === 0) {
        logger.info('Cannot detect package files');
        // istanbul ignore if
        if (config.repoIsOnboarded === false) {
          logger.warn('Need to delete onboarding PR');
          const pr = await config.api.getBranchPr(config.onboardingBranch);
          if (pr) {
            logger.info('Found onboarding PR');
            await config.api.updatePr(
              pr.number,
              'Configure Renovate - canceled',
              'This PR was created in error and is now being deleted automatically. Sorry for the inconvenience.'
            );
            await config.api.deleteBranch(config.onboardingBranch);
            throw new Error('no package files');
          }
        }
        return null;
      }
      logger.info(
        {
          packageFiles: config.packageFiles,
          count: config.packageFiles.length,
        },
        `Detected package files`
      );
    }
    logger.debug('Resolving package files and content');
    config = await apis.resolvePackageFiles(config);
    config = await checkMonorepos(config);
    logger.trace({ config }, 'post-packageFiles config');
    // TODO: why is this fix needed?!
    config.logger = logger;
    const allUpgrades = await upgrades.determineRepoUpgrades(config);
    const res = await upgrades.branchifyUpgrades(allUpgrades, logger);
    config.errors = config.errors.concat(res.errors);
    config.warnings = config.warnings.concat(res.warnings);
    let branchUpgrades = res.upgrades.sort(pinDependenciesFirst);
    logger.debug(`Updating ${branchUpgrades.length} branch(es)`);
    logger.trace({ config: branchUpgrades }, 'branchUpgrades');
    if (config.repoIsOnboarded) {
      logger.info(`Processing ${branchUpgrades.length} branch(es)`);
      const branchStartTime = process.hrtime();
      branchList = branchUpgrades.map(upgrade => upgrade.branchName);
      if (branchUpgrades.length && branchUpgrades[0].isPin) {
        branchUpgrades = branchUpgrades.filter(upg => upg.isPin);
        logger.info(`Processing ${branchUpgrades.length} "pin" PRs first`);
      }
      for (const branchUpgrade of branchUpgrades) {
        const branchResult = await branchWorker.processBranch(
          branchUpgrade,
          config.errors,
          config.warnings
        );
        if (branchResult === 'automerged') {
          // Stop procesing other branches because base branch has been changed by an automerge
          logger.info('Restarting repo renovation after automerge');
          return renovateRepositoryInner(count + 1);
        }
      }
      logger.info(
        { seconds: convertHrTime(process.hrtime(branchStartTime)).seconds },
        'Finished updating branches'
      );
    } else {
      await ensureOnboardingPr({ ...config, branches: branchUpgrades });
      logger.info('"Configure Renovate" PR needs to be closed first');
      branchList = [`${config.branchPrefix}configure`];
    }
    logger.debug(`branchList=${branchList}`);
    return branchList;
  }
  try {
    const branchList = await renovateRepositoryInner();
    if (branchList) {
      await cleanup.pruneStaleBranches(config, branchList);
    }
  } catch (err) {
    // Swallow this error so that other repositories can be processed
    if (err.message === 'uninitiated') {
      logger.info('Repository is uninitiated - skipping');
    } else if (err.message === 'no package files') {
      logger.info('Repository has no package files - skipping');
    } else {
      logger.error(`Failed to process repository: ${err.message}`);
      logger.debug({ err });
    }
  }
  config.tmpDir.cleanup();
}