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

// Define the AuthorizedUserReq type locally
interface AuthorizedUserReq {
  body: any
  userId?: string
  organizationId?: string | null
  subscriptionInfo?: {
    userId: string
    tier: string
    used: number
    limit: number
  }
}

describe("createStagingReview (unit test)", () => {
  let req: AuthorizedUserReq
  let res: Response
  let createStagingReview: any
  let saveStagingReview: any
  let setCreateStagingDependencies: any
  let resetCreateStagingDependencies: any
  let mockSave: jest.MockedFunction<any>
  let StagingStorageStrategyFactory: any

  beforeEach(async () => {
    jest.resetModules()
    jest.clearAllMocks()

    // Create mock save function
    mockSave = jest.fn()

    // Import and mock the staging module
    const stagingModule = await import("../../staging/index")
    StagingStorageStrategyFactory = stagingModule.StagingStorageStrategyFactory

    // Spy on getStrategy to return our mock
    jest.spyOn(StagingStorageStrategyFactory, "getStrategy").mockReturnValue({
      save: mockSave,
      getStorageType: () => "plain_text",
    })

    // Import the module under test
    const module = await import("../create-staging")
    createStagingReview = module.default
    saveStagingReview = module.saveStagingReview
    setCreateStagingDependencies = module.setCreateStagingDependencies
    resetCreateStagingDependencies = module.resetCreateStagingDependencies

    // Mock getEffectivePrivacyMode
    setCreateStagingDependencies({
      getEffectivePrivacyMode: jest.fn(() => Promise.resolve(false)),
    })

    // Set up mock request and response
    req = {
      body: {
        prCommentFields: {
          function_name: "testFunction",
          file_path: "/path/to/file.js",
          speedup_x: "2.5x",
          speedup_pct: "150%",
        },
        traceId: "test-trace-id",
        baseBranch: "main",
        diffContents: { "test.js": { originalContent: "old", newContent: "new" } },
      },
      userId: "test-user-id",
      subscriptionInfo: {
        userId: "test-user-id",
        tier: "pro",
        used: 0,
        limit: 100,
      },
    }

    res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn().mockReturnThis(),
    } as any as Response
  })

  afterEach(() => {
    if (resetCreateStagingDependencies) {
      resetCreateStagingDependencies()
    }
  })

  describe("validation", () => {
    it("should return 400 when prCommentFields is missing", async () => {
      req.body = { traceId: "test-trace-id" }

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(400)
      expect(res.json).toHaveBeenCalledWith({ error: "Missing prCommentFields in request body" })
    })

    it("should return 400 when function_name is missing", async () => {
      req.body.prCommentFields.function_name = undefined

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(400)
      expect(res.json).toHaveBeenCalledWith({
        error: "Missing required fields: function_name, speedup_x, and traceId",
      })
    })

    it("should return 400 when speedup_x is missing", async () => {
      req.body.prCommentFields.speedup_x = undefined

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(400)
      expect(res.json).toHaveBeenCalledWith({
        error: "Missing required fields: function_name, speedup_x, and traceId",
      })
    })

    it("should return 400 when traceId is missing", async () => {
      req.body.traceId = undefined

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(400)
      expect(res.json).toHaveBeenCalledWith({
        error: "Missing required fields: function_name, speedup_x, and traceId",
      })
    })
  })

  describe("strategy selection", () => {
    it("should use pro tier when subscriptionInfo is provided", async () => {
      mockSave.mockResolvedValue({ success: true, storageType: "plain_text" })

      await createStagingReview(req, res)

      expect(StagingStorageStrategyFactory.getStrategy).toHaveBeenCalledWith(
        expect.objectContaining({
          tier: "pro",
          hasGitRepo: false,
        }),
      )
    })

    it("should use free tier when subscriptionInfo is undefined", async () => {
      req.subscriptionInfo = undefined
      mockSave.mockResolvedValue({ success: true, storageType: "plain_text" })

      await createStagingReview(req, res)

      expect(StagingStorageStrategyFactory.getStrategy).toHaveBeenCalledWith(
        expect.objectContaining({
          tier: "free",
        }),
      )
    })

    it("should set hasGitRepo to true when owner and repo are provided", async () => {
      req.body.owner = "test-owner"
      req.body.repo = "test-repo"
      mockSave.mockResolvedValue({ success: true, storageType: "git_branch" })

      await createStagingReview(req, res)

      expect(StagingStorageStrategyFactory.getStrategy).toHaveBeenCalledWith(
        expect.objectContaining({
          hasGitRepo: true,
          owner: "test-owner",
          repo: "test-repo",
        }),
      )
    })
  })

  describe("strategy execution", () => {
    it("should return 200 when strategy save succeeds", async () => {
      mockSave.mockResolvedValue({ success: true, storageType: "plain_text" })

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(200)
      expect(res.json).toHaveBeenCalledWith({
        message: "staging created successfully",
        storageType: "plain_text",
      })
    })

    it("should include branchName in response when provided by strategy", async () => {
      mockSave.mockResolvedValue({
        success: true,
        storageType: "git_branch",
        branchName: "codeflash-staging/test-branch",
      })

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(200)
      expect(res.json).toHaveBeenCalledWith({
        message: "staging created successfully",
        storageType: "git_branch",
        branchName: "codeflash-staging/test-branch",
      })
    })

    it("should return 404 when strategy returns not found error", async () => {
      mockSave.mockResolvedValue({
        success: false,
        storageType: "plain_text",
        error: "No optimization event found for the given traceId",
      })

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(404)
      expect(res.json).toHaveBeenCalledWith({
        error: "No optimization event found for the given traceId",
      })
    })

    it("should return 400 when strategy returns invalid error", async () => {
      mockSave.mockResolvedValue({
        success: false,
        storageType: "plain_text",
        error: "Invalid speedup_x value",
      })

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(400)
      expect(res.json).toHaveBeenCalledWith({
        error: "Invalid speedup_x value",
      })
    })

    it("should return 500 for other errors", async () => {
      mockSave.mockResolvedValue({
        success: false,
        storageType: "plain_text",
        error: "Database connection failed",
      })

      await createStagingReview(req, res)

      expect(res.status).toHaveBeenCalledWith(500)
      expect(res.json).toHaveBeenCalledWith({
        error: "Database connection failed",
      })
    })
  })

  describe("saveStagingReview function", () => {
    it("should pass correct context to strategy", async () => {
      mockSave.mockResolvedValue({ success: true, storageType: "plain_text" })

      await saveStagingReview(req.body, "test-user-id", null, req.subscriptionInfo)

      expect(mockSave).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: "test-user-id",
          traceId: "test-trace-id",
          baseBranch: "main",
          prCommentFields: req.body.prCommentFields,
        }),
      )
    })

    it("should convert diffContents object to Map", async () => {
      mockSave.mockResolvedValue({ success: true, storageType: "plain_text" })

      await saveStagingReview(req.body, "test-user-id", null, req.subscriptionInfo)

      expect(mockSave).toHaveBeenCalledWith(
        expect.objectContaining({
          diffContents: expect.any(Map),
        }),
      )
    })
  })
})
