import { insertPR, insertComment, getCommentByGiteaId, getPRByGiteaId } from "./db"
import { getDiscordUser, getWebhook, getThread, getDiscordAvatarUrl } from "./discord-helpers"
import { convertUsername, threadName, fetchPR, fetchReviewComments } from "../gitea"
import type { Gitea } from "../gitea"
import { getConfig } from "../config"

type EventType = "issue_comment" | "pull_request" | "pull_request_comment"
export async function handleGiteaWebhook(payload: unknown, eventType: EventType) {
  if (ignorePayload(payload)) {
    return
  } else if (PRHandler.canHandle(payload, eventType)) {
    await PRHandler.handle(payload)
  } else if (ReviewHandler.canHandle(payload, eventType)) {
    await ReviewHandler.handle(payload)
  } else if (CommentHandler.canHandle(payload, eventType)) {
    await CommentHandler.handle(payload)
  }
}

const TEST_REPOS = ["ignore-me", "ignore-me-too"]

function ignorePayload(payload: any) {
  const isDev = process.env.NODE_ENV !== "production"
  const repositoryName = payload?.repository?.name
  if (isDev) {
    return !TEST_REPOS.includes(repositoryName)
  } else {
    return TEST_REPOS.includes(repositoryName)
  }
}

async function ensureThreadExists(
  pullRequest: Gitea.PullRequest | Gitea.Issue,
  repository: Gitea.Repository
) {
  const exists = getPRByGiteaId(pullRequest.id)
  if (exists) return

  const pr = await fetchPR(repository.full_name, pullRequest.number)
  await PRHandler.handleOpened(pr, repository)
}

class PRHandler {
  static canHandle(payload: unknown, eventType: EventType): payload is Gitea.PullRequestWebhook {
    return eventType === "pull_request"
  }

  static async handle(payload: Gitea.PullRequestWebhook) {
    const { pull_request: pullRequest, repository, action } = payload

    if (action === "opened") {
      await this.handleOpened(pullRequest, repository)
    } else if (action === "edited") {
      await this.handleEdited(pullRequest, repository)
    } else if (action === "closed" || action === "reopened") {
      await this.handleStateChange(pullRequest, repository)
    }

    console.log(`✅ pull request webhook action: ${action} #${payload.number}`)
  }

  static async handleOpened(pullRequest: Gitea.PullRequest, repository: Gitea.Repository) {
    // Post the PR details as first message in thread
    const webhook = await getWebhook()
    const prMessage = this.formatPrBody(pullRequest, repository.full_name)
    const discordUsername = convertUsername({ giteaUsername: pullRequest.user.login })

    const message = await webhook.send({
      content: prMessage,
      username: discordUsername,
      avatarURL: await getDiscordAvatarUrl(discordUsername),
    })

    const thread = await message.startThread({
      name: threadName(pullRequest, repository),
      reason: `PR #${pullRequest.number} opened`,
    })

    insertPR(pullRequest.id, repository.full_name, pullRequest.number, thread.id, message.id)
  }

  static async handleEdited(pullRequest: Gitea.PullRequest, repository: Gitea.Repository) {
    const thread = await getThread(pullRequest)
    if (!thread) {
      this.handleOpened(pullRequest, repository)
      return
    }

    const prBody = await thread.fetchStarterMessage()
    if (!prBody) {
      throw new Error(`Could not fetch PR body for #${pullRequest.number}`)
    }

    const updatedMessage = this.formatPrBody(pullRequest, repository.full_name)
    thread.setName(threadName(pullRequest, repository))
    const webhook = await getWebhook()
    await webhook.editMessage(prBody, { content: updatedMessage })
  }

  static async handleStateChange(pullRequest: Gitea.PullRequest, repository: Gitea.Repository) {
    const thread = await getThread(pullRequest)
    if (!thread) {
      this.handleOpened(pullRequest, repository)
      return
    }

    await thread.setName(threadName(pullRequest, repository))
  }

  static formatPrBody(pullRequest: Gitea.PullRequest, repositoryFullName: string): string {
    const body = pullRequest.body || "_empty_"
    let message = `
  > ### [${pullRequest.title}](<${pullRequest.html_url}>)
  > **${repositoryFullName}**
  >
  ${body
    .split("\n")
    .map((line) => `> ${line}`)
    .join("\n")}
  `

    const maxMessageSize = 2000
    const truncateMessage = `\n_message truncated. View full PR on Gitea_`

    if (message.length < maxMessageSize) return message

    return message.slice(0, maxMessageSize - 1 - truncateMessage.length) + truncateMessage
  }
}

class CommentHandler {
  static canHandle(payload: unknown, eventType: EventType): payload is Gitea.IssueCommentWebhook {
    return eventType === "issue_comment"
  }

  static async handle(payload: Gitea.IssueCommentWebhook) {
    const { issue, comment, repository, action } = payload

    if (!issue.pull_request) return

    if (action === "created") {
      await ensureThreadExists(issue, repository)
      await this.handleCreated(issue, comment)
    } else if (action === "edited") {
      await ensureThreadExists(issue, repository)
      await this.handleEdited(issue, comment)
    } else if (action === "deleted") {
      await ensureThreadExists(issue, repository)
      await this.handleDeleted(comment)
    }

    console.log(`✅ comment webhook action: ${action} on #${issue.number}`)
  }

