/**
 * Integration tests for admin proxy-evaluation flow.
 *
 * Exercises the three new admin procedures in src/server/routers/evaluation.ts
 * (adminStart, adminAutosave, adminSubmitOnBehalf) and getJurorAssignmentsForRound.
 *
 * These tests require a reachable Postgres matching DATABASE_URL_TEST
 * (or DATABASE_URL). Run with:
 *   npx vitest run tests/unit/admin-proxy-evaluation.test.ts
 */

import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { TRPCError } from '@trpc/server'
import { evaluationRouter } from '@/server/routers/evaluation'
import { prisma, createCaller } from '../setup'
import {
  createTestUser,
  createTestProgram,
  createTestCompetition,
  createTestRound,
  createTestProject,
  createTestAssignment,
  createTestEvaluationForm,
  createTestCOI,
  cleanupTestData,
} from '../helpers'

describe('admin proxy evaluation', () => {
  let programId: string
  let adminId: string
  let jurorId: string
  let otherJurorId: string
  let projectId: string
  let projectWithCoiId: string
  let roundId: string
  let assignmentId: string
  let coiAssignmentId: string
  let formId: string
  const createdUserIds: string[] = []

  beforeAll(async () => {
    const program = await createTestProgram()
    programId = program.id

    const admin = await createTestUser('PROGRAM_ADMIN', { email: `admin-${Date.now()}@test.local` })
    adminId = admin.id
    createdUserIds.push(adminId)

    const juror = await createTestUser('JURY_MEMBER', { email: `juror-${Date.now()}@test.local` })
    jurorId = juror.id
    createdUserIds.push(jurorId)

    const otherJuror = await createTestUser('JURY_MEMBER', { email: `other-${Date.now()}@test.local` })
    otherJurorId = otherJuror.id
    createdUserIds.push(otherJurorId)

    const competition = await createTestCompetition(programId)

    // Round with CLOSED voting window to prove bypass works
    const round = await createTestRound(competition.id, {
      status: 'ROUND_ACTIVE',
      windowOpenAt: new Date(Date.now() - 7 * 86_400_000), // 7 days ago
      windowCloseAt: new Date(Date.now() - 1 * 86_400_000), // closed 1 day ago
      configJson: {
        scoringMode: 'criteria',
        requireFeedback: true,
        feedbackMinLength: 10,
      },
    })
    roundId = round.id

    const project = await createTestProject(programId, {
      title: 'Proxy Test Project',
      competitionCategory: 'STARTUP',
    })
    projectId = project.id

    const projectWithCoi = await createTestProject(programId, {
      title: 'COI Test Project',
      competitionCategory: 'STARTUP',
    })
    projectWithCoiId = projectWithCoi.id

    const assignment = await createTestAssignment(jurorId, projectId, roundId)
    assignmentId = assignment.id

    const coiAssignment = await createTestAssignment(jurorId, projectWithCoiId, roundId)
    coiAssignmentId = coiAssignment.id

    await createTestCOI(coiAssignmentId, jurorId, projectWithCoiId, true)

    const form = await createTestEvaluationForm(roundId, [
      { id: 'c1', label: 'Innovation', scale: '1-10', weight: 1 },
      { id: 'c2', label: 'Impact', scale: '1-10', weight: 1 },
    ])
    formId = form.id
  })

  afterAll(async () => {
    await cleanupTestData(programId, createdUserIds)
  })

  it('rejects non-admin callers on adminSubmitOnBehalf', async () => {
    const juror = await prisma.user.findUniqueOrThrow({ where: { id: jurorId } })
    const caller = createCaller(evaluationRouter, juror)

    await expect(
      caller.adminSubmitOnBehalf({
        id: 'any-id',
        criterionScoresJson: { c1: 8, c2: 7 },
        feedbackText: 'some feedback for this test',
      }),
    ).rejects.toThrow(TRPCError)
  })

  it('rejects non-admin callers on adminStart', async () => {
    const juror = await prisma.user.findUniqueOrThrow({ where: { id: jurorId } })
    const caller = createCaller(evaluationRouter, juror)

    await expect(caller.adminStart({ assignmentId })).rejects.toThrow(TRPCError)
  })

  it('lists juror assignments for admin including COI flag', async () => {
    const admin = await prisma.user.findUniqueOrThrow({ where: { id: adminId } })
    const caller = createCaller(evaluationRouter, admin)

    const result = await caller.getJurorAssignmentsForRound({ roundId, userId: jurorId })

    expect(result.juror.id).toBe(jurorId)
    expect(result.round.id).toBe(roundId)
    expect(result.assignments).toHaveLength(2)

    const coiAssignment = result.assignments.find(
      (a: (typeof result.assignments)[number]) => a.project.id === projectWithCoiId,
    )
    expect(coiAssignment?.conflictOfInterest?.hasConflict).toBe(true)
  })

  it('admin can create + autosave + submit an evaluation outside the voting window', async () => {
    const admin = await prisma.user.findUniqueOrThrow({ where: { id: adminId } })
    const caller = createCaller(evaluationRouter, admin)

    // Start
    const evaluation = await caller.adminStart({ assignmentId })
    expect(evaluation.status).toBe('DRAFT')
    expect(evaluation.assignmentId).toBe(assignmentId)
    expect(evaluation.formId).toBe(formId)

    // Autosave
    const draft = await caller.adminAutosave({
      id: evaluation.id,
      criterionScoresJson: { c1: 6 },
      feedbackText: 'partial feedback draft',
    })
    expect(draft.status).toBe('DRAFT')
    expect((draft.criterionScoresJson as Record<string, number>).c1).toBe(6)

    // Submit — window is closed, should bypass successfully
    const submitted = await caller.adminSubmitOnBehalf({
      id: evaluation.id,
      criterionScoresJson: { c1: 8, c2: 9 },
      globalScore: 9,
      feedbackText: 'Comprehensive feedback from the admin proxy',
    })

    expect(submitted.status).toBe('SUBMITTED')
    expect(submitted.submittedAt).not.toBeNull()

    // Assignment marked complete
    const updatedAssignment = await prisma.assignment.findUniqueOrThrow({
      where: { id: assignmentId },
    })
    expect(updatedAssignment.isCompleted).toBe(true)

    // Audit entry captures both IDs + bypassedWindow flag
    const audit = await prisma.auditLog.findFirst({
      where: {
        action: 'ADMIN_PROXY_EVAL_SUBMITTED',
        entityId: evaluation.id,
      },
      orderBy: { timestamp: 'desc' },
    })
    expect(audit).not.toBeNull()
    expect(audit?.userId).toBe(adminId)

    const details = audit?.detailsJson as Record<string, unknown>
    expect(details.adminUserId).toBe(adminId)
    expect(details.onBehalfOfUserId).toBe(jurorId)
    expect(details.assignmentId).toBe(assignmentId)
    expect(details.bypassedWindow).toBe(true)
  })

  it('admin cannot submit for a juror who declared COI', async () => {
    const admin = await prisma.user.findUniqueOrThrow({ where: { id: adminId } })
    const caller = createCaller(evaluationRouter, admin)

    // Start is permitted (admin may still want to inspect), but submit must block
    const evaluation = await caller.adminStart({ assignmentId: coiAssignmentId })

    await expect(
      caller.adminSubmitOnBehalf({
        id: evaluation.id,
        criterionScoresJson: { c1: 5, c2: 5 },
        feedbackText: 'should not be accepted because of conflict',
      }),
    ).rejects.toThrowError(/conflict of interest/i)
  })

  it('enforces feedback minimum length on admin submit', async () => {
    const admin = await prisma.user.findUniqueOrThrow({ where: { id: adminId } })
    const caller = createCaller(evaluationRouter, admin)

    // Fresh assignment for a clean run
    const anotherProject = await createTestProject(programId, {
      title: 'Validation Test',
      competitionCategory: 'STARTUP',
    })
    const anotherAssignment = await createTestAssignment(otherJurorId, anotherProject.id, roundId)

    const evaluation = await caller.adminStart({ assignmentId: anotherAssignment.id })

    await expect(
      caller.adminSubmitOnBehalf({
        id: evaluation.id,
        criterionScoresJson: { c1: 7, c2: 8 },
        feedbackText: 'too short', // under 10 char minimum
      }),
    ).rejects.toThrowError(/at least/i)
  })

  it('adminAutosave refuses to overwrite a SUBMITTED evaluation', async () => {
    const admin = await prisma.user.findUniqueOrThrow({ where: { id: adminId } })
    const caller = createCaller(evaluationRouter, admin)

    // The first test already submitted the main assignment's evaluation
    const submittedEval = await prisma.evaluation.findUniqueOrThrow({
      where: { assignmentId },
    })

    await expect(
      caller.adminAutosave({
        id: submittedEval.id,
        criterionScoresJson: { c1: 1 },
      }),
    ).rejects.toThrowError(/submitted/i)
  })
})
