import { App } from "octokit"
import { createNodeMiddleware } from "@octokit/webhooks"
import fs from "node:fs"
import {
  getGithubAppPrivateKey,
  getGithubAppWebhookSecret,
} from "@codeflash-ai/common/dist/src/azure-keyvault.js"

import { posthog } from "../analytics.js"
import * as Sentry from "@sentry/node"
import { logger } from "../utils/logger.js"
import {
  createAppInstallation,
  createOrUpdateUser,
  getAppInstallationByInstalltionId,
  organizationMemberRepository,
  organizationRepository,
  prisma,
  upsertRepository,
  upsertRepositoryMember,
} from "@codeflash-ai/common"
import { getUserRole } from "./github-utils.js"

const APP_USER_ID: number = parseInt(process.env.GH_APP_USER_ID ?? "0") // GitHub App User ID

/** Extract common webhook context fields for structured logging */
function webhookContext(payload: any, operation: string) {
  return {
    repoOwner: payload?.repository?.owner?.login,
    repoName: payload?.repository?.name,
    prNumber: payload?.pull_request?.number,
    installationId: payload?.installation?.id,
    operation,
  }
}

// Create a function to get config without initializing the app
async function getGithubAppConfig() {
  const APP_ID: string = process.env.GH_APP_ID ?? ""

  const PRIVATE_KEY: string =
    process.env.NODE_ENV === "production"
      ? await getGithubAppPrivateKey()
      : process.env.NODE_ENV === "test"
        ? "test-private-key"
        : fs.readFileSync("github/Codeflash AI Dev GitHub App Private Key.pem", "utf8")

  const WEBHOOK_SECRET: string =
    process.env.NODE_ENV === "production"
      ? await getGithubAppWebhookSecret()
      : (process.env.GH_APP_WEBHOOK_SECRET ?? "default-secret")

  return { APP_ID, PRIVATE_KEY, WEBHOOK_SECRET }
}

// Initialize the app conditionally
const initializeApp = async () => {
  const { APP_ID, PRIVATE_KEY, WEBHOOK_SECRET } = await getGithubAppConfig()

  return new App({
    appId: APP_ID,
    privateKey: PRIVATE_KEY,
    webhooks: {
      secret: WEBHOOK_SECRET,
    },
    oauth: {
      // OAuth details are currently unused by the app
      clientId: "", // process.env.GH_APP_CLIENT_ID ?? "",
      clientSecret: "", // process.env.GH_APP_CLIENT_SECRET ?? "",
    },
  })
}

