"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getApiToken = exports.initMergeConfidence = exports.getMergeConfidenceLevel = exports.satisfiesConfidenceLevel = exports.isActiveConfidenceLevel = exports.isMergeConfidence = exports.resetConfig = exports.parseSupportedDatasourceString = exports.initConfig = exports.confidenceLevels = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const global_1 = require("../../config/global");
const merge_confidence_1 = require("../../config/presets/internal/merge-confidence");
const logger_1 = require("../../logger");
const external_host_error_1 = require("../../types/errors/external-host-error");
const packageCache = tslib_1.__importStar(require("../cache/package"));
const hostRules = tslib_1.__importStar(require("../host-rules"));
const http_1 = require("../http");
const regex_1 = require("../regex");
const url_1 = require("../url");
const common_1 = require("./common");
const hostType = 'merge-confidence';
const http = new http_1.Http(hostType);
let token;
let apiBaseUrl;
let supportedDatasources = [];
exports.confidenceLevels = {
    low: -1,
    neutral: 0,
    high: 1,
    'very high': 2,
};
function initConfig() {
    apiBaseUrl = getApiBaseUrl();
    token = getApiToken();
    supportedDatasources = parseSupportedDatasourceString();
    if (!is_1.default.nullOrUndefined(token)) {
        logger_1.logger.debug(`Merge confidence token found for ${apiBaseUrl}`);
    }
}
exports.initConfig = initConfig;
function parseSupportedDatasourceString() {
    const supportedDatasources = global_1.GlobalConfig.get('mergeConfidenceDatasources', merge_confidence_1.supportedDatasources);
    if (!is_1.default.array(supportedDatasources, is_1.default.string)) {
        logger_1.logger.warn({ supportedDatasources }, `Expected a string array but got ${typeof supportedDatasources} - using default value instead`);
        return merge_confidence_1.supportedDatasources;
    }
    return supportedDatasources;
}
exports.parseSupportedDatasourceString = parseSupportedDatasourceString;
function resetConfig() {
    token = undefined;
    apiBaseUrl = undefined;
    supportedDatasources = [];
}
exports.resetConfig = resetConfig;
function isMergeConfidence(value) {
    return common_1.MERGE_CONFIDENCE.includes(value);
}
exports.isMergeConfidence = isMergeConfidence;
function isActiveConfidenceLevel(confidence) {
    return isMergeConfidence(confidence) && confidence !== 'low';
}
exports.isActiveConfidenceLevel = isActiveConfidenceLevel;
function satisfiesConfidenceLevel(confidence, minimumConfidence) {
    return exports.confidenceLevels[confidence] >= exports.confidenceLevels[minimumConfidence];
}
exports.satisfiesConfidenceLevel = satisfiesConfidenceLevel;
const updateTypeConfidenceMapping = {
    pin: 'high',
    digest: 'neutral',
    pinDigest: 'high',
    bump: 'neutral',
    lockFileMaintenance: 'neutral',
    lockfileUpdate: 'neutral',
    rollback: 'neutral',
    replacement: 'neutral',
    major: null,
    minor: null,
    patch: null,
};
/**
 * Retrieves the merge confidence of a package update if the merge confidence API is enabled. Otherwise, undefined is returned.
 *
 * @param datasource
 * @param packageName
 * @param currentVersion
 * @param newVersion
 * @param updateType
 *
 * @returns The merge confidence level for the given package release.
 * @throws {ExternalHostError} If a request has been made and an error occurs during the request, such as a timeout, connection reset, authentication failure, or internal server error.
 */
async function getMergeConfidenceLevel(datasource, packageName, currentVersion, newVersion, updateType) {
    if (is_1.default.nullOrUndefined(apiBaseUrl) || is_1.default.nullOrUndefined(token)) {
        return undefined;
    }
    if (!supportedDatasources.includes(datasource)) {
        return undefined;
    }
    if (!(currentVersion && newVersion && updateType)) {
        return 'neutral';
    }
    const mappedConfidence = updateTypeConfidenceMapping[updateType];
    if (mappedConfidence) {
        return mappedConfidence;
    }
    return await queryApi(datasource, packageName, currentVersion, newVersion);
}
exports.getMergeConfidenceLevel = getMergeConfidenceLevel;
/**
 * Queries the Merge Confidence API with the given package release information.
 *
 * @param datasource
 * @param packageName
 * @param currentVersion
 * @param newVersion
 *
 * @returns The merge confidence level for the given package release.
 * @throws {ExternalHostError} if a timeout or connection reset error, authentication failure, or internal server error occurs during the request.
 *
 * @remarks
 * Results are cached for 60 minutes to reduce the number of API calls.
 */
