import { afterAll, describe, expect, it } from 'vitest'
import { prisma, createCaller } from '../setup'
import {
  createTestUser,
  createTestProgram,
  createTestProject,
  createTestCompetition,
  createTestRound,
  cleanupTestData,
  uid,
} from '../helpers'
import { mentorRouter } from '../../src/server/routers/mentor'
import type { UserRole } from '@prisma/client'

/**
 * The default `createTestUser` helper only populates the singular `role`
 * column — but `mentor.getCandidates` filters on the multi-role `roles[]`
 * array. Wrap creation here so test users land with the right shape.
 */
async function createUserWithRoles(
  primaryRole: UserRole,
  rolesArray: UserRole[],
  overrides: { expertiseTags?: string[]; maxAssignments?: number | null; country?: string | null } = {},
) {
  const id = uid('user')
  return prisma.user.create({
    data: {
      id,
      email: `${id}@test.local`,
      name: `Test ${primaryRole}`,
      role: primaryRole,
      roles: rolesArray,
      status: 'ACTIVE',
      expertiseTags: overrides.expertiseTags ?? [],
      maxAssignments: overrides.maxAssignments ?? null,
      country: overrides.country ?? null,
    },
  })
}

describe('mentor.getCandidates', () => {
  const programIds: string[] = []
  const userIds: string[] = []

  afterAll(async () => {
    for (const programId of programIds) {
      await prisma.mentorAssignment.deleteMany({ where: { project: { programId } } })
      await cleanupTestData(programId, [])
    }
    if (userIds.length > 0) {
      await prisma.user.deleteMany({ where: { id: { in: userIds } } })
    }
  })

  it('returns all MENTOR-role users sorted by expertise overlap, excluding non-mentors', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `getCandidates-${uid()}` })
    programIds.push(program.id)

    const project = await createTestProject(program.id, {
      title: 'Reef Monitor',
      description: 'Coral reef IoT sensor for ocean acidification.',
      tags: ['coral', 'iot', 'sensors'],
    })

    const mentorHigh = await createUserWithRoles('MENTOR', ['MENTOR'], {
      expertiseTags: ['coral', 'iot', 'marine biology'],
    })
    userIds.push(mentorHigh.id)

    const mentorLow = await createUserWithRoles('MENTOR', ['MENTOR'], {
      expertiseTags: ['marketing'],
    })
    userIds.push(mentorLow.id)

    const nonMentor = await createUserWithRoles('JURY_MEMBER', ['JURY_MEMBER'], {
      expertiseTags: ['coral', 'iot'],
    })
    userIds.push(nonMentor.id)

    const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.getCandidates({ projectId: project.id })

    const mentorIds = result.candidates.map((c: { id: string }) => c.id)
    expect(mentorIds).toContain(mentorHigh.id)
    expect(mentorIds).toContain(mentorLow.id)
    expect(mentorIds).not.toContain(nonMentor.id)

    const high = result.candidates.find((c: { id: string }) => c.id === mentorHigh.id)!
    const low = result.candidates.find((c: { id: string }) => c.id === mentorLow.id)!
    expect(high.overlapScore).toBeGreaterThan(low.overlapScore)

    // Default sort: highest overlap first
    const indexHigh = mentorIds.indexOf(mentorHigh.id)
    const indexLow = mentorIds.indexOf(mentorLow.id)
    expect(indexHigh).toBeLessThan(indexLow)
  })

  it('exposes load + capacity per candidate', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `getCandidates-load-${uid()}` })
    programIds.push(program.id)

    const project = await createTestProject(program.id, { title: 'P1', tags: ['x'] })
    const otherProject = await createTestProject(program.id, { title: 'P2', tags: ['x'] })

    const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], {
      expertiseTags: ['x'],
      maxAssignments: 3,
    })
    userIds.push(mentor.id)

    await prisma.mentorAssignment.create({
      data: {
        projectId: otherProject.id,
        mentorId: mentor.id,
        method: 'MANUAL',
        assignedBy: admin.id,
      },
    })

    const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.getCandidates({ projectId: project.id })
    const found = result.candidates.find((c: { id: string }) => c.id === mentor.id)!
    expect(found.currentAssignments).toBe(1)
    expect(found.maxAssignments).toBe(3)
  })
})

