import { describe, it, expect, vi, beforeEach } from "vitest"
import { prisma } from "@codeflash-ai/common"
import { getRepositoriesForAccountCached } from "@/lib/services/repository-utils"

vi.mock("@/lib/server-action-timing", () => ({
  withTiming: vi.fn((_name: string, fn: Function) => fn),
}))

vi.mock("@/lib/services/repository-utils", () => ({
  getRepositoriesForAccountCached: vi.fn(),
}))

// Use realistic test fixtures: valid UUIDs and Auth0-style user IDs
const mockOrgPayload = { orgId: "org-a1b2c3d4-e5f6-7890" }
const mockPersonalPayload = { userId: "github|12345", username: "testuser" }
const mockRepoIds = ["a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f678-9012-bcde-f12345678901"]

const mockEvents = [
  {
    id: "e1f2g3h4-i5j6-7890-cdef-123456789012",
    trace_id: "trace-1",
    function_name: "calculate",
    file_path: "src/utils.py",
    repository_id: mockRepoIds[0],
    status: "approved",
    is_staging: true,
    created_at: new Date("2024-06-01"),
    repository: { id: mockRepoIds[0], full_name: "org/repo", name: "repo" },
  },
  {
    id: "f2g3h4i5-j678-9012-defg-234567890123",
    trace_id: "trace-2",
    function_name: "process",
    file_path: "src/main.py",
    repository_id: mockRepoIds[1],
    status: "pending",
    is_staging: true,
    created_at: new Date("2024-06-02"),
    repository: { id: mockRepoIds[1], full_name: "org/repo2", name: "repo2" },
  },
]

const mockFeatures = [
  {
    trace_id: "trace-1",
    review_quality: "high",
    review_explanation: "Great optimization",
  },
]

/** Helper: extract SQL pattern from a $queryRaw tagged template mock call */
function getTaggedSql(mockFn: any, callIndex: number): string {
  const args = mockFn.mock.calls[callIndex]
  const strings = args[0] as string[]
  return strings.join("$?")
}

