import { App, createNodeMiddleware } from "octokit"
import fs from "fs"
import {
  getGithubAppPrivateKey,
  getGithubAppWebhookSecret,
} from "@codeflash-ai/common/azure-keyvault"

import { posthog } from "../analytics"
import * as Sentry from "@sentry/node"

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

const PRIVATE_KEY: string =
  process.env.NODE_ENV === "production"
    ? await getGithubAppPrivateKey()
    : 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

export const githubApp = 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 ?? "",
  },
})
console.log(`Github App Initialized`)

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

githubApp.webhooks.onAny(async ({ id, name, payload }) => {
  console.log(`Github App: Received webhook event ${name} (${id})`)
  console.log(`Payload: ${JSON.stringify(payload)}`)
  posthog.capture({
    distinctId: `github|${payload.sender.id}`,
    event: `cfapi-github-webhook-received`,
    properties: {
      event: name,
      id,
    },
  })
})

console.log(`Github App Authenticated as '${data.name}'`)

githubApp.webhooks.on("installation", async ({ octokit, payload }) => {
  console.log(`Received a new installation event: ${JSON.stringify(payload)}`)
  // Create an installation access token
  const installationAccessToken = await octokit.rest.apps.createInstallationAccessToken({
    installation_id: payload.installation.id,
  })
  console.log(`Installation access token: ${installationAccessToken.data.token}`)
})

githubApp.webhooks.on("pull_request.opened", async ({ octokit, payload }) => {
  console.log(`Received a pull request opened event: ${JSON.stringify(payload)}`)
})
githubApp.webhooks.on("pull_request.edited", async ({ octokit, payload }) => {
  console.log(`Received a pull request edited event: ${JSON.stringify(payload)}`)
})

githubApp.webhooks.on("pull_request.closed", async ({ octokit, payload }) => {
  if (payload.pull_request) {
    console.log(
      `Received a pull request closed event. PR #${payload.pull_request.number} ` +
        `by ${payload.pull_request.user.login} was closed.`,
    )

    // Check if the PR was merged and is a PR created by Codeflash
    if (payload.pull_request.merged && payload.pull_request.user.id === APP_USER_ID) {
      // 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,
          },
        })
        console.log(
          `Commented on original PR #${originalPrNumber} and logged the event to Posthog.`,
        )
      } 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,
          },
        })
        console.log(`Logged standalone PR #${payload.pull_request.number} merge event to Posthog.`)
      }
    }

    // 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
    // Logic to close any open optimization PRs targeting this branch
    console.log(`Closing optimization PRs targeting branch ${closedPrBranch}...`)
    if (payload.installation === undefined) {
      console.error(
        `Error! Installation ID is missing from payload. Cannot close PRs for this installation!`,
      )
      return
    }
    try {
      const installationOctokit = await githubApp.getInstallationOctokit(payload.installation.id)
      const openPrs = await installationOctokit.rest.pulls.list({
        owner: payload.repository.owner.login,
        repo: payload.repository.name,
        state: "open",
        base: closedPrBranch,
      })

      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",
          })
          console.log(
            `Closed optimization PR #${pr.number} targeting branch '${closedPrBranch}' ` +
              `because original PR #${payload.pull_request.number} by ${payload.pull_request.user.login} was closed.`,
          )
          console.log(`Posting pull request comment...`)
          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.`,
          })
          console.log(`Deleting the branch associated with the closed PR...`)
          await installationOctokit.rest.git.deleteRef({
            owner: payload.repository.owner.login,
            repo: payload.repository.name,
            ref: `heads/${pr.head.ref}`,
          })
          console.log(
            `Branch '${pr.head.ref}' associated with the closed PR #${pr.number} has been deleted.`,
          )
        }
      }
    } catch (error) {
      console.error(`Failed to close optimization PRs targeting branch ${closedPrBranch}: ${error}`)
      Sentry.captureException(error)
    }
  }
})

githubApp.webhooks.on("installation.created", async ({ octokit, payload }) => {
  console.log(`Received a installation.created event: ${JSON.stringify(payload)}`)
})

githubApp.webhooks.on("installation_repositories.added", async ({ octokit, payload }) => {
  console.log(`Received a installation_repositories.added event: ${JSON.stringify(payload)}`)
})

githubApp.webhooks.on("marketplace_purchase", async ({ id, name, payload }) => {
  console.log(`Received a marketplace purchase event: ${name} (${id})`)
  console.log(`Payload: ${JSON.stringify(payload)}`)
  posthog.capture({
    distinctId: `github|${payload.sender.id}`,
    event: `cfapi-github-marketplace-purchase`,
    properties: {
      event: name,
      id,
    },
  })
})

githubApp.webhooks.on("pull_request.synchronize", async ({ octokit, payload }) => {
  if (payload.pull_request) {
    console.log(
      `Received a pull request synchronize event. PR #${payload.pull_request.number} ` +
        `by ${payload.pull_request.user.login} was updated with new commits.`,
    )
    // 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,
        },
      })
      console.log(`Logged co-authored commit to Posthog: ${latestCommit.sha}`)

      // 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.`,
      })
      console.log(
        `Commented on PR #${payload.pull_request.number} about the accepted review comment.`,
      )
    }
  }
})

// Optional: Handle errors
githubApp.webhooks.onError((error) => {
  console.log(`Error occurred in Github App: ${error}`)
  if (error.name === "AggregateError") {
    // Log Secret verification errors
    console.log(`Possible webhook secret verification error: ${JSON.stringify(error.event)}`)
  } else {
    console.log(error)
  }
  Sentry.captureException(error)
})

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

export const ghAppMiddleware = createNodeMiddleware(githubApp, {
  pathPrefix: ghAppPathPrefix,
})
