/**
 * Regression: mentor-assignment emails must be deferred while the
 * project's MENTORING round is still ROUND_DRAFT. The earlier fix only
 * deferred the explicit `sendMentorBulkAssignmentEmail` path; the parallel
 * in-app-notification → email path (MENTEE_ASSIGNED, MENTOR_ASSIGNED) kept
 * firing immediately, causing duplicate sends both at assign-time AND
 * again when activateRound coalesced the same assignments. Verified
 * against prod incident 2026-05-26 (Camille Lopez received 9 emails).
 *
 * These tests assert that:
 *   - in DRAFT: in-app notifications still create rows, but the styled
 *     notification email is NOT sent;
 *   - in ACTIVE: the styled notification email IS sent (legacy behaviour
 *     preserved when the round is open).
 */

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

vi.mock('@/lib/email', async () => {
  const actual = await vi.importActual<typeof import('@/lib/email')>('@/lib/email')
  return {
    ...actual,
    sendStyledNotificationEmail: vi.fn(async () => undefined),
    sendMentorTeamAssignmentEmail: vi.fn(async () => undefined),
    sendMentorBulkAssignmentEmail: vi.fn(async () => undefined),
    sendTeamMentorIntroductionEmail: vi.fn(async () => undefined),
  }
})

const email = await import('@/lib/email')
const sendStyledMock = email.sendStyledNotificationEmail as ReturnType<typeof vi.fn>
const sendMentorBulkMock = email.sendMentorBulkAssignmentEmail as ReturnType<typeof vi.fn>
const sendTeamIntroMock = email.sendTeamMentorIntroductionEmail as ReturnType<typeof vi.fn>

async function makeMentor(): Promise<{ id: string; email: string }> {
  const id = uid('mentor')
  const u = await prisma.user.create({
    data: {
      id,
      email: `${id}@test.local`,
      name: `Mentor ${id}`,
      role: 'MENTOR' as UserRole,
      roles: ['MENTOR'] as UserRole[],
      status: 'ACTIVE',
      // Email path requires the user to opt into emails. Default for new test
      // users is EMAIL so styled-email sends fire when the gate is open.
      notificationPreference: 'EMAIL',
    },
  })
  return { id: u.id, email: u.email }
}

async function makeTeamMember(projectId: string): Promise<string> {
  const id = uid('teamuser')
  const u = await prisma.user.create({
    data: {
      id,
      email: `${id}@test.local`,
      name: `Team ${id}`,
      role: 'APPLICANT' as UserRole,
      roles: ['APPLICANT'] as UserRole[],
      status: 'ACTIVE',
      notificationPreference: 'EMAIL',
    },
  })
  await prisma.teamMember.create({
    data: { projectId, userId: u.id, role: 'MEMBER' },
  })
  return u.id
}

async function attachToMentoringRound(
  programId: string,
  projectId: string,
  status: 'ROUND_DRAFT' | 'ROUND_ACTIVE',
): Promise<string> {
  const slug = uid()
  const competition = await prisma.competition.create({
    data: {
      name: `Comp ${slug}`,
      slug: `comp-${slug}`,
      programId,
      status: 'ACTIVE',
    },
  })
  const round = await prisma.round.create({
    data: {
      name: `Mentoring ${slug}`,
      slug: `mentoring-${slug}`,
      roundType: 'MENTORING',
      sortOrder: 1,
      status,
      competitionId: competition.id,
    },
  })
  await prisma.projectRoundState.create({
    data: { roundId: round.id, projectId },
  })
  return round.id
}

describe('mentor-assignment email deferral (regression for 2026-05-26 duplicate-email incident)', () => {
  const programIds: string[] = []
  const userIds: string[] = []

  beforeEach(() => {
    sendStyledMock.mockClear()
    sendMentorBulkMock.mockClear()
    sendTeamIntroMock.mockClear()
  })

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

  it('mentor.assign in DRAFT round creates in-app notif rows but sends ZERO emails', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)
    const program = await createTestProgram({ name: `defer-draft-${uid()}` })
    programIds.push(program.id)
    const project = await createTestProject(program.id, { title: 'Draft Project' })
    await attachToMentoringRound(program.id, project.id, 'ROUND_DRAFT')

    const mentor = await makeMentor()
    userIds.push(mentor.id)
    const teamUser = await makeTeamMember(project.id)
    userIds.push(teamUser)

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

    expect(sendStyledMock).not.toHaveBeenCalled()
    expect(sendMentorBulkMock).not.toHaveBeenCalled()
    expect(sendTeamIntroMock).not.toHaveBeenCalled()

    // In-app notification rows still fire so admin + mentor see staged state.
    const mentorNotifs = await prisma.inAppNotification.findMany({
      where: { userId: mentor.id, type: 'MENTEE_ASSIGNED' },
    })
    expect(mentorNotifs.length).toBe(1)
    const teamNotifs = await prisma.inAppNotification.findMany({
      where: { userId: teamUser, type: 'MENTOR_ASSIGNED' },
    })
    expect(teamNotifs.length).toBe(1)
  })

  it('mentor.assign in ACTIVE round still sends the per-assignment emails (legacy behaviour preserved)', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)
    const program = await createTestProgram({ name: `defer-active-${uid()}` })
    programIds.push(program.id)
    const project = await createTestProject(program.id, { title: 'Active Project' })
    await attachToMentoringRound(program.id, project.id, 'ROUND_ACTIVE')

    const mentor = await makeMentor()
    userIds.push(mentor.id)
    const teamUser = await makeTeamMember(project.id)
    userIds.push(teamUser)

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

    // Either styled notif email OR the explicit team-intro email is allowed
    // to fire here — point is: at least one outbound email happens when the
    // round is open. The DRAFT test above is the one that must stay at zero.
    const sentCount =
      sendStyledMock.mock.calls.length + sendTeamIntroMock.mock.calls.length
    expect(sentCount).toBeGreaterThan(0)
  })

  it('mentor.bulkAssign in DRAFT round sends ZERO emails across multiple projects', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)
    const program = await createTestProgram({ name: `defer-bulk-${uid()}` })
    programIds.push(program.id)
    const p1 = await createTestProject(program.id, { title: 'BulkDraft 1' })
    const p2 = await createTestProject(program.id, { title: 'BulkDraft 2' })
    const p3 = await createTestProject(program.id, { title: 'BulkDraft 3' })
    await attachToMentoringRound(program.id, p1.id, 'ROUND_DRAFT')
    await attachToMentoringRound(program.id, p2.id, 'ROUND_DRAFT')
    await attachToMentoringRound(program.id, p3.id, 'ROUND_DRAFT')

    const mentor = await makeMentor()
    userIds.push(mentor.id)
    userIds.push(await makeTeamMember(p1.id))
    userIds.push(await makeTeamMember(p2.id))
    userIds.push(await makeTeamMember(p3.id))

    const caller = createCaller(mentorRouter, {
      id: admin.id,
      email: admin.email,
      role: 'SUPER_ADMIN',
    })
    await caller.bulkAssign({
      mentorIds: [mentor.id],
      projectIds: [p1.id, p2.id, p3.id],
    })

    expect(sendStyledMock).not.toHaveBeenCalled()
    expect(sendMentorBulkMock).not.toHaveBeenCalled()
    expect(sendTeamIntroMock).not.toHaveBeenCalled()
  })
})