describe("getAllOptimizationEvents", () => {
  let getAllOptimizationEvents: typeof import("../action").getAllOptimizationEvents

  beforeEach(async () => {
    vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
      repoIds: mockRepoIds,
      repos: [],
    } as any)

    // $queryRaw is used as a tagged template literal — auto-mock doesn't create it
    ;(prisma as any).$queryRaw = vi.fn()

    const mod = await import("../action")
    getAllOptimizationEvents = mod.getAllOptimizationEvents
  })

  describe("Path B: standard Prisma query (org account)", () => {
    // Org accounts use Prisma findMany/count (not raw SQL) when not sorting by review_quality
    it("calls findMany and count in parallel", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue(mockEvents as any)
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(2)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      await getAllOptimizationEvents({ payload: mockOrgPayload as any })

      expect(prisma.optimization_events.findMany).toHaveBeenCalledTimes(1)
      expect(prisma.optimization_events.count).toHaveBeenCalledTimes(1)
    })

    it("batch-fetches optimization_features by trace_id array (not N+1)", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue(mockEvents as any)
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(2)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue(mockFeatures as any)

      await getAllOptimizationEvents({ payload: mockOrgPayload as any })

      // Single batch query with all trace IDs — NOT one per event
      expect(prisma.optimization_features.findMany).toHaveBeenCalledTimes(1)
      expect(prisma.optimization_features.findMany).toHaveBeenCalledWith({
        where: { trace_id: { in: ["trace-1", "trace-2"] } },
        select: {
          trace_id: true,
          review_quality: true,
          review_explanation: true,
        },
      })
    })

    it("merges review_quality into events", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue(mockEvents as any)
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(2)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue(mockFeatures as any)

      const result = await getAllOptimizationEvents({ payload: mockOrgPayload as any })

      expect((result.events[0] as any).review_quality).toBe("high")
      expect((result.events[0] as any).review_explanation).toBe("Great optimization")
      expect((result.events[1] as any).review_quality).toBeNull()
    })

    it("returns totalCount from count query", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue([])
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(42)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      const result = await getAllOptimizationEvents({ payload: mockOrgPayload as any })
      expect(result.totalCount).toBe(42)
    })

    it("applies pagination with skip and take", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue([])
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(0)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        page: 3,
        pageSize: 25,
      })

      expect(prisma.optimization_events.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          skip: 50, // (3 - 1) * 25
          take: 25,
        }),
      )
    })

    it("uses default sort (created_at desc) when no sort provided", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue([])
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(0)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      await getAllOptimizationEvents({ payload: mockOrgPayload as any })

      expect(prisma.optimization_events.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          orderBy: { created_at: "desc" },
        }),
      )
    })

    it("applies search filter", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue([])
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(0)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        search: "calc",
      })

      // Check findMany was called with a search-containing where clause
      const call = vi.mocked(prisma.optimization_events.findMany).mock.calls[0][0] as any
      // Should have AND with OR containing the search fields
      expect(call.where.AND).toBeDefined()
      const orClause = call.where.AND.find((c: any) => c.OR)
      expect(orClause).toBeDefined()
      expect(orClause.OR).toHaveLength(3) // function_name, file_path, repository.full_name
    })

    it("applies repository_id filter", async () => {
      vi.mocked(prisma.optimization_events.findMany).mockResolvedValue([])
      vi.mocked(prisma.optimization_events.count).mockResolvedValue(0)
      vi.mocked(prisma.optimization_features.findMany).mockResolvedValue([])

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        filter: { repository_id: mockRepoIds[0] },
      })

      const call = vi.mocked(prisma.optimization_events.findMany).mock.calls[0][0] as any
      // The repository_id filter should be in the AND clause
      const repoFilter = call.where.AND.find((c: any) => c.repository_id !== undefined)
      expect(repoFilter).toBeDefined()
      expect(repoFilter.repository_id).toBe(mockRepoIds[0])
    })
  })

  describe("Path A: raw SQL query (review_quality sort/filter)", () => {
    it("triggers when sort includes review_quality", async () => {
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce([]) // events
        .mockResolvedValueOnce([{ count: BigInt(0) }]) // count

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        sort: { review_quality: "desc" },
      })

      expect((prisma as any).$queryRaw).toHaveBeenCalledTimes(2)
      // Should NOT use standard Prisma findMany
      expect(prisma.optimization_events.findMany).not.toHaveBeenCalled()
    })

    it("triggers when filter includes review_quality", async () => {
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([{ count: BigInt(0) }])

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        filter: { review_quality: "high" },
      })

      expect((prisma as any).$queryRaw).toHaveBeenCalledTimes(2)
    })

    it("returns correct totalCount from BigInt conversion", async () => {
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([{ count: BigInt(99) }])

      const result = await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        sort: { review_quality: "asc" },
      })

      expect(result.totalCount).toBe(99)
    })

    it("maps JOIN results to include repository object", async () => {
      const rawEvents = [
        {
          id: "evt-1",
          trace_id: "trace-1",
          review_quality: "high",
          review_explanation: "Good",
          repo_full_name: "org/repo",
          repo_name: "repo",
          repo_id: mockRepoIds[0],
        },
      ]
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce(rawEvents)
        .mockResolvedValueOnce([{ count: BigInt(1) }])

      const result = await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        sort: { review_quality: "desc" },
      })

      expect((result.events[0] as any).repository).toEqual({
        id: mockRepoIds[0],
        full_name: "org/repo",
        name: "repo",
      })
    })

    it("sets repository to null when repo_id is missing", async () => {
      const rawEvents = [
        {
          id: "evt-1",
          trace_id: "trace-1",
          review_quality: null,
          review_explanation: null,
          repo_full_name: null,
          repo_name: null,
          repo_id: null,
        },
      ]
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce(rawEvents)
        .mockResolvedValueOnce([{ count: BigInt(1) }])

      const result = await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        sort: { review_quality: "desc" },
      })

      expect((result.events[0] as any).repository).toBeNull()
    })

    it("includes LEFT JOIN in raw SQL queries", async () => {
      ;(prisma as any).$queryRaw
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce([{ count: BigInt(0) }])

      await getAllOptimizationEvents({
        payload: mockOrgPayload as any,
        sort: { review_quality: "desc" },
      })

      // $queryRaw is a tagged template — first arg is TemplateStringsArray
      const sql = getTaggedSql((prisma as any).$queryRaw, 0)
      expect(sql).toContain("LEFT JOIN optimization_features")
      expect(sql).toContain("LEFT JOIN repositories")
    })
  })

  describe("edge cases", () => {
    it("handles empty repoIds", async () => {
      vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
        repoIds: [],
        repos: [],
      } as any)

      const result = await getAllOptimizationEvents({ payload: mockPersonalPayload as any })
      expect(result.events).toEqual([])
      expect(result.totalCount).toBe(0)
    })
  })
})
