var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import Execute from "../../Execute";
import logger from "../../Logger";
import HostedCodeRepository from "../HostedCodeRepository/HostedCodeRepository";
import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
import URL from "../../../../Types/API/URL";
import PullRequestState from "../../../../Types/CodeRepository/PullRequestState";
import OneUptimeDate from "../../../../Types/Date";
import API from "../../../../Utils/API";
import CaptureSpan from "../../Telemetry/CaptureSpan";
import { GitHubAppId, GitHubAppPrivateKey, GitHubAppWebhookSecret, } from "../../../EnvironmentConfig";
import BadDataException from "../../../../Types/Exception/BadDataException";
import * as crypto from "crypto";
/**
 * Error thrown when a GitHub App installation is no longer valid (e.g., uninstalled from GitHub)
 */
export class GitHubInstallationNotFoundError extends BadDataException {
    constructor() {
        super("GitHub App installation not found. The app may have been uninstalled from GitHub. Please reconnect with GitHub to reinstall the app.");
    }
}
export default class GitHubUtil extends HostedCodeRepository {
    getPullRequestFromJSONObject(data) {
        let pullRequestState = data.pullRequest["state"] === "open"
            ? PullRequestState.Open
            : PullRequestState.Closed;
        if (data.pullRequest["merged_at"]) {
            pullRequestState = PullRequestState.Merged;
        }
        return {
            pullRequestId: data.pullRequest["id"],
            pullRequestNumber: data.pullRequest["number"],
            title: data.pullRequest["title"],
            body: data.pullRequest["body"],
            url: URL.fromString(data.pullRequest["url"]),
            state: pullRequestState,
            createdAt: OneUptimeDate.fromString(data.pullRequest["created_at"]),
            updatedAt: OneUptimeDate.fromString(data.pullRequest["updated_at"]),
            repoOrganizationName: data.organizationName,
            repoName: data.repositoryName,
            headRefName: data.pullRequest["head"] &&
                data.pullRequest["head"]["ref"]
                ? data.pullRequest["head"]["ref"]
                : "",
        };
    }
    async getPullRequestByNumber(data) {
        const gitHubToken = this.authToken;
        const url = URL.fromString(`https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls/${data.pullRequestId}`);
        const result = await API.get({
            url: url,
            data: {},
            headers: {
                Authorization: `Bearer ${gitHubToken}`,
                Accept: "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
        });
        if (result instanceof HTTPErrorResponse) {
            throw result;
        }
        return this.getPullRequestFromJSONObject({
            pullRequest: result.data,
            organizationName: data.organizationName,
            repositoryName: data.repositoryName,
        });
    }
    async getPullRequestsByPage(data) {
        const gitHubToken = this.authToken;
        const url = URL.fromString(`https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls?base=${data.baseBranchName}&state=${data.pullRequestState}&per_page=100&page=${data.page}`);
        const result = await API.get({
            url: url,
            data: {},
            headers: {
                Authorization: `Bearer ${gitHubToken}`,
                Accept: "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
        });
        if (result instanceof HTTPErrorResponse) {
            throw result;
        }
        const pullRequests = result.data.map((pullRequest) => {
            return this.getPullRequestFromJSONObject({
                pullRequest: pullRequest,
                organizationName: data.organizationName,
                repositoryName: data.repositoryName,
            });
        });
        return pullRequests;
    }
    async getPullRequests(data) {
        const allPullRequests = [];
        let page = 1;
        let pullRequests = await this.getPullRequestsByPage({
            organizationName: data.organizationName,
            repositoryName: data.repositoryName,
            pullRequestState: data.pullRequestState,
            baseBranchName: data.baseBranchName,
            page: page,
        });
        /*
         * Fetch all pull requests by paginating through the results
         * 100 pull requests per page is the limit of the GitHub API
         */
        while (pullRequests.length === page * 100 || page === 1) {
            pullRequests = await this.getPullRequestsByPage({
                organizationName: data.organizationName,
                repositoryName: data.repositoryName,
                pullRequestState: data.pullRequestState,
                baseBranchName: data.baseBranchName,
                page: page,
            });
            page++;
            allPullRequests.push(...pullRequests);
        }
        return allPullRequests;
    }
    async addRemote(data) {
        const url = URL.fromString(`https://github.com/${data.organizationName}/${data.repositoryName}.git`);
        logger.debug(`Adding remote '${data.remoteName}' for ${data.organizationName}/${data.repositoryName}`);
        const result = await Execute.executeCommandFile({
            command: "git",
            args: ["remote", "add", data.remoteName, url.toString()],
            cwd: process.cwd(),
        });
        logger.debug(result);
    }
    async pushChanges(data) {
        const branchName = data.branchName;
        const username = this.username;
        const password = this.authToken;
        logger.debug("Pushing changes to remote repository with username: " + username);
        const encodedUsername = encodeURIComponent(username);
        const encodedPassword = encodeURIComponent(password);
        const remoteUrl = `https://${encodedUsername}:${encodedPassword}@github.com/${data.organizationName}/${data.repositoryName}.git`;
        logger.debug(`Pushing branch '${branchName}' to ${data.organizationName}/${data.repositoryName}`);
        const result = await Execute.executeCommandFile({
            command: "git",
            args: ["push", "-u", remoteUrl, branchName],
            cwd: data.repoPath,
        });
        logger.debug(result);
    }
    async createPullRequest(data) {
        const gitHubToken = this.authToken;
        const url = URL.fromString(`https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls`);
        const result = await API.post({
            url: url,
            data: {
                base: data.baseBranchName,
                head: data.headBranchName,
                title: data.title,
                body: data.body,
            },
            headers: {
                Authorization: `Bearer ${gitHubToken}`,
                Accept: "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
        });
        if (result instanceof HTTPErrorResponse) {
            throw result;
        }
        return this.getPullRequestFromJSONObject({
            pullRequest: result.data,
            organizationName: data.organizationName,
            repositoryName: data.repositoryName,
        });
    }
    // GitHub App Authentication Methods
    /**
     * Generates a JWT for GitHub App authentication
     * @returns JWT string valid for 10 minutes
     */
    static generateAppJWT() {
        if (!GitHubAppId) {
            throw new BadDataException("GITHUB_APP_ID environment variable is not set");
        }
        if (!GitHubAppPrivateKey) {
            throw new BadDataException("GITHUB_APP_PRIVATE_KEY environment variable is not set");
        }
        const now = Math.floor(Date.now() / 1000);
        const payload = {
            iat: now - 60, // Issued at time (60 seconds in the past to allow for clock drift)
            exp: now + 600, // Expiration time (10 minutes from now)
            iss: GitHubAppId,
        };
        // Create JWT header
        const header = {
            alg: "RS256",
            typ: "JWT",
        };
        const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
        const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
        const signatureInput = `${encodedHeader}.${encodedPayload}`;
        // Sign with private key
        const sign = crypto.createSign("RSA-SHA256");
        sign.update(signatureInput);
        const signature = sign.sign(GitHubAppPrivateKey, "base64url");
        return `${signatureInput}.${signature}`;
    }
    /**
     * Gets an installation access token for a GitHub App installation
     * @param installationId - The GitHub App installation ID
     * @param options - Optional configuration for the token
     * @param options.permissions - Specific permissions to request for the token
     * @returns Installation token and expiration date
     */
    static async getInstallationAccessToken(installationId, options) {
        var _a, _b;
        const jwt = GitHubUtil.generateAppJWT();
        const url = URL.fromString(`https://api.github.com/app/installations/${installationId}/access_tokens`);
        // Build request data with optional permissions
        const requestData = {};
        if (options === null || options === void 0 ? void 0 : options.permissions) {
            requestData["permissions"] = options.permissions;
        }
        const result = await API.post({
            url: url,
            data: requestData,
            headers: {
                Authorization: `Bearer ${jwt}`,
                Accept: "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
        });
        if (result instanceof HTTPErrorResponse) {
            // Check if this is a permission error and provide helpful message
            const errorMessage = ((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a["message"]) === null || _b === void 0 ? void 0 : _b.toString()) || "";
            // Check if the installation is not found (404) - this means the app was uninstalled from GitHub
            if (result.statusCode === 404) {
                logger.error(`GitHub App installation not found (ID: ${installationId}). ` +
                    `The app may have been uninstalled from GitHub. User needs to reinstall the app.`);
                throw new GitHubInstallationNotFoundError();
            }
            if (errorMessage.includes("permissions") ||
                result.statusCode === 403 ||
                result.statusCode === 422) {
                logger.error(`GitHub App permission error: ${errorMessage}. ` +
                    `Please ensure the GitHub App is configured with the required permissions ` +
                    `(contents: write, pull_requests: write, metadata: read) in the GitHub App settings.`);
            }
            throw result;
        }
        return {
            token: result.data["token"],
            expiresAt: OneUptimeDate.fromString(result.data["expires_at"]),
        };
    }
    /**
     * Lists repositories accessible to a GitHub App installation
     * @param installationId - The GitHub App installation ID
     * @returns Array of repositories
     */
    static async listRepositoriesForInstallation(installationId) {
        const tokenData = await GitHubUtil.getInstallationAccessToken(installationId);
        const allRepositories = [];
        let page = 1;
        let hasMore = true;
        while (hasMore) {
            const url = URL.fromString(`https://api.github.com/installation/repositories?per_page=100&page=${page}`);
            const result = await API.get({
                url: url,
                data: {},
                headers: {
                    Authorization: `Bearer ${tokenData.token}`,
                    Accept: "application/vnd.github+json",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
            });
            if (result instanceof HTTPErrorResponse) {
                throw result;
            }
            const repositories = result.data["repositories"] || [];
            for (const repo of repositories) {
                const repoData = repo;
                const owner = repoData["owner"];
                allRepositories.push({
                    id: repoData["id"],
                    name: repoData["name"],
                    fullName: repoData["full_name"],
                    private: repoData["private"],
                    htmlUrl: repoData["html_url"],
                    description: repoData["description"] || null,
                    defaultBranch: repoData["default_branch"],
                    ownerLogin: owner["login"],
                });
            }
            // Check if there are more pages
            if (repositories.length < 100) {
                hasMore = false;
            }
            else {
                page++;
            }
        }
        return allRepositories;
    }
    /**
     * Verifies a GitHub webhook signature
     * @param payload - The raw request body
     * @param signature - The X-Hub-Signature-256 header value
     * @returns true if signature is valid
     */
    static verifyWebhookSignature(payload, signature) {
        if (!GitHubAppWebhookSecret) {
            logger.warn("GITHUB_APP_WEBHOOK_SECRET is not set, skipping verification");
            return true;
        }
        const expectedSignature = `sha256=${crypto
            .createHmac("sha256", GitHubAppWebhookSecret)
            .update(payload)
            .digest("hex")}`;
        try {
            return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
        }
        catch (_a) {
            return false;
        }
    }
    /**
     * Gets the GitHub App installation URL for a project to install the app
     * @returns The installation URL
     */
    static getAppInstallationUrl() {
        if (!GitHubAppId) {
            throw new BadDataException("GITHUB_APP_ID environment variable is not set");
        }
        /*
         * This is the standard GitHub App installation URL format
         * The app slug would typically come from another env var, but we can use the client ID approach
         */
        return `https://github.com/apps`;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil.prototype, "getPullRequestByNumber", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil.prototype, "getPullRequests", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil.prototype, "addRemote", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil.prototype, "pushChanges", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil.prototype, "createPullRequest", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", String)
], GitHubUtil, "generateAppJWT", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], GitHubUtil, "getInstallationAccessToken", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], GitHubUtil, "listRepositoriesForInstallation", null);
//# sourceMappingURL=GitHub.js.map