import { jest, describe, it, expect, beforeEach, afterEach, beforeAll } from "@jest/globals"
import { Request, Response } from "express"

let closePr: typeof import("../close-pr").closePr
let setClosePrDependencies: typeof import("../close-pr").setClosePrDependencies
let resetClosePrDependencies: typeof import("../close-pr").resetClosePrDependencies

interface TestRequest extends Request {
  userId: string
}

describe("closePr", () => {
  beforeAll(async () => {
    process.env.KEY_VAULT_NAME = "mocked-keyvault-name"
    const mod = await import("../close-pr")
    closePr = mod.closePr
    setClosePrDependencies = mod.setClosePrDependencies
    resetClosePrDependencies = mod.resetClosePrDependencies
  })

  let mockReq: Partial<TestRequest>
  let mockRes: Partial<Response>
  let mockDependencies: any

  beforeEach(() => {
    mockRes = {
      status: jest.fn().mockReturnThis() as any,
      send: jest.fn() as any,
      json: jest.fn() as any,
    }

    mockDependencies = {
      userNickname: jest.fn(),
      getInstallationOctokitByOwner: jest.fn(),
      isUserCollaborator: jest.fn(),
      githubApp: {},
    }

    setClosePrDependencies(mockDependencies)
  })

  afterEach(() => {
    resetClosePrDependencies()
    jest.clearAllMocks()
  })

  describe("input validation", () => {
    it("should throw missingRequiredFields when owner is missing", async () => {
      mockReq = {
        body: { repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow(
        "Missing required fields",
      )
    })

    it("should throw validationFailure when owner is empty", async () => {
      mockReq = {
        body: { owner: "  ", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow(
        "owner and repo cannot be empty",
      )
    })

    it("should throw validationFailure when pr_number is invalid", async () => {
      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: "invalid" },
        userId: "test-user-id",
      }

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow(
        "pr_number must be a positive integer",
      )
    })
  })

  describe("authorization", () => {
    it("should throw unauthorized when user nickname is null", async () => {
      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }
      ;(mockDependencies.userNickname as jest.Mock<any>).mockResolvedValue(null)

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow()
    })

    it("should throw githubNotCollaborator when user is not a collaborator", async () => {
      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }
      ;(mockDependencies.userNickname as jest.Mock<any>).mockResolvedValue("test-user")
      ;(mockDependencies.getInstallationOctokitByOwner as jest.Mock<any>).mockResolvedValue({
        rest: { pulls: { get: jest.fn() } },
      })
      ;(mockDependencies.isUserCollaborator as jest.Mock<any>).mockResolvedValue(false)

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow(
        "not a collaborator",
      )
    })
  })

  describe("PR validation", () => {
    it("should throw forbidden when PR was not created by Codeflash", async () => {
      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }
      ;(mockDependencies.userNickname as jest.Mock<any>).mockResolvedValue("test-user")
      ;(mockDependencies.getInstallationOctokitByOwner as jest.Mock<any>).mockResolvedValue({
        rest: {
          pulls: {
            get: jest.fn<any>().mockResolvedValue({
              data: {
                number: 123,
                state: "open",
                html_url: "https://github.com/test-owner/test-repo/pull/123",
                user: { login: "some-other-user", type: "User" },
              },
            }),
          },
        },
      })
      ;(mockDependencies.isUserCollaborator as jest.Mock<any>).mockResolvedValue(true)

      await expect(closePr(mockReq as Request, mockRes as Response)).rejects.toThrow(
        "not created by Codeflash",
      )
    })
  })

  describe("successful operations", () => {
    it("should return success when PR is already closed", async () => {
      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }
      ;(mockDependencies.userNickname as jest.Mock<any>).mockResolvedValue("test-user")
      ;(mockDependencies.getInstallationOctokitByOwner as jest.Mock<any>).mockResolvedValue({
        rest: {
          pulls: {
            get: jest.fn<any>().mockResolvedValue({
              data: {
                number: 123,
                state: "closed",
                html_url: "https://github.com/test-owner/test-repo/pull/123",
                user: { login: "codeflash-ai[bot]", type: "Bot" },
              },
            }),
          },
        },
      })
      ;(mockDependencies.isUserCollaborator as jest.Mock<any>).mockResolvedValue(true)

      await closePr(mockReq as Request, mockRes as Response)

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        message: "PR #123 is already closed",
        pr_url: "https://github.com/test-owner/test-repo/pull/123",
        state: "closed",
      })
    })

    it("should successfully close a PR created by Codeflash", async () => {
      const mockUpdate = jest.fn<any>().mockResolvedValue({})
      const mockCreateComment = jest.fn<any>().mockResolvedValue({})

      mockReq = {
        body: { owner: "test-owner", repo: "test-repo", pr_number: 123 },
        userId: "test-user-id",
      }
      ;(mockDependencies.userNickname as jest.Mock<any>).mockResolvedValue("test-user")
      ;(mockDependencies.getInstallationOctokitByOwner as jest.Mock<any>).mockResolvedValue({
        rest: {
          pulls: {
            get: jest.fn<any>().mockResolvedValue({
              data: {
                number: 123,
                state: "open",
                html_url: "https://github.com/test-owner/test-repo/pull/123",
                user: { login: "codeflash-ai[bot]", type: "Bot" },
              },
            }),
            update: mockUpdate,
          },
          issues: {
            createComment: mockCreateComment,
          },
        },
      })
      ;(mockDependencies.isUserCollaborator as jest.Mock<any>).mockResolvedValue(true)

      await closePr(mockReq as Request, mockRes as Response)

      expect(mockUpdate).toHaveBeenCalledWith({
        owner: "test-owner",
        repo: "test-repo",
        pull_number: 123,
        state: "closed",
      })
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        message: "Successfully closed PR #123",
        pr_url: "https://github.com/test-owner/test-repo/pull/123",
        closed_by: "test-user",
      })
    })
  })
})