async function queryApi(datasource, packageName, currentVersion, newVersion) {
    // istanbul ignore if: defensive, already been validated before calling this function
    if (is_1.default.nullOrUndefined(apiBaseUrl) || is_1.default.nullOrUndefined(token)) {
        return 'neutral';
    }
    const escapedPackageName = packageName.replace((0, regex_1.regEx)(/\//g), '%2f');
    const url = (0, url_1.joinUrlParts)(apiBaseUrl, 'api/mc/json', datasource, escapedPackageName, currentVersion, newVersion);
    const cacheKey = `${token}:${url}`;
    const cachedResult = await packageCache.get(hostType, cacheKey);
    // istanbul ignore if
    if (cachedResult) {
        logger_1.logger.debug({
            datasource,
            packageName,
            currentVersion,
            newVersion,
            cachedResult,
        }, 'using merge confidence cached result');
        return cachedResult;
    }
    let confidence = 'neutral';
    try {
        const res = (await http.getJson(url)).body;
        if (isMergeConfidence(res.confidence)) {
            confidence = res.confidence;
        }
    }
    catch (err) {
        apiErrorHandler(err);
    }
    await packageCache.set(hostType, cacheKey, confidence, 60);
    return confidence;
}
/**
 * Checks the health of the Merge Confidence API by attempting to authenticate with it.
 *
 * @returns Resolves when the API health check is completed successfully.
 *
 * @throws {ExternalHostError} if a timeout, connection reset error, authentication failure, or internal server error occurs during the request.
 *
 * @remarks
 * This function first checks that the API base URL and an authentication bearer token are defined before attempting to
 * authenticate with the API. If either the base URL or token is not defined, it will immediately return
 * without making a request.
 */
async function initMergeConfidence() {
    initConfig();
    if (is_1.default.nullOrUndefined(apiBaseUrl) || is_1.default.nullOrUndefined(token)) {
        logger_1.logger.trace('merge confidence API usage is disabled');
        return;
    }
    const url = (0, url_1.joinUrlParts)(apiBaseUrl, 'api/mc/availability');
    try {
        await http.get(url);
    }
    catch (err) {
        apiErrorHandler(err);
    }
    logger_1.logger.debug({ supportedDatasources }, 'merge confidence API - successfully authenticated');
    return;
}
exports.initMergeConfidence = initMergeConfidence;
function getApiBaseUrl() {
    const defaultBaseUrl = 'https://developer.mend.io/';
    const baseFromEnv = global_1.GlobalConfig.get('mergeConfidenceEndpoint', defaultBaseUrl);
    try {
        const parsedBaseUrl = new URL(baseFromEnv).toString();
        logger_1.logger.trace({ baseUrl: parsedBaseUrl }, 'using merge confidence API base found in environment variables');
        return (0, url_1.ensureTrailingSlash)(parsedBaseUrl);
    }
    catch (err) {
        logger_1.logger.warn({ err, baseFromEnv }, 'invalid merge confidence API base URL found in environment variables - using default value instead');
        return defaultBaseUrl;
    }
}
function getApiToken() {
    return hostRules.find({
        url: apiBaseUrl,
        hostType,
    })?.token;
}
exports.getApiToken = getApiToken;
/**
 * Handles errors returned by the Merge Confidence API.
 *
 * @param err - The error object returned by the API.
 * @throws {ExternalHostError} if a timeout or connection reset error, authentication failure, or internal server error occurs during the request.
 */
function apiErrorHandler(err) {
    if (err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET') {
        logger_1.logger.error({ err }, 'merge confidence API request failed - aborting run');
        throw new external_host_error_1.ExternalHostError(err, hostType);
    }
    if (err.statusCode === 403) {
        logger_1.logger.error({ err }, 'merge confidence API token rejected - aborting run');
        throw new external_host_error_1.ExternalHostError(err, hostType);
    }
    if (err.statusCode >= 500 && err.statusCode < 600) {
        logger_1.logger.error({ err }, 'merge confidence API failure: 5xx - aborting run');
        throw new external_host_error_1.ExternalHostError(err, hostType);
    }
    logger_1.logger.warn({ err }, 'error fetching merge confidence data');
}
//# sourceMappingURL=index.js.map