import { GetPkgReleasesConfig, getPkgReleases } from '../../../../datasource';
import { logger } from '../../../../logger';
import { api as semver } from '../../../../versioning/npm';

/**
 * Finds the first stable version of parentName after parentStartingVersion which either:
 * - depends on targetDepName@targetVersion or a range which it satisfies, OR
 * - removes the dependency targetDepName altogether, OR
 * - depends on any version of targetDepName higher than targetVersion
 */
export async function findFirstParentVersion(
  parentName: string,
  parentStartingVersion: string,
  targetDepName: string,
  targetVersion: string
): Promise<string | null> {
  logger.debug(
    `Finding first version of ${parentName} starting with ${parentStartingVersion} which supports >= ${targetDepName}@${targetVersion}`
  );
  try {
    let lookupConfig: GetPkgReleasesConfig = {
      datasource: 'npm',
      depName: targetDepName,
    };
    const targetDep = await getPkgReleases(lookupConfig);
    const targetVersions = targetDep.releases
      .map((release) => release.version)
      .filter(
        (version) =>
          semver.isStable(version) &&
          (version === targetVersion ||
            semver.isGreaterThan(version, targetVersion))
      );
    lookupConfig = {
      datasource: 'npm',
      depName: parentName,
    };
    const parentDep = await getPkgReleases(lookupConfig);
    const parentVersions = parentDep.releases
      .map((release) => release.version)
      .filter(
        (version) =>
          semver.isStable(version) &&
          (version === parentStartingVersion ||
            semver.isGreaterThan(version, parentStartingVersion))
      )
      .sort((v1, v2) => semver.sortVersions(v1, v2));
    // iterate through parentVersions in sorted order
    for (const parentVersion of parentVersions) {
      const constraint = parentDep.releases.find(
        (release) => release.version === parentVersion
      ).dependencies?.[targetDepName];
      if (!constraint) {
        logger.debug(
          `${targetDepName} has been removed from ${parentName}@${parentVersion}`
        );
        return parentVersion;
      }
      if (semver.matches(targetVersion, constraint)) {
        // could be version or range
        logger.debug(
          `${targetDepName} needs ${parentName}@${parentVersion} which uses constraint "${constraint}" in order to update to ${targetVersion}`
        );
        return parentVersion;
      }
      if (semver.isVersion(constraint)) {
        if (semver.isGreaterThan(constraint, targetVersion)) {
          // it's not the version we were after - the parent skipped to a higher version
          logger.debug(
            `${targetDepName} needs ${parentName}@${parentVersion} which uses version "${constraint}" in order to update to greater than ${targetVersion}`
          );
          return parentVersion;
        }
      } else if (
        // check the range against all versions
        targetVersions.some((version) => semver.matches(version, constraint))
      ) {
        // the constraint didn't match the version we wanted, but it matches one of the versions higher
        logger.debug(
          `${targetDepName} needs ${parentName}@${parentVersion} which uses constraint "${constraint}" in order to update to greater than ${targetVersion}`
        );
        return parentVersion;
      }
    }
  } catch (err) /* istanbul ignore next */ {
    logger.warn({ err }, 'findFirstSupportingVersion error');
    return null;
  }
  logger.debug(`Could not find a matching version`);
  return null;
}