import { Request, Response } from "express"
import { AuthorizedUserReq } from "../types.js"
import { userNickname } from "../auth0-mgmt.js"
import { getInstallationOctokitByOwner, isUserCollaborator } from "../github/github-utils.js"
import { githubApp } from "../github/github-app.js"
import { logger } from "../utils/logger.js"
import {
  missingRequiredFields,
  validationFailure,
  unauthorized,
  githubInstallationError,
  githubNotCollaborator,
  githubPrNotFound,
  forbidden,
  internalServerError,
} from "../exceptions/index.js"

// Dependencies interface for easier testing
export interface ClosePrDependencies {
  userNickname: typeof userNickname
  getInstallationOctokitByOwner: typeof getInstallationOctokitByOwner
  isUserCollaborator: typeof isUserCollaborator
  githubApp: typeof githubApp
}

// Default dependencies
let dependencies: ClosePrDependencies = {
  userNickname,
  getInstallationOctokitByOwner,
  isUserCollaborator,
  githubApp,
}

// For testing - allow dependency injection
export function setClosePrDependencies(deps: Partial<ClosePrDependencies>) {
  dependencies = { ...dependencies, ...deps }
}

export function resetClosePrDependencies() {
  dependencies = {
    userNickname,
    getInstallationOctokitByOwner,
    isUserCollaborator,
    githubApp,
  }
}

/**
 * Close a PR that was created by Codeflash
 *
 * This endpoint allows closing PRs that were raised by mistake on client repositories.
 * It verifies that:
 * 1. The user is authenticated and is a collaborator on the repo
 * 2. The PR was created by the Codeflash bot (safety check)
 * 3. The PR exists and is open
 */
export async function closePr(req: Request, res: Response): Promise<void> {
  const { owner, repo, pr_number, comment } = req.body

  // Validate required fields
  if (!owner || !repo || !pr_number) {
    throw missingRequiredFields("owner, repo, pr_number")
  }

  const ownerStr = String(owner).trim()
  const repoStr = String(repo).trim()
  const prNumber = Number(pr_number)

  if (ownerStr === "" || repoStr === "") {
    throw validationFailure("owner and repo cannot be empty")
  }

  if (isNaN(prNumber) || prNumber <= 0) {
    throw validationFailure("pr_number must be a positive integer")
  }

  try {
    // Verify user is authenticated
    const nickname = await dependencies.userNickname((req as AuthorizedUserReq).userId)
    if (nickname == null) {
      throw unauthorized("")
    }

    // Get installation octokit
    const installationOctokit = await dependencies.getInstallationOctokitByOwner(
      dependencies.githubApp,
      ownerStr,
      repoStr,
    )

    if (installationOctokit instanceof Error) {
      throw githubInstallationError(installationOctokit.message)
    }

    //Verify user is a collaborator
    const isCollaborator = await dependencies.isUserCollaborator(
      installationOctokit,
      ownerStr,
      repoStr,
      nickname,
    )

    if (!isCollaborator) {
      throw githubNotCollaborator(`${ownerStr}/${repoStr}`)
    }

    // Get the PR to verify it exists and was created by Codeflash
    let pr
    try {
      const prResponse = await installationOctokit.rest.pulls.get({
        owner: ownerStr,
        repo: repoStr,
        pull_number: prNumber,
      })
      pr = prResponse.data
    } catch (error: any) {
      if (error.status === 404) {
        throw githubPrNotFound(`${ownerStr}/${repoStr}#${prNumber}`)
      }
      throw error
    }

    // Safety check: Only allow closing PRs created by the Codeflash bot
    const isCodeflashBot = pr.user?.type === "Bot" && pr.user?.login?.includes("codeflash")
    if (!isCodeflashBot) {
      throw forbidden(
        `Cannot close PR #${prNumber} - it was not created by Codeflash. ` +
          `PR was created by ${pr.user?.login} (${pr.user?.type})`,
      )
    }

    // Check if PR is already closed
    if (pr.state === "closed") {
      logger.info("PR is already closed", req, {
        repo: `${ownerStr}/${repoStr}`,
        prNumber,
      })
      res.json({
        success: true,
        message: `PR #${prNumber} is already closed`,
        pr_url: pr.html_url,
        state: pr.state,
      })
      return
    }

    // Add a comment if provided
    const closeComment =
      comment || `This PR has been manually closed by ${nickname} via Codeflash admin API.`

    await installationOctokit.rest.issues.createComment({
      owner: ownerStr,
      repo: repoStr,
      issue_number: prNumber,
      body: closeComment,
    })

    // Close the PR
    await installationOctokit.rest.pulls.update({
      owner: ownerStr,
      repo: repoStr,
      pull_number: prNumber,
      state: "closed",
    })

    logger.info("Successfully closed PR", req, {
      repo: `${ownerStr}/${repoStr}`,
      prNumber,
      closedBy: nickname,
    })

    res.json({
      success: true,
      message: `Successfully closed PR #${prNumber}`,
      pr_url: pr.html_url,
      closed_by: nickname,
    })
  } catch (error: any) {
    // Re-throw AppExceptions
    if (error && typeof error === "object" && "getHttpStatus" in error) {
      throw error
    }

    logger.errorWithSentry(
      "Error closing PR",
      req,
      { repo: `${ownerStr}/${repoStr}`, prNumber },
      error,
    )
    throw internalServerError("Error closing PR")
  }
}
