import Express from "../Utils/Express";
import Response from "../Utils/Response";
import BadDataException from "../../Types/Exception/BadDataException";
import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
import logger, { getLogAttributesFromRequest } from "../Utils/Logger";
import { DashboardClientUrl, GitHubAppName, HomeClientUrl, } from "../EnvironmentConfig";
import ObjectID from "../../Types/ObjectID";
import GitHubUtil, { GitHubInstallationNotFoundError, } from "../Utils/CodeRepository/GitHub/GitHub";
import CodeRepositoryService from "../Services/CodeRepositoryService";
import ProjectService from "../Services/ProjectService";
import AccessTokenService from "../Services/AccessTokenService";
import CodeRepository from "../../Models/DatabaseModels/CodeRepository";
import CodeRepositoryType from "../../Types/CodeRepository/CodeRepositoryType";
import URL from "../../Types/API/URL";
import UserMiddleware from "../Middleware/UserAuthorization";
import JSONWebToken from "../Utils/JsonWebToken";
import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
export default class GitHubAPI {
    getRouter() {
        const router = Express.getRouter();
        /*
         * GitHub App installation callback
         * This is called after a user installs the GitHub App
         * The state parameter contains base64 encoded JSON with projectId and userId
         */
        router.get("/github/auth/callback", async (req, res) => {
            var _a, _b;
            try {
                // GitHub sends state parameter back which contains projectId and userId
                const state = (_a = req.query["state"]) === null || _a === void 0 ? void 0 : _a.toString();
                if (!state) {
                    return Response.sendErrorResponse(req, res, new BadDataException("State parameter is required"));
                }
                // Verify and decode the signed state token
                let projectId;
                let userId;
                try {
                    const decodedState = JSONWebToken.decodeJsonPayload(state);
                    projectId = decodedState["projectId"];
                    userId = decodedState["userId"];
                }
                catch (_c) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Invalid or expired state parameter. Please restart the GitHub App installation."));
                }
                if (!projectId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Project ID is required in state"));
                }
                if (!userId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("User ID is required in state"));
                }
                // Verify the user is a member of this project
                const userTenantAccessPermission = await AccessTokenService.getUserTenantAccessPermission(new ObjectID(userId), new ObjectID(projectId));
                if (!userTenantAccessPermission) {
                    return Response.sendErrorResponse(req, res, new NotAuthorizedException("You do not have access to this project."));
                }
                // GitHub sends installation_id in query params after app installation
                const installationId = (_b = req.query["installation_id"]) === null || _b === void 0 ? void 0 : _b.toString();
                if (!installationId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Installation ID is required. Please install the GitHub App first."));
                }
                /*
                 * Store the installation ID in the project
                 * This allows reuse when connecting additional repositories
                 */
                await ProjectService.updateOneById({
                    id: new ObjectID(projectId),
                    data: {
                        gitHubAppInstallationId: installationId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                // Redirect back to dashboard with installation ID
                const redirectUrl = `${DashboardClientUrl.toString()}/${projectId}/code-repository?installation_id=${installationId}`;
                return Response.redirect(req, res, URL.fromString(redirectUrl));
            }
            catch (error) {
                logger.error("GitHub Auth Callback Error:", getLogAttributesFromRequest(req));
                logger.error(error, getLogAttributesFromRequest(req));
                return Response.sendErrorResponse(req, res, error instanceof Error
                    ? new BadDataException(error.message)
                    : new BadDataException("An error occurred"));
            }
        });
        // Initiate GitHub App installation
        router.get("/github/auth/install", async (req, res) => {
            var _a, _b;
            try {
                if (!GitHubAppName) {
                    return Response.sendErrorResponse(req, res, new BadDataException("GitHub App is not configured. Please set GITHUB_APP_NAME."));
                }
                const projectId = (_a = req.query["projectId"]) === null || _a === void 0 ? void 0 : _a.toString();
                const userId = (_b = req.query["userId"]) === null || _b === void 0 ? void 0 : _b.toString();
                if (!projectId || !userId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Project ID and User ID are required"));
                }
                /*
                 * Redirect to GitHub App installation page
                 * The state parameter is a signed JWT to prevent tampering
                 * It expires in 1 hour to limit the window for replay attacks
                 */
                const state = JSONWebToken.signJsonPayload({ projectId, userId }, 3600);
                const callbackUrl = `${HomeClientUrl.toString()}api/github/auth/callback`;
                const installUrl = `https://github.com/apps/${GitHubAppName}/installations/new?state=${encodeURIComponent(state)}&redirect_uri=${encodeURIComponent(callbackUrl)}`;
                return Response.redirect(req, res, URL.fromString(installUrl));
            }
            catch (error) {
                logger.error("GitHub Install Redirect Error:", getLogAttributesFromRequest(req));
                logger.error(error, getLogAttributesFromRequest(req));
                return Response.sendErrorResponse(req, res, error instanceof Error
                    ? new BadDataException(error.message)
                    : new BadDataException("An error occurred"));
            }
        });
        // List repositories for an installation
        router.get("/github/repositories/:projectId/:installationId", UserMiddleware.getUserMiddleware, async (req, res) => {
            var _a, _b, _c;
            try {
                const oneuptimeRequest = req;
                // Require authentication
                if (!oneuptimeRequest.userAuthorization) {
                    return Response.sendErrorResponse(req, res, new NotAuthenticatedException("Authentication is required to list repositories."));
                }
                const projectId = (_a = req.params["projectId"]) === null || _a === void 0 ? void 0 : _a.toString();
                const installationId = (_b = req.params["installationId"]) === null || _b === void 0 ? void 0 : _b.toString();
                if (!projectId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Project ID is required"));
                }
                if (!installationId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Installation ID is required"));
                }
                // Verify user has access to this project
                const userTenantAccessPermission = await AccessTokenService.getUserTenantAccessPermission(oneuptimeRequest.userAuthorization.userId, new ObjectID(projectId));
                if (!userTenantAccessPermission) {
                    return Response.sendErrorResponse(req, res, new NotAuthorizedException("You do not have access to this project."));
                }
                const repositories = await GitHubUtil.listRepositoriesForInstallation(installationId);
                return Response.sendJsonObjectResponse(req, res, {
                    repositories: repositories,
                });
            }
            catch (error) {
                logger.error("GitHub List Repositories Error:", getLogAttributesFromRequest(req));
                logger.error(error, getLogAttributesFromRequest(req));
                // Handle stale installation ID - clear it from the project and return specific error
                if (error instanceof GitHubInstallationNotFoundError) {
                    const projectId = (_c = req.params["projectId"]) === null || _c === void 0 ? void 0 : _c.toString();
                    if (projectId) {
                        try {
                            // Clear the stale installation ID from the project
                            await ProjectService.updateOneById({
                                id: new ObjectID(projectId),
                                data: {
                                    gitHubAppInstallationId: null,
                                },
                                props: {
                                    isRoot: true,
                                },
                            });
                            logger.info(`Cleared stale GitHub App installation ID from project ${projectId}`, getLogAttributesFromRequest(req));
                        }
                        catch (clearError) {
                            logger.error("Failed to clear stale installation ID from project:", getLogAttributesFromRequest(req));
                            logger.error(clearError, getLogAttributesFromRequest(req));
                        }
                    }
                    // Return the specific error so the frontend knows to prompt reinstallation
                    return Response.sendErrorResponse(req, res, error);
                }
                return Response.sendErrorResponse(req, res, error instanceof Error
                    ? new BadDataException(error.message)
                    : new BadDataException("An error occurred"));
            }
        });
        // Connect a repository to a project
        router.post("/github/repository/connect", UserMiddleware.getUserMiddleware, async (req, res) => {
            var _a, _b, _c, _d, _e, _f, _g, _h;
            try {
                const oneuptimeRequest = req;
                // Require authentication
                if (!oneuptimeRequest.userAuthorization) {
                    return Response.sendErrorResponse(req, res, new NotAuthenticatedException("Authentication is required to connect a repository."));
                }
                const body = req.body;
                const projectId = (_a = body["projectId"]) === null || _a === void 0 ? void 0 : _a.toString();
                const installationId = (_b = body["installationId"]) === null || _b === void 0 ? void 0 : _b.toString();
                const repositoryName = (_c = body["repositoryName"]) === null || _c === void 0 ? void 0 : _c.toString();
                const organizationName = (_d = body["organizationName"]) === null || _d === void 0 ? void 0 : _d.toString();
                const name = (_e = body["name"]) === null || _e === void 0 ? void 0 : _e.toString();
                const defaultBranch = (_f = body["defaultBranch"]) === null || _f === void 0 ? void 0 : _f.toString();
                const repositoryUrl = (_g = body["repositoryUrl"]) === null || _g === void 0 ? void 0 : _g.toString();
                const description = (_h = body["description"]) === null || _h === void 0 ? void 0 : _h.toString();
                if (!projectId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Project ID is required"));
                }
                if (!installationId) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Installation ID is required"));
                }
                // Verify user has access to this project
                const userTenantAccessPermission = await AccessTokenService.getUserTenantAccessPermission(oneuptimeRequest.userAuthorization.userId, new ObjectID(projectId));
                if (!userTenantAccessPermission) {
                    return Response.sendErrorResponse(req, res, new NotAuthorizedException("You do not have access to this project."));
                }
                if (!repositoryName) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Repository name is required"));
                }
                if (!organizationName) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Organization name is required"));
                }
                // Create the code repository record
                const codeRepository = new CodeRepository();
                codeRepository.projectId = new ObjectID(projectId);
                codeRepository.name = name || `${organizationName}/${repositoryName}`;
                codeRepository.repositoryHostedAt = CodeRepositoryType.GitHub;
                codeRepository.organizationName = organizationName;
                codeRepository.repositoryName = repositoryName;
                codeRepository.mainBranchName = defaultBranch || "main";
                codeRepository.gitHubAppInstallationId = installationId;
                if (repositoryUrl) {
                    codeRepository.repositoryUrl = URL.fromString(repositoryUrl);
                }
                if (description) {
                    codeRepository.description = description;
                }
                const createdRepository = await CodeRepositoryService.create({
                    data: codeRepository,
                    props: {
                        isRoot: true,
                    },
                });
                return Response.sendJsonObjectResponse(req, res, {
                    repository: BaseModel.toJSON(createdRepository, CodeRepository),
                });
            }
            catch (error) {
                logger.error("GitHub Connect Repository Error:", getLogAttributesFromRequest(req));
                logger.error(error, getLogAttributesFromRequest(req));
                return Response.sendErrorResponse(req, res, error instanceof Error
                    ? new BadDataException(error.message)
                    : new BadDataException("An error occurred"));
            }
        });
        // GitHub webhook handler
        router.post("/github/webhook", async (req, res) => {
            var _a, _b, _c, _d, _e;
            try {
                const signature = req.headers["x-hub-signature-256"];
                const event = req.headers["x-github-event"];
                if (!signature) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Missing webhook signature"));
                }
                // Get raw body for signature verification
                const rawBody = JSON.stringify(req.body);
                // Verify webhook signature
                const isValid = GitHubUtil.verifyWebhookSignature(rawBody, signature);
                if (!isValid) {
                    return Response.sendErrorResponse(req, res, new BadDataException("Invalid webhook signature"));
                }
                logger.debug(`Received GitHub webhook event: ${event}`, getLogAttributesFromRequest(req));
                // Handle installation events - specifically when the app is uninstalled
                if (event === "installation") {
                    const action = (_b = (_a = req.body) === null || _a === void 0 ? void 0 : _a["action"]) === null || _b === void 0 ? void 0 : _b.toString();
                    const installationId = (_e = (_d = (_c = req.body) === null || _c === void 0 ? void 0 : _c["installation"]) === null || _d === void 0 ? void 0 : _d["id"]) === null || _e === void 0 ? void 0 : _e.toString();
                    if (action === "deleted" && installationId) {
                        logger.info(`GitHub App installation ${installationId} was deleted. Clearing from database...`, getLogAttributesFromRequest(req));
                        try {
                            // Clear the installation ID from any projects that have it
                            await ProjectService.updateBy({
                                query: {
                                    gitHubAppInstallationId: installationId,
                                },
                                data: {
                                    gitHubAppInstallationId: null,
                                },
                                limit: 1000,
                                skip: 0,
                                props: {
                                    isRoot: true,
                                },
                            });
                            // Also clear from any code repositories that have this installation ID
                            await CodeRepositoryService.updateBy({
                                query: {
                                    gitHubAppInstallationId: installationId,
                                },
                                data: {
                                    gitHubAppInstallationId: null,
                                },
                                limit: 10000,
                                skip: 0,
                                props: {
                                    isRoot: true,
                                },
                            });
                            logger.info(`Successfully cleared GitHub App installation ${installationId} from database`, getLogAttributesFromRequest(req));
                        }
                        catch (clearError) {
                            logger.error(`Failed to clear GitHub App installation ${installationId} from database:`, getLogAttributesFromRequest(req));
                            logger.error(clearError, getLogAttributesFromRequest(req));
                        }
                    }
                }
                /*
                 * Handle different webhook events here
                 * For now, just acknowledge receipt
                 * Future: Handle push, pull_request, check_run events
                 */
                return Response.sendJsonObjectResponse(req, res, {
                    success: true,
                    message: "Webhook received",
                });
            }
            catch (error) {
                logger.error("GitHub Webhook Error:", getLogAttributesFromRequest(req));
                logger.error(error, getLogAttributesFromRequest(req));
                return Response.sendErrorResponse(req, res, error instanceof Error
                    ? new BadDataException(error.message)
                    : new BadDataException("An error occurred"));
            }
        });
        return router;
    }
}
//# sourceMappingURL=GitHubAPI.js.map