import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { prisma, createCaller } from '../setup'
import { createTestUser, cleanupTestData, uid } from '../helpers'
import { userRouter } from '../../src/server/routers/user'
import * as emailModule from '../../src/lib/email'
import type { UserRole } from '@prisma/client'

async function createUserWithRoles(primaryRole: UserRole, rolesArray: UserRole[]) {
  const id = uid('user')
  return prisma.user.create({
    data: {
      id,
      email: `${id}@test.local`,
      name: `Test ${primaryRole}`,
      role: primaryRole,
      roles: rolesArray,
      status: 'ACTIVE',
    },
  })
}

describe('user.bulkUpdateRoles', () => {
  const userIds: string[] = []

  afterAll(async () => {
    if (userIds.length > 0) {
      await prisma.auditLog.deleteMany({ where: { userId: { in: userIds } } })
      await prisma.decisionAuditLog.deleteMany({ where: { actorId: { in: userIds } } })
      await prisma.user.deleteMany({ where: { id: { in: userIds } } })
    }
  })

  beforeEach(() => {
    vi.restoreAllMocks()
  })

  it('adds MENTOR role to multiple users and sends one onboarding email each', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    const j1 = await createUserWithRoles('JURY_MEMBER', ['JURY_MEMBER'])
    const j2 = await createUserWithRoles('JURY_MEMBER', ['JURY_MEMBER'])
    userIds.push(admin.id, j1.id, j2.id)

    const sendSpy = vi.spyOn(emailModule, 'sendMentorOnboardingEmail').mockResolvedValue()

    const caller = createCaller(userRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.bulkUpdateRoles({
      userIds: [j1.id, j2.id],
      addRole: 'MENTOR',
    })

    expect(result.updated).toBe(2)
    expect(result.alreadyHadRole).toBe(0)
    expect(sendSpy).toHaveBeenCalledTimes(2)

    const updatedJ1 = await prisma.user.findUniqueOrThrow({ where: { id: j1.id } })
    expect(updatedJ1.roles).toContain('MENTOR')
    expect(updatedJ1.roles).toContain('JURY_MEMBER')
    expect(updatedJ1.mentorOnboardingSentAt).not.toBeNull()
  })

  it('is idempotent — second call with same input does NOT resend onboarding email', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    const j = await createUserWithRoles('JURY_MEMBER', ['JURY_MEMBER'])
    userIds.push(admin.id, j.id)

    const sendSpy = vi.spyOn(emailModule, 'sendMentorOnboardingEmail').mockResolvedValue()

    const caller = createCaller(userRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    await caller.bulkUpdateRoles({ userIds: [j.id], addRole: 'MENTOR' })
    expect(sendSpy).toHaveBeenCalledTimes(1)

    const second = await caller.bulkUpdateRoles({ userIds: [j.id], addRole: 'MENTOR' })
    expect(sendSpy).toHaveBeenCalledTimes(1) // still 1
    expect(second.updated).toBe(0)
    expect(second.alreadyHadRole).toBe(1)
  })

  it('removeRole strips the role; does not affect onboarding stamp', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    const m = await createUserWithRoles('MENTOR', ['MENTOR', 'JURY_MEMBER'])
    userIds.push(admin.id, m.id)

    const caller = createCaller(userRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    const result = await caller.bulkUpdateRoles({ userIds: [m.id], removeRole: 'JURY_MEMBER' })

    expect(result.updated).toBe(1)
    const updated = await prisma.user.findUniqueOrThrow({ where: { id: m.id } })
    expect(updated.roles).toEqual(['MENTOR'])
  })

  it('refuses to remove SUPER_ADMIN from self', async () => {
    const admin = await createTestUser('SUPER_ADMIN')
    userIds.push(admin.id)
    await prisma.user.update({ where: { id: admin.id }, data: { roles: ['SUPER_ADMIN'] } })

    const caller = createCaller(userRouter, { id: admin.id, email: admin.email, role: 'SUPER_ADMIN' })
    await expect(
      caller.bulkUpdateRoles({ userIds: [admin.id], removeRole: 'SUPER_ADMIN' }),
    ).rejects.toThrow(/cannot remove SUPER_ADMIN from self|self-demote/i)
  })
})
