import { describe, it, expect, vi, beforeEach } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'

vi.mock('../../../server/models/member.js', () => ({
  default: { findById: vi.fn(), findByIdAndUpdate: vi.fn() }
}))

vi.mock('../../../server/utils/mongoose.js', () => ({
  connectDB: vi.fn()
}))

vi.mock('../../../server/utils/validateBody.js', () => ({
  validateBody: vi.fn()
}))

vi.mock('../../../server/utils/schemas.js', () => ({
  adminRoleUpdateSchema: {}
}))

import handler from '../../../server/api/admin/members/[id]/role.patch.js'
import Member from '../../../server/models/member.js'
import { validateBody } from '../../../server/utils/validateBody.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

describe('admin role PATCH endpoint', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({
      _id: { toString: () => 'admin-123' }
    })
    validateBody.mockResolvedValue({ role: 'member' })
    vi.stubGlobal('getRouterParam', vi.fn().mockReturnValue('target-member-id'))
  })

  describe('source inspection', () => {
    const source = readFileSync(
      resolve(import.meta.dirname, '../../../server/api/admin/members/[id]/role.patch.js'),
      'utf-8'
    )

    it('calls requireAdmin before validateBody', () => {
      const adminIndex = source.indexOf('requireAdmin(event)')
      const validateIndex = source.indexOf('validateBody(event')

      expect(adminIndex).toBeGreaterThan(-1)
      expect(validateIndex).toBeGreaterThan(-1)
      expect(adminIndex).toBeLessThan(validateIndex)
    })
  })

  describe('auth', () => {
    it('rejects non-admin users', async () => {
      requireAdmin.mockRejectedValue(
        createError({ statusCode: 403, statusMessage: 'Forbidden' })
      )

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/target-member-id/role',
        body: { role: 'member' }
      })

      await expect(handler(event)).rejects.toMatchObject({ statusCode: 403 })
    })
  })

  describe('self-demotion', () => {
    it('returns 400 when admin tries to remove their own admin role', async () => {
      requireAdmin.mockResolvedValue({
        _id: { toString: () => 'admin-123' }
      })
      validateBody.mockResolvedValue({ role: 'member' })
      vi.stubGlobal('getRouterParam', vi.fn().mockReturnValue('admin-123'))

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/admin-123/role',
        body: { role: 'member' }
      })

      await expect(handler(event)).rejects.toMatchObject({
        statusCode: 400,
        statusMessage: 'You cannot remove your own admin role.'
      })
    })
  })

  describe('validation', () => {
    it('rejects invalid role via validateBody', async () => {
      validateBody.mockRejectedValue(
        createError({ statusCode: 400, statusMessage: 'Invalid role' })
      )

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/target-member-id/role',
        body: { role: 'superadmin' }
      })

      await expect(handler(event)).rejects.toMatchObject({ statusCode: 400 })
    })
  })

  describe('member not found', () => {
    it('returns 404 when member does not exist', async () => {
      Member.findById.mockResolvedValue(null)

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/nonexistent-id/role',
        body: { role: 'member' }
      })

      await expect(handler(event)).rejects.toMatchObject({
        statusCode: 404,
        statusMessage: 'Member not found.'
      })
    })
  })

  describe('successful role changes', () => {
    it('promotes a member to admin', async () => {
      validateBody.mockResolvedValue({ role: 'admin' })
      const existingMember = { _id: 'target-member-id', role: 'member', name: 'Test User' }
      const updatedMember = { _id: 'target-member-id', role: 'admin', name: 'Test User' }
      Member.findById.mockResolvedValue(existingMember)
      Member.findByIdAndUpdate.mockResolvedValue(updatedMember)

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/target-member-id/role',
        body: { role: 'admin' }
      })

      const result = await handler(event)

      expect(result).toEqual({ success: true, member: updatedMember })
      expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
        'target-member-id',
        { role: 'admin' },
        { new: true }
      )
    })

    it('demotes a member to regular role', async () => {
      validateBody.mockResolvedValue({ role: 'member' })
      const existingMember = { _id: 'target-member-id', role: 'admin', name: 'Test User' }
      const updatedMember = { _id: 'target-member-id', role: 'member', name: 'Test User' }
      Member.findById.mockResolvedValue(existingMember)
      Member.findByIdAndUpdate.mockResolvedValue(updatedMember)

      const event = createMockEvent({
        method: 'PATCH',
        path: '/api/admin/members/target-member-id/role',
        body: { role: 'member' }
      })

      const result = await handler(event)

      expect(result).toEqual({ success: true, member: updatedMember })
      expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
        'target-member-id',
        { role: 'member' },
        { new: true }
      )
    })
  })
})
