import { describe, it, expect, afterAll, vi } from 'vitest'

vi.mock('@/lib/minio', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@/lib/minio')>()
  return { ...actual, getPresignedUrl: vi.fn(async () => 'https://example.test/presigned') }
})

import { prisma } from '../setup'
import {
  createTestProgram,
  createTestCompetition,
  createTestRound,
  createTestProject,
  createTestProjectRoundState,
  createTestUser,
  cleanupTestData,
  uid,
} from '../helpers'
import { listFinalistDocumentsForReview, listReviewVisibilityOptions } from '@/server/services/final-documents'

const programIds: string[] = []
afterAll(async () => {
  for (const id of programIds) await cleanupTestData(id)
})

/**
 * One finalist team with 4 files:
 *  - Business Plan (prior SUBMISSION round, via requirement reqBP)
 *  - Pitch Deck   (prior SUBMISSION round, via requirement reqDeck)
 *  - loose.pdf    (prior SUBMISSION round, NO requirement)
 *  - final.mp4    (uploaded directly to the LIVE_FINAL round, via reqFinal)
 */
async function setupCuration() {
  const program = await createTestProgram()
  programIds.push(program.id)
  const comp = await createTestCompetition(program.id, { status: 'ACTIVE' })
  const priorRound = await createTestRound(comp.id, { roundType: 'SUBMISSION', status: 'ROUND_CLOSED', sortOrder: 2 })
  const reqBP = await prisma.fileRequirement.create({
    data: { id: uid('req'), roundId: priorRound.id, name: 'Business Plan', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 1 },
  })
  const reqDeck = await prisma.fileRequirement.create({
    data: { id: uid('req'), roundId: priorRound.id, name: 'Pitch Deck', acceptedMimeTypes: ['application/pdf'], isRequired: true, sortOrder: 2 },
  })
  const finale = await createTestRound(comp.id, {
    roundType: 'LIVE_FINAL', status: 'ROUND_ACTIVE', sortOrder: 6,
    windowCloseAt: new Date(Date.now() + 86_400_000),
    configJson: { allowFinalistRevisedUploads: true },
  })
  const reqFinal = await prisma.fileRequirement.create({
    data: { id: uid('req'), roundId: finale.id, name: '1-minute Video', acceptedMimeTypes: ['video/*'], isRequired: false, sortOrder: 1 },
  })
  const project = await createTestProject(program.id)
  await createTestProjectRoundState(project.id, finale.id)

  const mkFile = (roundId: string, requirementId: string | null, fileName: string) =>
    prisma.projectFile.create({
      data: {
        id: uid('file'), projectId: project.id, roundId, requirementId,
        fileType: 'SUPPORTING_DOC', fileName, mimeType: 'application/pdf', size: 10,
        bucket: 'b', objectKey: uid('key'),
      },
    })
  await mkFile(priorRound.id, reqBP.id, 'bp.pdf')
  await mkFile(priorRound.id, reqDeck.id, 'deck.pdf')
  await mkFile(priorRound.id, null, 'loose.pdf')
  await mkFile(finale.id, reqFinal.id, 'final.mp4')

  return { program, priorRound, finale, reqBP, reqDeck, reqFinal, project }
}

async function setSelection(roundId: string, ids: string[] | null) {
  const round = await prisma.round.findUniqueOrThrow({ where: { id: roundId }, select: { configJson: true } })
  const cfg = (round.configJson ?? {}) as Record<string, unknown>
  if (ids === null) delete cfg.reviewVisibleRequirementIds
  else cfg.reviewVisibleRequirementIds = ids
  await prisma.round.update({ where: { id: roundId }, data: { configJson: cfg as object } })
}

