"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureCommentRemoval = exports.ensureComment = exports.setBranchStatus = exports.getBranchStatusCheck = exports.getBranchStatus = exports.deleteLabel = exports.ensureIssueClosing = exports.getIssueList = exports.ensureIssue = exports.findIssue = exports.addAssignees = exports.addReviewers = exports.mergePr = exports.updatePr = exports.createPr = exports.getRawFile = exports.getJsonFile = exports.maxBodyLength = exports.massageMarkdown = exports.getRepos = exports.getPr = exports.getBranchPr = exports.findPr = exports.getPrList = exports.initRepo = exports.initPlatform = exports.config = exports.id = void 0;
const tslib_1 = require("tslib");
const node_buffer_1 = require("node:buffer");
const client_codecommit_1 = require("@aws-sdk/client-codecommit");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const common_1 = require("../../../util/common");
const git = tslib_1.__importStar(require("../../../util/git"));
const regex_1 = require("../../../util/regex");
const sanitize_1 = require("../../../util/sanitize");
const util_1 = require("../util");
const pr_body_1 = require("../utils/pr-body");
const client = tslib_1.__importStar(require("./codecommit-client"));
exports.id = 'codecommit';
exports.config = {};
async function initPlatform({ endpoint, username, password, token: awsToken, }) {
    const accessKeyId = username;
    const secretAccessKey = password;
    let region;
    if (accessKeyId) {
        process.env.AWS_ACCESS_KEY_ID = accessKeyId;
    }
    if (secretAccessKey) {
        process.env.AWS_SECRET_ACCESS_KEY = secretAccessKey;
    }
    if (awsToken) {
        process.env.AWS_SESSION_TOKEN = awsToken;
    }
    if (endpoint) {
        const regionReg = (0, regex_1.regEx)(/.*codecommit\.(?<region>.+)\.amazonaws\.com/);
        const codeCommitMatch = regionReg.exec(endpoint);
        region = codeCommitMatch?.groups?.region;
        if (region) {
            process.env.AWS_REGION = region;
        }
        else {
            logger_1.logger.warn("Can't parse region, make sure your endpoint is correct");
        }
    }
    // If any of the below fails, it will throw an exception stopping the program.
    client.buildCodeCommitClient();
    // To check if we have permission to codecommit, throws exception if failed.
    await client.listRepositories();
    const platformConfig = {
        endpoint: endpoint ??
            `https://git-codecommit.${process.env.AWS_REGION ?? 'us-east-1'}.amazonaws.com/`,
    };
    return Promise.resolve(platformConfig);
}
exports.initPlatform = initPlatform;
async function initRepo({ repository, endpoint, }) {
    logger_1.logger.debug(`initRepo("${repository}")`);
    exports.config.repository = repository;
    let repo;
    try {
        repo = await client.getRepositoryInfo(repository);
    }
    catch (err) {
        logger_1.logger.error({ err }, 'Could not find repository');
        throw new Error(error_messages_1.REPOSITORY_NOT_FOUND);
    }
    if (!repo?.repositoryMetadata) {
        logger_1.logger.error({ repository }, 'Could not find repository');
        throw new Error(error_messages_1.REPOSITORY_NOT_FOUND);
    }
    logger_1.logger.debug({ repositoryDetails: repo }, 'Repository details');
    const metadata = repo.repositoryMetadata;
    const url = client.getCodeCommitUrl(metadata, repository);
    try {
        await git.initRepo({
            url,
        });
    }
    catch (err) {
        logger_1.logger.debug({ err }, 'Failed to git init');
        throw new Error(error_messages_1.PLATFORM_BAD_CREDENTIALS);
    }
    if (!metadata.defaultBranch || !metadata.repositoryId) {
        logger_1.logger.debug('Repo is empty');
        throw new Error(error_messages_1.REPOSITORY_EMPTY);
    }
    const defaultBranch = metadata.defaultBranch;
    exports.config.defaultBranch = defaultBranch;
    logger_1.logger.debug(`${repository} default branch = ${defaultBranch}`);
    return {
        repoFingerprint: (0, util_1.repoFingerprint)(metadata.repositoryId, endpoint),
        defaultBranch,
        isFork: false,
    };
}
exports.initRepo = initRepo;
async function getPrList() {
    logger_1.logger.debug('getPrList()');
    if (exports.config.prList) {
        return exports.config.prList;
    }
    const listPrsResponse = await client.listPullRequests(exports.config.repository);
    const fetchedPrs = [];
    if (listPrsResponse && !listPrsResponse.pullRequestIds) {
        return fetchedPrs;
    }
    const prIds = (0, array_1.coerceArray)(listPrsResponse.pullRequestIds);
    for (const prId of prIds) {
        const prRes = await client.getPr(prId);
        if (!prRes?.pullRequest) {
            continue;
        }
        const prInfo = prRes.pullRequest;
        const pr = {
            targetBranch: prInfo.pullRequestTargets[0].destinationReference,
            sourceBranch: prInfo.pullRequestTargets[0].sourceReference,
            destinationCommit: prInfo.pullRequestTargets[0].destinationCommit,
            sourceCommit: prInfo.pullRequestTargets[0].sourceCommit,
            state: prInfo.pullRequestStatus === client_codecommit_1.PullRequestStatusEnum.OPEN
                ? 'open'
                : 'closed',
            number: Number.parseInt(prId),
            title: prInfo.title,
            body: prInfo.description,
        };
        fetchedPrs.push(pr);
    }
    exports.config.prList = fetchedPrs;
    logger_1.logger.debug(`Retrieved Pull Requests, count: ${fetchedPrs.length}`);
    return fetchedPrs;
}
exports.getPrList = getPrList;
async function findPr({ branchName, prTitle, state = 'all', }) {
    let prsFiltered = [];
    try {
        const prs = await getPrList();
        const refsHeadBranchName = (0, util_1.getNewBranchName)(branchName);
        prsFiltered = prs.filter((item) => item.sourceBranch === refsHeadBranchName);
        if (prTitle) {
            prsFiltered = prsFiltered.filter((item) => item.title.toUpperCase() === prTitle.toUpperCase());
        }
        switch (state) {
            case 'all':
                break;
            case '!open':
                prsFiltered = prsFiltered.filter((item) => item.state !== 'open');
                break;
            default:
                prsFiltered = prsFiltered.filter((item) => item.state === 'open');
                break;
        }
    }
    catch (err) {
        logger_1.logger.error({ err }, 'findPr error');
    }
    if (prsFiltered.length === 0) {
        return null;
    }
    return prsFiltered[0];
}
exports.findPr = findPr;
async function getBranchPr(branchName) {
    logger_1.logger.debug(`getBranchPr(${branchName})`);
    const existingPr = await findPr({
        branchName,
        state: 'open',
    });
    return existingPr ? getPr(existingPr.number) : null;
}
exports.getBranchPr = getBranchPr;
async function getPr(pullRequestId) {
    logger_1.logger.debug(`getPr(${pullRequestId})`);
    const prRes = await client.getPr(`${pullRequestId}`);
    if (!prRes?.pullRequest) {
        return null;
    }
    const prInfo = prRes.pullRequest;
    let prState;
    if (prInfo.pullRequestTargets[0].mergeMetadata?.isMerged) {
        prState = 'merged';
    }
    else {
        prState =
            prInfo.pullRequestStatus === client_codecommit_1.PullRequestStatusEnum.OPEN
                ? 'open'
                : 'closed';
    }
    return {
        sourceBranch: prInfo.pullRequestTargets[0].sourceReference,
        sourceCommit: prInfo.pullRequestTargets[0].sourceCommit,
        state: prState,
        number: pullRequestId,
        title: prInfo.title,
        targetBranch: prInfo.pullRequestTargets[0].destinationReference,
        destinationCommit: prInfo.pullRequestTargets[0].destinationCommit,
        body: prInfo.description,
    };
}
exports.getPr = getPr;
async function getRepos() {
    logger_1.logger.debug('Autodiscovering AWS CodeCommit repositories');
    let reposRes;
    try {
        reposRes = await client.listRepositories();
        //todo do we need pagination? maximum number of repos is 1000 without pagination, also the same for free account
    }
    catch (error) {
        logger_1.logger.error({ error }, 'Could not retrieve repositories');
        return [];
    }
    const res = [];
    const repoNames = (0, array_1.coerceArray)(reposRes?.repositories);
    for (const repo of repoNames) {
        if (repo.repositoryName) {
            res.push(repo.repositoryName);
        }
    }
    return res;
}
exports.getRepos = getRepos;
function massageMarkdown(input) {
    // Remove any HTML we use
    return input
        .replace('you tick the rebase/retry checkbox', 'rename PR to start with "rebase!"')
        .replace('checking the rebase/retry box above', 'renaming the PR to start with "rebase!"')
        .replace((0, regex_1.regEx)(/<\/?summary>/g), '**')
        .replace((0, regex_1.regEx)(/<\/?details>/g), '')
        .replace((0, regex_1.regEx)(`\n---\n\n.*?<!-- rebase-check -->.*?\n`), '')
        .replace((0, regex_1.regEx)(/\]\(\.\.\/pull\//g), '](../../pull-requests/')
        .replace((0, regex_1.regEx)(/(?<hiddenComment><!--renovate-(?:debug|config-hash):.*?-->)/g), '[//]: # ($<hiddenComment>)');
}
exports.massageMarkdown = massageMarkdown;
/**
 * Unsed, no Dashboard
 */
function maxBodyLength() {
    return Infinity;
}
exports.maxBodyLength = maxBodyLength;
async function getJsonFile(fileName, repoName, branchOrTag) {
    const raw = await getRawFile(fileName, repoName, branchOrTag);
    return (0, common_1.parseJson)(raw, fileName);
}
exports.getJsonFile = getJsonFile;
async function getRawFile(fileName, repoName, branchOrTag) {
    const fileRes = await client.getFile(repoName ?? exports.config.repository, fileName, branchOrTag);
    if (!fileRes?.fileContent) {
        return null;
    }
    const buf = node_buffer_1.Buffer.from(fileRes.fileContent);
    return buf.toString();
}
exports.getRawFile = getRawFile;
const AMAZON_MAX_BODY_LENGTH = 10239;
async function createPr({ sourceBranch, targetBranch, prTitle: title, prBody: body, }) {
    const description = (0, pr_body_1.smartTruncate)((0, sanitize_1.sanitize)(body), AMAZON_MAX_BODY_LENGTH);
    const prCreateRes = await client.createPr(title, (0, sanitize_1.sanitize)(description), sourceBranch, targetBranch, exports.config.repository);
    if (!prCreateRes.pullRequest?.title ||
        !prCreateRes.pullRequest?.pullRequestId ||
        !prCreateRes.pullRequest?.description ||
        !prCreateRes.pullRequest?.pullRequestTargets?.length) {
        throw new Error('Could not create pr, missing PR info');
    }
    return {
        number: Number.parseInt(prCreateRes.pullRequest.pullRequestId),
        state: 'open',
        title: prCreateRes.pullRequest.title,
        sourceBranch,
        targetBranch,
        sourceCommit: prCreateRes.pullRequest.pullRequestTargets[0].sourceCommit,
        destinationCommit: prCreateRes.pullRequest.pullRequestTargets[0].destinationCommit,
        sourceRepo: exports.config.repository,
        body: prCreateRes.pullRequest.description,
    };
}
exports.createPr = createPr;
async function updatePr({ number: prNo, prTitle: title, prBody: body, state, }) {
    logger_1.logger.debug(`updatePr(${prNo}, ${title}, body)`);
    let cachedPr = undefined;
    const cachedPrs = exports.config.prList ?? [];
    for (const p of cachedPrs) {
        if (p.number === prNo) {
            cachedPr = p;
        }
    }
    if (body && cachedPr?.body !== body) {
        await client.updatePrDescription(`${prNo}`, (0, pr_body_1.smartTruncate)((0, sanitize_1.sanitize)(body), AMAZON_MAX_BODY_LENGTH));
    }
    if (title && cachedPr?.title !== title) {
        await client.updatePrTitle(`${prNo}`, title);
    }
    const prStatusInput = state === 'closed'
        ? client_codecommit_1.PullRequestStatusEnum.CLOSED
        : client_codecommit_1.PullRequestStatusEnum.OPEN;
    if (cachedPr?.state !== prStatusInput) {
        try {
            await client.updatePrStatus(`${prNo}`, prStatusInput);
        }
        catch (err) {
            // safety check
            // do nothing, it's ok to fail sometimes when trying to update from open to open or from closed to closed.
        }
    }
}
exports.updatePr = updatePr;
// Auto-Merge not supported currently.
/* istanbul ignore next */
async function mergePr({ branchName, id: prNo, strategy, }) {
    logger_1.logger.debug(`mergePr(${prNo}, ${branchName})`);
    await client.getPr(`${prNo}`);
    return Promise.resolve(false);
    //
    // // istanbul ignore if
    // if (!prOut) {
    //   return false;
    // }
    // const pReq = prOut.pullRequest;
    // const targets = pReq?.pullRequestTargets;
    //
    // // istanbul ignore if
    // if (!targets) {
    //   return false;
    // }
    //
    // if (strategy === 'rebase') {
    //   logger.warn('CodeCommit does not support a "rebase" strategy.');
    //   return false;
    // }
    //
    // try {
    //   if (strategy === 'auto' || strategy === 'squash') {
    //     await client.squashMerge(
    //       targets[0].repositoryName!,
    //       targets[0].sourceReference!,
    //       targets[0].destinationReference!,
    //       pReq?.title
    //     );
    //   } else if (strategy === 'fast-forward') {
    //     await client.fastForwardMerge(
    //       targets[0].repositoryName!,
    //       targets[0].sourceReference!,
    //       targets[0].destinationReference!
    //     );
    //   } else {
    //     logger.debug(`unsupported strategy`);
    //     return false;
    //   }
    // } catch (err) {
    //   logger.debug({ err }, `PR merge error`);
    //   logger.info({ pr: prNo }, 'PR automerge failed');
    //   return false;
    // }
    //
    // logger.trace(`Updating PR ${prNo} to status ${PullRequestStatusEnum.CLOSED}`);
    //
    // try {
    //   const response = await client.updatePrStatus(
    //     `${prNo}`,
    //     PullRequestStatusEnum.CLOSED
    //   );
    //   const isClosed =
    //     response.pullRequest?.pullRequestStatus === PullRequestStatusEnum.CLOSED;
    //
    //   if (!isClosed) {
    //     logger.warn(
    //       {
    //         pullRequestId: prNo,
    //         status: response.pullRequest?.pullRequestStatus,
    //       },
    //       `Expected PR to have status`
    //     );
    //   }
    //   return true;
    // } catch (err) {
    //   logger.debug({ err }, 'Failed to set the PR as Closed.');
    //   return false;
    // }
}
exports.mergePr = mergePr;
async function addReviewers(prNo, reviewers) {
    const numberOfApprovers = reviewers.length;
    const approvalRuleContents = `{"Version":"2018-11-08","Statements": [{"Type": "Approvers","NumberOfApprovalsNeeded":${numberOfApprovers},"ApprovalPoolMembers": ${JSON.stringify(reviewers)}}]}`;
    const res = await client.createPrApprovalRule(`${prNo}`, approvalRuleContents);
    if (res) {
        const approvalRule = res.approvalRule;
        logger_1.logger.debug({ approvalRule }, `Approval Rule Added to PR #${prNo}:`);
    }
}
exports.addReviewers = addReviewers;
/* istanbul ignore next */
function addAssignees(iid, assignees) {
    // CodeCommit does not support adding reviewers
    return Promise.resolve();
}
exports.addAssignees = addAssignees;
/* istanbul ignore next */
function findIssue(title) {
    // CodeCommit does not have issues
    return Promise.resolve(null);
}
exports.findIssue = findIssue;
/* istanbul ignore next */
function ensureIssue({ title, }) {
    // CodeCommit does not have issues
    return Promise.resolve(null);
}
exports.ensureIssue = ensureIssue;
/* istanbul ignore next */
function getIssueList() {
    // CodeCommit does not have issues
    return Promise.resolve([]);
}
exports.getIssueList = getIssueList;
/* istanbul ignore next */
function ensureIssueClosing(title) {
    // CodeCommit does not have issues
    return Promise.resolve();
}
exports.ensureIssueClosing = ensureIssueClosing;
/* istanbul ignore next */
function deleteLabel(prNumber, label) {
    return Promise.resolve();
}
exports.deleteLabel = deleteLabel;
// Returns the combined status for a branch.
/* istanbul ignore next */
function getBranchStatus(branchName) {
    logger_1.logger.debug(`getBranchStatus(${branchName})`);
    logger_1.logger.debug('returning branch status yellow, because getBranchStatus isnt supported on aws yet');
    return Promise.resolve('yellow');
}
exports.getBranchStatus = getBranchStatus;
/* istanbul ignore next */
function getBranchStatusCheck(branchName, context) {
    logger_1.logger.debug(`getBranchStatusCheck(${branchName}, context=${context})`);
    logger_1.logger.debug('returning null, because getBranchStatusCheck is not supported on aws yet');
    return Promise.resolve(null);
}
exports.getBranchStatusCheck = getBranchStatusCheck;
/* istanbul ignore next */
function setBranchStatus({ branchName, context, description, state, url: targetUrl, }) {
    return Promise.resolve();
}
exports.setBranchStatus = setBranchStatus;
async function ensureComment({ number, topic, content, }) {
    logger_1.logger.debug(`ensureComment(${number}, ${topic}, content)`);
    const header = topic ? `### ${topic}\n\n` : '';
    const body = `${header}${(0, sanitize_1.sanitize)(content)}`;
    let prCommentsResponse;
    try {
        prCommentsResponse = await client.getPrComments(`${number}`);
    }
    catch (err) {
        logger_1.logger.debug({ err }, 'Unable to retrieve pr comments');
        return false;
    }
    let commentId = undefined;
    let commentNeedsUpdating = false;
    if (!prCommentsResponse?.commentsForPullRequestData) {
        return false;
    }
    for (const commentObj of prCommentsResponse.commentsForPullRequestData) {
        if (!commentObj?.comments) {
            continue;
        }
        const firstCommentContent = commentObj.comments[0].content;
        if ((topic && firstCommentContent?.startsWith(header)) === true ||
            (!topic && firstCommentContent === body)) {
            commentId = commentObj.comments[0].commentId;
            commentNeedsUpdating = firstCommentContent !== body;
            break;
        }
    }
    if (!commentId) {
        const prs = await getPrList();
        const thisPr = prs.filter((item) => item.number === number);
        if (!thisPr[0].sourceCommit || !thisPr[0].destinationCommit) {
            return false;
        }
        await client.createPrComment(`${number}`, exports.config.repository, body, thisPr[0].destinationCommit, thisPr[0].sourceCommit);
        logger_1.logger.info({ repository: exports.config.repository, prNo: number, topic }, 'Comment added');
    }
    else if (commentNeedsUpdating && commentId) {
        await client.updateComment(commentId, body);
        logger_1.logger.debug({ repository: exports.config.repository, prNo: number, topic }, 'Comment updated');
    }
    else {
        logger_1.logger.debug({ repository: exports.config.repository, prNo: number, topic }, 'Comment is already update-to-date');
    }
    return true;
}
exports.ensureComment = ensureComment;
async function ensureCommentRemoval(removeConfig) {
    const { number: prNo } = removeConfig;
    const key = removeConfig.type === 'by-topic'
        ? removeConfig.topic
        : removeConfig.content;
    logger_1.logger.debug(`Ensuring comment "${key}" in #${prNo} is removed`);
    let prCommentsResponse;
    try {
        prCommentsResponse = await client.getPrComments(`${prNo}`);
    }
    catch (err) {
        logger_1.logger.debug({ err }, 'Unable to retrieve pr comments');
        return;
    }
    if (!prCommentsResponse?.commentsForPullRequestData) {
        logger_1.logger.debug('commentsForPullRequestData not found');
        return;
    }
    let commentIdToRemove;
    for (const commentObj of prCommentsResponse.commentsForPullRequestData) {
        if (!commentObj?.comments) {
            logger_1.logger.debug('comments object not found under commentsForPullRequestData');
            continue;
        }
        for (const comment of commentObj.comments) {
            if ((removeConfig.type === 'by-topic' &&
                comment.content?.startsWith(`### ${removeConfig.topic}\n\n`)) ===
                true ||
                (removeConfig.type === 'by-content' &&
                    removeConfig.content === comment.content?.trim())) {
                commentIdToRemove = comment.commentId;
                break;
            }
        }
        if (commentIdToRemove) {
            await client.deleteComment(commentIdToRemove);
            logger_1.logger.debug(`comment "${key}" in PR #${prNo} was removed`);
            break;
        }
    }
}
exports.ensureCommentRemoval = ensureCommentRemoval;
//# sourceMappingURL=index.js.map