  static async handleCreated(issue: Gitea.Issue, comment: Gitea.Comment) {
    const commentRow = getCommentByGiteaId(comment.id)
    if (commentRow) {
      return // Comment already exists, skip
    }

    const discordUsername = convertUsername({ giteaUsername: comment.user.login })
    const thread = await getThread(issue)
    if (!thread) {
      throw new Error(`No thread found for PR #${issue.number} when handling new comment`)
    }

    const webhook = await getWebhook()
    const message = await webhook.send({
      content: await this.formatComment(comment.body),
      username: discordUsername,
      avatarURL: await getDiscordAvatarUrl(discordUsername),
      threadId: thread.id,
    })

    insertComment(comment.id, message.id, thread.id)
  }

  static async handleEdited(issue: Gitea.Issue, comment: Gitea.Comment) {
    const commentRow = getCommentByGiteaId(comment.id)
    if (!commentRow) {
      this.handleCreated(issue, comment)
      return
    }

    const webhook = await getWebhook()
    const content = await this.formatComment(comment.body)
    await webhook.editMessage(commentRow.discord_message_id, {
      content,
      threadId: commentRow.discord_thread_id,
    })
  }

  static async handleDeleted(comment: Gitea.Comment) {
    const commentRow = getCommentByGiteaId(comment.id)
    if (!commentRow) {
      return // Comment doesn't exist, skip
    }

    const webhook = await getWebhook()
    await webhook.editMessage(commentRow.discord_message_id, {
      content: `[DELETED]`,
      threadId: commentRow.discord_thread_id,
    })
  }

  static async formatComment(comment: string) {
    const giteaToDiscordUserMappings = getConfig("giteaToDiscordUserMappings")

    // Find all @username mentions
    const mentionRegex = /@(\w+)/g
    const mentions = [...comment.matchAll(mentionRegex)]

    let result = comment
    for (const match of mentions) {
      const giteaUsername = match[1]!
      const discordUsername = giteaToDiscordUserMappings[giteaUsername]

      if (discordUsername) {
        const discordUser = await getDiscordUser(discordUsername)
        if (discordUser) {
          result = result.replace(match[0], `<@${discordUser.id}>`)
        }
      }
    }

    return result
  }
}

class ReviewHandler {
  static canHandle(payload: unknown, eventType: EventType): payload is Gitea.PullRequestReviewWebhook {
    return eventType === "pull_request_comment"
  }

  static async handle(payload: Gitea.PullRequestReviewWebhook) {
    const { pull_request: pullRequest, repository, review, action } = payload

    // Only handle new reviews, ignore edits
    if (action !== "reviewed") {
      console.log(`✅ review webhook action: ${action} on #${pullRequest.number} (ignored)`)
      return
    }

    await ensureThreadExists(pullRequest as any, repository as any)

    const thread = await getThread(pullRequest)
    if (!thread) {
      throw new Error(`No thread found for PR #${pullRequest.number} when handling review`)
    }

    const webhook = await getWebhook()
    const discordUsername = convertUsername({ giteaUsername: payload.sender.login })

    // Post the review body if it exists
    if (review.content) {
      const reviewMessage = (await CommentHandler.formatComment(review.content))
        .split("\n")
        .map((s) => `> ${s}`)
        .join("\n")

      await webhook.send({
        content: `**Review:**\n${reviewMessage}\n${payload.review.type}`,
        username: discordUsername,
        avatarURL: await getDiscordAvatarUrl(discordUsername),
        threadId: thread.id,
      })
    }

    // Fetch and post all review comments
    const reviewComments = await fetchReviewComments(repository.full_name, pullRequest.number)

    for (const comment of reviewComments) {
      const existingComment = getCommentByGiteaId(comment.id)
      if (existingComment) {
        continue // Skip if already posted
      }

      const formattedComment = await this.formatReviewComment(comment)
      const message = await webhook.send({
        content: formattedComment,
        username: discordUsername,
        avatarURL: await getDiscordAvatarUrl(discordUsername),
        threadId: thread.id,
      })

      insertComment(comment.id, message.id, thread.id)
    }

    console.log(`✅ review webhook action: reviewed on #${pullRequest.number}`)
  }

  static async formatReviewComment(comment: Gitea.ReviewComment): Promise<string> {
    const formattedBody = await CommentHandler.formatComment(comment.body)
    const codeContext = this.extractCodeFromDiff(comment.diff_hunk)

    return `
[**${comment.path}**](<${comment.html_url}>)
\`\`\`diff
${codeContext}
\`\`\`
${formattedBody}
`.trim()
  }

  static extractCodeFromDiff(diffHunk: string): string {
    const lines = diffHunk.split("\n")
    const codeLines = lines.filter((line) => !line.startsWith("@@"))
    return codeLines.join("\n")
  }
}