describe('listFinalistDocumentsForReview curation', () => {
  it('no selection key → all files visible (current behavior)', async () => {
    const { program } = await setupCuration()
    const result = await listFinalistDocumentsForReview(prisma, program.id)
    expect(result.teams).toHaveLength(1)
    expect(result.teams[0].files).toHaveLength(4)
  })

  it('selection → only matching prior files, finale uploads always visible', async () => {
    const { program, finale, reqBP } = await setupCuration()
    await setSelection(finale.id, [reqBP.id])
    const result = await listFinalistDocumentsForReview(prisma, program.id)
    const names = result.teams[0].files.map((f) => f.fileName).sort()
    expect(names).toEqual(['bp.pdf', 'final.mp4']) // deck.pdf and loose.pdf hidden
  })

  it('empty selection → only finale uploads visible', async () => {
    const { program, finale } = await setupCuration()
    await setSelection(finale.id, [])
    const result = await listFinalistDocumentsForReview(prisma, program.id)
    expect(result.teams[0].files.map((f) => f.fileName)).toEqual(['final.mp4'])
  })

  it('prior file without a requirement is excluded under any selection', async () => {
    const { program, finale, reqBP, reqDeck } = await setupCuration()
    await setSelection(finale.id, [reqBP.id, reqDeck.id])
    const result = await listFinalistDocumentsForReview(prisma, program.id)
    expect(result.teams[0].files.map((f) => f.fileName)).not.toContain('loose.pdf')
  })
})

describe('listReviewVisibilityOptions', () => {
  it('lists distinct prior-round slots with counts; excludes finale-round slots and requirement-less files', async () => {
    const { program, reqBP, reqDeck } = await setupCuration()
    const options = await listReviewVisibilityOptions(prisma, program.id)
    expect(options.map((o) => o.requirementId).sort()).toEqual([reqBP.id, reqDeck.id].sort())
    const bp = options.find((o) => o.requirementId === reqBP.id)!
    expect(bp.name).toBe('Business Plan')
    expect(bp.fileCount).toBe(1)
    expect(bp.roundName).toBeTruthy()
  })

  it('returns [] when there is no open finale round', async () => {
    const program = await createTestProgram()
    programIds.push(program.id)
    expect(await listReviewVisibilityOptions(prisma, program.id)).toEqual([])
  })
})

describe('finalist review-doc settings procedures', () => {
  const userIds: string[] = []
  afterAll(async () => {
    for (const id of programIds) await cleanupTestData(id, userIds)
  })

  it('round-trips a selection and preserves sibling configJson keys', async () => {
    const { program, finale, reqBP } = await setupCuration()
    const admin = await createTestUser('PROGRAM_ADMIN')
    userIds.push(admin.id)
    const { finalistRouter } = await import('@/server/routers/finalist')
    const { createCaller } = await import('../setup')
    const caller = createCaller(finalistRouter, admin)

    const initial = await caller.getReviewDocSettings({ programId: program.id, roundId: finale.id })
    expect(initial.selectedIds).toBeNull()
    expect(initial.options.length).toBe(2)

    await caller.setReviewVisibleRequirements({ roundId: finale.id, requirementIds: [reqBP.id] })
    const curated = await caller.getReviewDocSettings({ programId: program.id, roundId: finale.id })
    expect(curated.selectedIds).toEqual([reqBP.id])

    // sibling key from setupCuration must survive
    const round = await prisma.round.findUniqueOrThrow({ where: { id: finale.id }, select: { configJson: true } })
    expect((round.configJson as Record<string, unknown>).allowFinalistRevisedUploads).toBe(true)

    await caller.setReviewVisibleRequirements({ roundId: finale.id, requirementIds: null })
    const cleared = await caller.getReviewDocSettings({ programId: program.id, roundId: finale.id })
    expect(cleared.selectedIds).toBeNull()
  })

  it('rejects a non-LIVE_FINAL round', async () => {
    const { program, priorRound } = await setupCuration()
    const admin = await createTestUser('PROGRAM_ADMIN')
    userIds.push(admin.id)
    const { finalistRouter } = await import('@/server/routers/finalist')
    const { createCaller } = await import('../setup')
    const caller = createCaller(finalistRouter, admin)
    await expect(
      caller.setReviewVisibleRequirements({ roundId: priorRound.id, requirementIds: [] }),
    ).rejects.toThrow()
    void program
  })
})