// Export the actual App instance, initialized based on environment
export const githubApp = await (async () => {
  // Check if GitHub App is configured
  const GH_APP_ID = process.env.GH_APP_ID

  if (!GH_APP_ID || GH_APP_ID === "" || process.env.NODE_ENV === "test") {
    logger.warn("GitHub App not configured (GH_APP_ID missing)", { operation: "server_startup" })
    logger.warn("PR creation and GitHub webhook features are disabled", {
      operation: "server_startup",
    })
    logger.info("CLI and optimization features will continue to work", {
      operation: "server_startup",
    })

    // Return a minimal mock that won't fail
    return {
      octokit: {
        request: async () => ({ data: { name: "GitHub App Disabled" } }),
        log: {
          debug: () => {},
        },
      },
      webhooks: {
        on: () => {},
        onAny: () => {},
        onError: () => {},
      },
      getInstallationOctokit: async () => {
        throw new Error("GitHub App not configured. Set GH_APP_ID to enable PR creation.")
      },
    } as any as App
  }

  // In other environments, initialize normally
  logger.info(`GitHub App ID ${GH_APP_ID} detected, initializing...`, {
    operation: "server_startup",
  })
  const app = await initializeApp()

  logger.info("GitHub App initialized", { operation: "server_startup" })

  const { data } = await app.octokit.request("/app")
  // Read more about custom logging: https://github.com/octokit/core.js#logging
  app.octokit.log.debug(`Authenticated as '${data.name}'`)

  app.webhooks.onAny(async ({ id, name, payload }) => {
    // Only log event type and ID, not full payload (too verbose)
    logger.info(
      "GitHub App: Received webhook event",
      {
        operation: "webhook_received",
        repoOwner: (payload as any)?.repository?.owner?.login,
        repoName: (payload as any)?.repository?.name,
      },
      { eventType: name, eventId: id },
    )
    posthog?.capture({
      distinctId: `github|${payload.sender?.id}`,
      event: `cfapi-github-webhook-received`,
      properties: {
        event: name,
        id,
      },
    })
  })

  logger.info(`GitHub App authenticated as '${data.name}'`, { operation: "server_startup" })

  app.webhooks.on("installation", async ({ octokit, payload }) => {
    const account = payload.installation.account
    const accountName =
      account && "login" in account
        ? account.login
        : account && "slug" in account
          ? account.slug
          : "unknown"
    logger.info(
      `Received installation event: installation_id=${payload.installation.id}, account=${accountName}`,
      webhookContext(payload, "installation"),
    )
    // Create an installation access token
    const installationAccessToken = await octokit.rest.apps.createInstallationAccessToken({
      installation_id: payload.installation.id,
    })
    // Don't log the token for security reasons
  })

  app.webhooks.on("pull_request.opened", async ({ octokit, payload }) => {
    logger.info(
      `Received pull_request.opened event: PR #${payload.pull_request?.number} in ${payload.repository?.full_name}`,
      webhookContext(payload, "pull_request_opened"),
    )
  })

  app.webhooks.on("pull_request.edited", async ({ octokit, payload }) => {
    logger.info(
      `Received pull_request.edited event: PR #${payload.pull_request?.number} in ${payload.repository?.full_name}`,
      webhookContext(payload, "pull_request_edited"),
    )
  })

  app.webhooks.on("pull_request.closed", async ({ octokit, payload }) => {
    if (payload.pull_request) {
      const prId = String(payload.pull_request.id)
      try {
        const optimizationEvent = await prisma.optimization_events.findUnique({
          where: { pr_id: prId },
          select: { id: true, metadata: true },
        })

        if (optimizationEvent) {
          const currentMetadata = (optimizationEvent.metadata ?? {}) as Record<string, unknown>
          // Remove line profiler data from metadata
          delete currentMetadata.originalLineProfiler
          delete currentMetadata.optimizedLineProfiler

          await prisma.optimization_events.update({
            where: { id: optimizationEvent.id },
            data: {
              event_type: payload.pull_request.merged ? "pr_merged" : "pr_closed",
              metadata: currentMetadata as object,
            },
          })
        }

        logger.info(
          `Updated optimization_event for PR ID ${prId} to ${payload.pull_request.merged ? "pr_merged" : "pr_closed"} and removed line profiler data`,
          webhookContext(payload, "pull_request_closed"),
        )
      } catch (err) {
        logger.error(
          `Failed to update optimization_event for PR ID ${prId}:`,
          webhookContext(payload, "pull_request_closed"),
          {},
          err as Error,
        )
      }
      logger.info(
        `Received pull_request.closed event: PR #${payload.pull_request.number} by ${payload.pull_request.user.login} was closed`,
        webhookContext(payload, "pull_request_closed"),
      )

      // Check if the PR was merged and is a PR created by Codeflash
      const is_user_code_flash = payload.pull_request.user.id === APP_USER_ID
      try {
        if (payload.pull_request.merged && is_user_code_flash) {
          // Extract the original PR number from the branch name
          const dependentBranchNamePattern = /codeflash.optimize-pr(\d+)-\d{4}-\d{2}-\d{2}T.+$/
          const standaloneBranchNamePattern = /codeflash.optimize-(.+)-\d{4}-\d{2}-\d{2}T.+$/
          const dependentPrMatch = dependentBranchNamePattern.exec(payload.pull_request.head.ref)
          const standalonePrMatch = standaloneBranchNamePattern.exec(payload.pull_request.head.ref)
          if (dependentPrMatch != null) {
            const originalPrNumber = parseInt(dependentPrMatch[1])
            let username = "You"
            if (payload.pull_request.merged_by != null) {
              // should not be null, but check anyway
              username = `@${payload.pull_request.merged_by.login}`
            }
            // Comment on the original PR
            await octokit.rest.issues.createComment({
              owner: payload.repository.owner.login,
              repo: payload.repository.name,
              issue_number: originalPrNumber,
              body: `This PR is now faster! 🚀 ${username} accepted my optimizations from:
- #${payload.pull_request.number}`,
            })

            posthog?.capture({
              distinctId: `github|${payload.sender.id}`, // this is the user who merged the PR
              event: `cfapi-github-dependent-pr-merged`,
              properties: {
                owner: payload.repository.owner.login,
                repo: payload.repository.name,
                originalPrNumber,
                dependentPrNumber: payload.pull_request.number,
                mergedBy: payload.pull_request.merged_by?.login,
              },
            })
            logger.info(
              `Commented on original PR #${originalPrNumber} and logged the event to PostHog`,
              webhookContext(payload, "dependent_pr_merged"),
            )
          } else if (standalonePrMatch != null) {
            posthog?.capture({
              distinctId: `github|${payload.sender.id}`,
              event: `cfapi-github-standalone-pr-merged`,
              properties: {
                owner: payload.repository.owner.login,
                repo: payload.repository.name,
                functionName: standalonePrMatch[1],
                prNumber: payload.pull_request.number,
                mergedBy: payload.pull_request.merged_by?.login,
              },
            })
            logger.info(
              `Logged standalone PR #${payload.pull_request.number} merge event to PostHog`,
              webhookContext(payload, "standalone_pr_merged"),
            )
          }
        }
      } catch (mergedPrError) {
        logger.errorWithSentry(
          "Failed to process merged PR comment/analytics",
          webhookContext(payload, "pull_request_closed"),
          {},
          mergedPrError as Error,
        )
      }

      // Close any open optimization PRs targeting the branch of the closed PR
      // Ensure we only close PRs that are targeting the branch of the PR that was just closed
      const closedPrBranch = payload.pull_request.head.ref
      const closeCtx = webhookContext(payload, "close_dependent_prs")
      logger.info(`Closing optimization PRs targeting branch ${closedPrBranch}`, closeCtx, {
        is_user_code_flash,
        closedPrBranch,
        APP_USER_ID,
      })
      if (payload.installation === undefined) {
        logger.error(
          "Installation ID is missing from payload. Cannot close PRs for this installation!",
          closeCtx,
        )
        return
      }
      try {
        const installationOctokit = await app.getInstallationOctokit(payload.installation.id)
        const openPrs = await installationOctokit.rest.pulls.list({
          owner: payload.repository.owner.login,
          repo: payload.repository.name,
          state: "open",
          base: closedPrBranch,
        })

        logger.info(
          `Found ${openPrs.data.length} open PRs targeting branch ${closedPrBranch}`,
          closeCtx,
          {
            openPrCount: openPrs.data.length,
            openPrNumbers: openPrs.data.map(pr => pr.number).join(","),
            openPrUsers: openPrs.data
              .map(pr => `#${pr.number}:${pr.user?.login}(id=${pr.user?.id},type=${pr.user?.type})`)
              .join(","),
          },
        )

        for (const pr of openPrs.data) {
          // Check if the PR is opened by the Codeflash GitHub App and targets the same base branch as the closed PR
          if (
            pr.user?.type === "Bot" &&
            pr.user?.id === APP_USER_ID &&
            pr.base.ref === closedPrBranch
          ) {
            await installationOctokit.rest.pulls.update({
              owner: payload.repository.owner.login,
              repo: payload.repository.name,
              pull_number: pr.number,
              state: "closed",
            })
            logger.info(
              `Closed optimization PR #${pr.number} targeting branch '${closedPrBranch}' because original PR #${payload.pull_request.number} by ${payload.pull_request.user.login} was closed`,
              webhookContext(payload, "close_dependent_prs"),
            )
            logger.info(
              "Posting pull request comment...",
              webhookContext(payload, "close_dependent_prs"),
            )
            await octokit.rest.issues.createComment({
              owner: payload.repository.owner.login,
              repo: payload.repository.name,
              issue_number: pr.number,
              body:
                `>This PR has been automatically closed because the original PR #${payload.pull_request.number} ` +
                `by ${payload.pull_request.user.login} was closed.`,
            })

            // Delete the optimization PR's branch (safe: pr.user.id === APP_USER_ID verified above)
            await deleteBranchIfExists(installationOctokit, payload, pr.head.ref)
          }
        }

        // If the closed PR itself was created by Codeflash, delete its branch too.
        // Guard is correct here: we must NOT delete a user's feature branch.
        if (is_user_code_flash) {
          await deleteBranchIfExists(installationOctokit, payload, closedPrBranch)
        }
      } catch (error) {
        logger.errorWithSentry(
          `Failed to close optimization PRs targeting branch ${closedPrBranch}`,
          webhookContext(payload, "close_dependent_prs"),
          {},
          error as Error,
        )
      }
    }
  })

  app.webhooks.on("installation.created", async ({ octokit, payload }) => {
    // TODO: check if it's organization
    const account = payload.installation.account
    const accountName =
      account && "login" in account
        ? account.login
        : account && "slug" in account
          ? account.slug
          : "unknown"
    logger.info(
      `Received installation.created event: installation_id=${payload.installation.id}, account=${accountName}`,
      webhookContext(payload, "installation_created"),
    )
  })

  app.webhooks.on("installation_repositories.added", async ({ octokit, payload }) => {
    const repoCount = payload.repositories_added?.length || 0
    logger.info(
      `Received installation_repositories.added event: installation_id=${payload.installation.id}, repositories_added=${repoCount}`,
      webhookContext(payload, "installation_repositories_added"),
    )
  })

  app.webhooks.on("marketplace_purchase", async ({ id, name, payload }) => {
    logger.info(
      `Received marketplace purchase event: ${name} (${id})`,
      webhookContext(payload, "marketplace_purchase"),
    )
    posthog?.capture({
      distinctId: `github|${payload.sender.id}`,
      event: `cfapi-github-marketplace-purchase`,
      properties: {
        event: name,
        id,
      },
    })
  })

  app.webhooks.on("pull_request.synchronize", async ({ octokit, payload }) => {
    if (payload.pull_request) {
      logger.info(
        `Received pull_request.synchronize event: PR #${payload.pull_request.number} by ${payload.pull_request?.user?.login} was updated with new commits`,
        webhookContext(payload, "pull_request_synchronize"),
      )
      // Retrieve the list of commits for the pull request
      const commits = await octokit.rest.pulls.listCommits({
        owner: payload.repository.owner.login,
        repo: payload.repository.name,
        pull_number: payload.pull_request.number,
      })

      // Check the latest commit for the co-authored-by line
      const latestCommit = commits.data[commits.data.length - 1]
      if (
        latestCommit.commit.message.includes(
          "Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com>",
        )
      ) {
        // Log the event to Posthog
        posthog?.capture({
          distinctId: `github|${payload.sender.id}`,
          event: `cfapi-github-commit-coauthored-by-codeflash`,
          properties: {
            prNumber: payload.pull_request.number,
            commitId: latestCommit.sha,
            repository: payload.repository.full_name,
            author: latestCommit.commit.author?.name,
          },
        })
        logger.info(
          `Logged co-authored commit to PostHog: ${latestCommit.sha}`,
          webhookContext(payload, "pull_request_synchronize"),
        )

        // should not be null, but check anyway
        const authorname = latestCommit.commit.author?.name ?? "You"
        // Comment on the PR
        await octokit.rest.issues.createComment({
          owner: payload.repository.owner.login,
          repo: payload.repository.name,
          issue_number: payload.pull_request.number,
          body: `This PR is now faster! 🚀 ${authorname} accepted my code suggestion above.`,
        })
        logger.info(
          `Commented on PR #${payload.pull_request.number} about the accepted review comment`,
          webhookContext(payload, "pull_request_synchronize"),
        )
      }
    }
  })

  // Helper function to process feedback from PR comments
  async function processFeedbackComment({
    octokit,
    commentBody,
    commentAuthor,
    commentId,
    prNumber,
    repository,
    commentType,
  }: {
    octokit: any
    commentBody: string
    commentAuthor: { id: number; login: string; email?: string | null; name?: string | null }
    commentId: number
    prNumber: number
    repository: { owner: { login: string }; name: string; full_name: string }
    commentType: "issue_comment" | "review_comment"
  }) {
    // Check if the comment mentions the bot
    // Support both @codeflash-ai and @codeflash-ai[bot] mentions
    // Extract feedback as everything after the mention
    const mentionPattern = /@codeflash-ai(?:\[bot\])?\s*([\s\S]*)/i

    const mentionMatch = mentionPattern.exec(commentBody)
    if (!mentionMatch) {
      return
    }

    const feedbackContent = mentionMatch[1].trim()
    if (!feedbackContent) {
      logger.info(`Empty feedback received from ${commentAuthor.login}, ignoring`, {
        operation: "process_feedback",
        repoOwner: repository.owner.login,
        repoName: repository.name,
        prNumber,
      })
      return
    }

    logger.info(
      `Received feedback (${commentType}) from ${commentAuthor.login} on PR #${prNumber}: "${feedbackContent.substring(0, 100)}..."`,
      {
        operation: "process_feedback",
        repoOwner: repository.owner.login,
        repoName: repository.name,
        prNumber,
      },
    )

    // Helper to add reaction based on comment type
    const addReaction = async (content: "+1") => {
      if (commentType === "issue_comment") {
        await octokit.rest.reactions.createForIssueComment({
          owner: repository.owner.login,
          repo: repository.name,
          comment_id: commentId,
          content,
        })
      } else {
        await octokit.rest.reactions.createForPullRequestReviewComment({
          owner: repository.owner.login,
          repo: repository.name,
          comment_id: commentId,
          content,
        })
      }
    }

    try {
      // Find the optimization event associated with this PR
      const pr = await octokit.rest.pulls.get({
        owner: repository.owner.login,
        repo: repository.name,
        pull_number: prNumber,
      })

      const prId = String(pr.data.id)
      const prUrl = pr.data.html_url
      logger.info(`Looking for optimization event with pr_id=${prId} or pr_url=${prUrl}`, {
        operation: "process_feedback",
        repoOwner: repository.owner.login,
        repoName: repository.name,
        prNumber,
      })

      // Find optimization events by PR ID or by PR URL
      const optimizationEvent = await prisma.optimization_events.findFirst({
        where: {
          OR: [
            { pr_id: prId },
            { pr_url: prUrl },
            {
              pr_url: {
                contains: `/${repository.full_name}/pull/${prNumber}`,
              },
            },
          ],
        },
        orderBy: {
          created_at: "desc",
        },
      })

      if (!optimizationEvent) {
        logger.info(
          `No optimization event found for PR #${prNumber} in ${repository.full_name} (pr_id=${prId})`,
          {
            operation: "process_feedback",
            repoOwner: repository.owner.login,
            repoName: repository.name,
            prNumber,
          },
        )
        await addReaction("+1")
        return
      }

      logger.info(
        `Found optimization event: id=${optimizationEvent.id}, trace_id=${optimizationEvent.trace_id}`,
        {
          operation: "process_feedback",
          repoOwner: repository.owner.login,
          repoName: repository.name,
          prNumber,
        },
      )

      // Create or get the user
      const user = await createOrUpdateUser(
        `github|${commentAuthor.id}`,
        commentAuthor.login,
        commentAuthor.email ?? null,
        commentAuthor.name ?? null,
      )

      // Atomically append feedback to optimization event's feedback field (JSON array)
      // Using SELECT FOR UPDATE to lock the row and prevent race conditions
      const newFeedbackEntry = {
        content: feedbackContent,
        authorLogin: commentAuthor.login,
        authorId: commentAuthor.id,
        commentType,
        createdAt: new Date().toISOString(),
      }

      await prisma.$transaction(async tx => {
        // Lock the row with FOR UPDATE to prevent concurrent modifications
        const [lockedEvent] = await tx.$queryRaw<Array<{ feedback: unknown[] }>>`
          SELECT feedback FROM optimization_events WHERE id = ${optimizationEvent.id} FOR UPDATE
        `
        const existingFeedback = (lockedEvent.feedback as any[]) || []

        await tx.optimization_events.update({
          where: { id: String(optimizationEvent.id) },
          data: {
            feedback: [...existingFeedback, newFeedbackEntry],
          },
        })
      })

      logger.info(
        `Saved feedback from ${commentAuthor.login} for optimization event ${optimizationEvent.id} (trace_id: ${optimizationEvent.trace_id})`,
        {
          operation: "process_feedback",
          repoOwner: repository.owner.login,
          repoName: repository.name,
          prNumber,
        },
      )

      // Log to PostHog
      posthog?.capture({
        distinctId: `github|${commentAuthor.id}`,
        event: `cfapi-github-feedback-received`,
        properties: {
          prNumber,
          repository: repository.full_name,
          optimizationEventId: optimizationEvent.id,
          traceId: optimizationEvent.trace_id,
          feedbackContent,
          feedbackLength: feedbackContent.length,
          commentType,
          functionName: optimizationEvent.function_name,
          filePath: optimizationEvent.file_path,
          speedupX: optimizationEvent.speedup_x,
          speedupPct: optimizationEvent.speedup_pct,
          authorLogin: commentAuthor.login,
        },
      })

      // React with a thumbs up to acknowledge the feedback
      await addReaction("+1")
    } catch (error) {
      logger.errorWithSentry(
        `Failed to process feedback from ${commentAuthor.login}`,
        {
          operation: "process_feedback",
          repoOwner: repository.owner.login,
          repoName: repository.name,
          prNumber,
        },
        {},
        error as Error,
      )

      try {
        await addReaction("+1")
      } catch (reactionError) {
        logger.error(
          "Failed to add reaction:",
          {
            operation: "process_feedback",
            repoOwner: repository.owner.login,
            repoName: repository.name,
            prNumber,
          },
          {},
          reactionError as Error,
        )
      }
    }
  }

  // Handle PR conversation thread comments (issue_comment)
  app.webhooks.on("issue_comment.created", async ({ octokit, payload }) => {
    // Only process comments on pull requests (not regular issues)
    if (!payload.issue.pull_request) {
      return
    }

    await processFeedbackComment({
      octokit,
      commentBody: payload.comment.body,
      commentAuthor: payload.comment.user,
      commentId: payload.comment.id,
      prNumber: payload.issue.number,
      repository: payload.repository,
      commentType: "issue_comment",
    })
  })

  // Handle PR review comments (comments on specific lines of code)
  app.webhooks.on("pull_request_review_comment.created", async ({ octokit, payload }) => {
    await processFeedbackComment({
      octokit,
      commentBody: payload.comment.body,
      commentAuthor: payload.comment.user,
      commentId: payload.comment.id,
      prNumber: payload.pull_request.number,
      repository: payload.repository,
      commentType: "review_comment",
    })
  })

  // Optional: Handle errors
  app.webhooks.onError(error => {
    const errorContext = { operation: "webhook_error" }
    logger.error(`Error occurred in GitHub App's onError handler: ${error}`, errorContext)
    if (error instanceof Error) {
      // Check if it's an AggregateError, common for signature issues
      if (error.name === "AggregateError" && Array.isArray((error as any).errors)) {
        logger.error(
          "AggregateError details (possible secret mismatch or multiple issues):",
          errorContext,
        )
        ;(error as any).errors.forEach((subError: Error, i: number) => {
          logger.error(`  Sub-error ${i + 1}: ${subError.message}`, errorContext)
        })
      } else if (error.message.includes("content length")) {
        logger.error(
          "Content length mismatch detected by Octokit. Payload may be truncated or header incorrect.",
          errorContext,
        )
        const eventRequest = (error as any).event?.request
        if (eventRequest?.headers) {
          logger.error("Request headers from error.event:", errorContext, {
            headers: JSON.stringify(eventRequest.headers, null, 2),
          })
        }
      }
      // Log the full error structure for better debugging
      logger.error("Full error object (onError):", errorContext, {
        errorDetails: JSON.stringify(error, Object.getOwnPropertyNames(error), 2),
      })
    } else {
      logger.error("Full error (onError, non-Error instance):", errorContext, {
        errorDetails: String(error),
      })
    }
    Sentry.captureException(error)
  })

  app.webhooks.on("installation_repositories", async ({ payload }) => {
    const repoCount = payload.repositories_added?.length || 0
    logger.info(
      `Received installation_repositories event: installation_id=${payload.installation?.id}, repositories_added=${repoCount}`,
      webhookContext(payload, "installation_repositories"),
    )
    const { repositories_added, installation, sender } = payload
    // Check if required fields are missing
    if (!repositories_added || !installation?.id) {
      logger.warn(
        "Missing repositories_added or installation.id",
        webhookContext(payload, "installation_repositories"),
      )
      return
    }
    const account = installation.account
    let accountLogin: string | undefined

    // Check if the account is a user or an organization
    if ("login" in account) {
      // It's a user account, use `login`
      accountLogin = account.login
    } else if ("slug" in account) {
      // It's an organization account, use `slug`
      accountLogin = account.slug
    }

    let accountType: string | undefined
    if ("type" in account) {
      accountType = account.type
    } else {
      accountType = "Organization" // fallback assumption
    }

    if (!accountLogin) {
      logger.error(
        "Account login or slug not found",
        webhookContext(payload, "installation_repositories"),
      )
      return
    }

    // Check if the installation exists, if not, create it
    const installationExists = await getAppInstallationByInstalltionId(installation.id)

    if (!installationExists) {
      // Create the installation if it doesn't exist
      await createAppInstallation({
        installation_id: installation.id,
        account_id: installation.account.id,
        account_login: accountLogin,
        account_type: accountType,
      })
      logger.info(
        `Installation created for ID: ${installation.id}`,
        webhookContext(payload, "installation_repositories"),
      )
    }

    // Process each repository in the list of added repositories
    for (const repo of repositories_added) {
      try {
        // Get the GitHub user ID of the sender
        const githubUserId = sender?.id

        if (githubUserId) {
          logger.info(
            `GitHub User ID: ${githubUserId} triggered the event`,
            webhookContext(payload, "installation_repositories"),
          )
          // Fetch the user's role using the helper
          // Use octokit from getInstallationOctokit for this installation
          const installationOctokit = await app.getInstallationOctokit(installation.id)
          const userRole = await getUserRole({
            octokit: installationOctokit,
            owner: accountLogin,
            repo: repo.name,
            username: sender.login,
            isOrg: accountType === "Organization",
          })
          logger.info(
            `Fetched user role: ${userRole}`,
            webhookContext(payload, "installation_repositories"),
          )
          const user = await createOrUpdateUser(
            `github|${githubUserId}`,
            sender.login,
            sender.email ?? null,
            sender.name ?? null,
          )

          let orgId: string = undefined
          if (accountType === "Organization") {
            const ghOrgId = String(account.id)
            const existingOrg = await prisma.organizations.findUnique({
              where: { github_org_id: ghOrgId },
            })
            orgId = existingOrg ? String(existingOrg.id) : undefined
            if (!existingOrg) {
              const organization = await organizationRepository.upsertOrganization({
                github_org_id: ghOrgId,
                name: account.name || accountLogin,
                added_by: user.user_id,
              })

              await organizationMemberRepository.addMember({
                organizationId: organization.id,
                userId: user.user_id,
                role: "admin",
                addedBy: user.user_id, // Indicates that this user was the first to be added . If user_id equals addedBy, it means this user installed GitHub App for this repository.
              })

              logger.info(
                `Organization upserted: ${accountLogin}`,
                webhookContext(payload, "installation_repositories"),
              )
              orgId = organization.id
            }
          }

          const savedRepo = await upsertRepository({
            github_repo_id: String(repo.id),
            installation_id: installation.id,
            name: repo.name,
            full_name: repo.full_name,
            is_private: repo.private,
            organization_id: orgId,
          })

          logger.info(
            `Repository upserted: ${savedRepo.full_name}`,
            webhookContext(payload, "installation_repositories"),
          )
          await upsertRepositoryMember({
            repository_id: savedRepo.id,
            user_id: user.user_id,
            role: userRole,
          })
        } else {
          logger.error(
            "GitHub User ID not found in sender",
            webhookContext(payload, "installation_repositories"),
          )
        }
      } catch (error) {
        logger.errorWithSentry(
          `Failed to add/reactivate repository ${repo.full_name}`,
          webhookContext(payload, "installation_repositories"),
          {},
          error as Error,
        )
      }
    }
  })

  return app
})()