describe('mentor.autoAssignBulkForRound', () => {
  const programIds: string[] = []
  const userIds: string[] = []

  afterAll(async () => {
    for (const programId of programIds) {
      await prisma.mentorAssignment.deleteMany({ where: { project: { programId } } })
      await cleanupTestData(programId, [])
    }
    if (userIds.length > 0) {
      await prisma.user.deleteMany({ where: { id: { in: userIds } } })
    }
  })

  it('respects requested_only eligibility — skips projects without wantsMentorship=true', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `bulk-requested-${uid()}` })
    programIds.push(program.id)
    const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
    const round = await createTestRound(competition.id, {
      roundType: 'MENTORING',
      configJson: { eligibility: 'requested_only' },
    })

    const projWithRequest = await prisma.project.create({
      data: {
        id: uid('proj'),
        title: 'Wants Mentorship',
        programId: program.id,
        tags: ['x'],
        wantsMentorship: true,
      },
    })
    const projWithoutRequest = await prisma.project.create({
      data: {
        id: uid('proj'),
        title: 'No Request',
        programId: program.id,
        tags: ['x'],
        wantsMentorship: false,
      },
    })
    await prisma.projectRoundState.create({
      data: { projectId: projWithRequest.id, roundId: round.id, state: 'PENDING' },
    })
    await prisma.projectRoundState.create({
      data: { projectId: projWithoutRequest.id, roundId: round.id, state: 'PENDING' },
    })

    // Both projects must have CONFIRMED FinalistConfirmation to qualify for
    // mentor assignment — the wantsMentorship gate is what we're isolating here.
    await prisma.finalistConfirmation.createMany({
      data: [
        {
          projectId: projWithRequest.id,
          category: 'STARTUP',
          status: 'CONFIRMED',
          deadline: new Date(Date.now() + 86_400_000),
          token: `tok_${uid()}`,
          confirmedAt: new Date(),
        },
        {
          projectId: projWithoutRequest.id,
          category: 'STARTUP',
          status: 'CONFIRMED',
          deadline: new Date(Date.now() + 86_400_000),
          token: `tok_${uid()}`,
          confirmedAt: new Date(),
        },
      ],
    })

    const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
    userIds.push(mentor.id)

    const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.autoAssignBulkForRound({ roundId: round.id, useAI: false })

    expect(result.assigned).toBe(1)

    const requestedAssigned = await prisma.mentorAssignment.findFirst({
      where: { projectId: projWithRequest.id },
    })
    expect(requestedAssigned).not.toBeNull()

    const skippedNotAssigned = await prisma.mentorAssignment.findFirst({
      where: { projectId: projWithoutRequest.id },
    })
    expect(skippedNotAssigned).toBeNull()
  })

  it('skips projects already assigned (any method)', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `bulk-skip-${uid()}` })
    programIds.push(program.id)
    const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
    const round = await createTestRound(competition.id, {
      roundType: 'MENTORING',
      configJson: { eligibility: 'all_advancing' },
    })

    const existingMentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
    const otherMentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
    userIds.push(existingMentor.id, otherMentor.id)

    const projAlreadyAssigned = await prisma.project.create({
      data: { id: uid('proj'), title: 'Already', programId: program.id, tags: ['x'], wantsMentorship: true },
    })
    const projUnassigned = await prisma.project.create({
      data: { id: uid('proj'), title: 'Open', programId: program.id, tags: ['x'], wantsMentorship: false },
    })
    await prisma.projectRoundState.create({
      data: { projectId: projAlreadyAssigned.id, roundId: round.id, state: 'PENDING' },
    })
    await prisma.projectRoundState.create({
      data: { projectId: projUnassigned.id, roundId: round.id, state: 'PENDING' },
    })

    // Both projects must have CONFIRMED FinalistConfirmation to qualify for
    // mentor assignment — the existing-assignment gate is what we're isolating here.
    await prisma.finalistConfirmation.createMany({
      data: [
        {
          projectId: projAlreadyAssigned.id,
          category: 'STARTUP',
          status: 'CONFIRMED',
          deadline: new Date(Date.now() + 86_400_000),
          token: `tok_${uid()}`,
          confirmedAt: new Date(),
        },
        {
          projectId: projUnassigned.id,
          category: 'STARTUP',
          status: 'CONFIRMED',
          deadline: new Date(Date.now() + 86_400_000),
          token: `tok_${uid()}`,
          confirmedAt: new Date(),
        },
      ],
    })

    await prisma.mentorAssignment.create({
      data: {
        projectId: projAlreadyAssigned.id,
        mentorId: existingMentor.id,
        method: 'MANUAL',
        assignedBy: admin.id,
      },
    })

    const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.autoAssignBulkForRound({ roundId: round.id, useAI: false })

    expect(result.assigned).toBe(1)
    expect(result.skipped).toBe(1)

    const stillExisting = await prisma.mentorAssignment.findFirst({
      where: { projectId: projAlreadyAssigned.id },
    })
    expect(stillExisting?.mentorId).toBe(existingMentor.id) // unchanged
  })

  it('only assigns mentors to projects with CONFIRMED FinalistConfirmation', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `bulk-finalist-${uid()}` })
    programIds.push(program.id)
    const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
    const round = await createTestRound(competition.id, {
      roundType: 'MENTORING',
      configJson: { eligibility: 'all_advancing' },
    })

    const projConfirmed = await prisma.project.create({
      data: {
        id: uid('proj'),
        title: 'Confirmed Finalist',
        programId: program.id,
        tags: ['x'],
        wantsMentorship: true,
      },
    })
    const projPending = await prisma.project.create({
      data: {
        id: uid('proj'),
        title: 'Pending Finalist',
        programId: program.id,
        tags: ['x'],
        wantsMentorship: true,
      },
    })
    const projNoConfirmation = await prisma.project.create({
      data: {
        id: uid('proj'),
        title: 'No Confirmation',
        programId: program.id,
        tags: ['x'],
        wantsMentorship: true,
      },
    })

    await prisma.projectRoundState.createMany({
      data: [
        { projectId: projConfirmed.id, roundId: round.id, state: 'PENDING' },
        { projectId: projPending.id, roundId: round.id, state: 'PENDING' },
        { projectId: projNoConfirmation.id, roundId: round.id, state: 'PENDING' },
      ],
    })

    await prisma.finalistConfirmation.create({
      data: {
        projectId: projConfirmed.id,
        category: 'STARTUP',
        status: 'CONFIRMED',
        deadline: new Date(Date.now() + 86_400_000),
        token: `tok_${uid()}`,
        confirmedAt: new Date(),
      },
    })
    await prisma.finalistConfirmation.create({
      data: {
        projectId: projPending.id,
        category: 'STARTUP',
        status: 'PENDING',
        deadline: new Date(Date.now() + 86_400_000),
        token: `tok_${uid()}`,
      },
    })

    const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
    userIds.push(mentor.id)

    const caller = createCaller(mentorRouter, {
      id: admin.id,
      email: admin.email,
      role: 'SUPER_ADMIN',
    })
    const result = await caller.autoAssignBulkForRound({ roundId: round.id, useAI: false })

    expect(result.assigned).toBe(1)

    const confirmedAssigned = await prisma.mentorAssignment.findFirst({
      where: { projectId: projConfirmed.id },
    })
    expect(confirmedAssigned).not.toBeNull()

    const pendingAssigned = await prisma.mentorAssignment.findFirst({
      where: { projectId: projPending.id },
    })
    expect(pendingAssigned).toBeNull()

    const noConfAssigned = await prisma.mentorAssignment.findFirst({
      where: { projectId: projNoConfirmation.id },
    })
    expect(noConfAssigned).toBeNull()
  })

  it('refuses with admin_selected eligibility (admin must pick manually)', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)

    const program = await createTestProgram({ name: `bulk-admin-selected-${uid()}` })
    programIds.push(program.id)
    const competition = await createTestCompetition(program.id, { status: 'ACTIVE' })
    const round = await createTestRound(competition.id, {
      roundType: 'MENTORING',
      configJson: { eligibility: 'admin_selected' },
    })

    const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    await expect(
      caller.autoAssignBulkForRound({ roundId: round.id, useAI: false }),
    ).rejects.toThrow(/admin_selected|admin-selected|manually/i)
  })
})

describe('mentor.getSuggestions source flag', () => {
  const programIds: string[] = []
  const userIds: string[] = []

  afterAll(async () => {
    for (const programId of programIds) {
      await cleanupTestData(programId, [])
    }
    if (userIds.length > 0) {
      await prisma.user.deleteMany({ where: { id: { in: userIds } } })
    }
  })

  it('marks source=fallback when OPENAI_API_KEY is missing', async () => {
    const original = process.env.OPENAI_API_KEY
    delete process.env.OPENAI_API_KEY
    try {
      const admin = await createTestUser('SUPER_ADMIN')
      userIds.push(admin.id)

      const program = await createTestProgram({ name: `source-flag-${uid()}` })
      programIds.push(program.id)

      const project = await createTestProject(program.id, { title: 'Fallback test', tags: ['x'] })

      const mentor = await createUserWithRoles('MENTOR', ['MENTOR'], { expertiseTags: ['x'] })
      userIds.push(mentor.id)

      const caller = createCaller(mentorRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
      const result = await caller.getSuggestions({ projectId: project.id, limit: 5 })
      expect(result.source).toBe('fallback')
    } finally {
      if (original) process.env.OPENAI_API_KEY = original
    }
  })
})