export const ghAppPathPrefix: string = "/cfapi/github"

// Path is already prefixed with /cfapi/github in the appExpress.postAsync handler
export const ghAppMiddleware = createNodeMiddleware(githubApp.webhooks, {
  path: "/cfapi/github/webhooks",
})

const deleteBranchIfExists = async (installationOctokit: any, payload: any, branchName: string) => {
  const ctx = {
    ...webhookContext(payload, "delete_branch"),
    branchName,
  }
  try {
    logger.info(`Deleting branch '${branchName}' associated with the closed PR`, ctx)
    // Check if the branch exists by querying the reference
    const ref = await installationOctokit.rest.git.getRef({
      owner: payload.repository.owner.login,
      repo: payload.repository.name,
      ref: `heads/${branchName}`,
    })

    // If the ref is found, it means the branch exists, and you can delete it
    logger.info(`Branch exists: ${ref.data.ref}`, ctx)

    // Proceed to delete the branch
    await installationOctokit.rest.git.deleteRef({
      owner: payload.repository.owner.login,
      repo: payload.repository.name,
      ref: `heads/${branchName}`,
    })
    logger.info(`Branch '${branchName}' has been deleted`, ctx)
  } catch (error: any) {
    // If the branch doesn't exist or other errors occur, catch the error
    if (error.status === 404) {
      logger.info(`Branch '${branchName}' does not exist`, ctx)
    } else {
      logger.error(
        `Error checking branch existence or deleting '${branchName}':`,
        ctx,
        {},
        error as Error,
      )
    }
  }
}
