import { describe, it, expect, vi, beforeEach } from 'vitest'

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

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

vi.mock('jsonwebtoken', () => ({
  default: {
    verify: vi.fn(),
    sign: vi.fn().mockReturnValue('mock-session-token')
  }
}))

import jwt from 'jsonwebtoken'
import Member from '../../../server/models/member.js'
import verifyHandler from '../../../server/api/auth/verify.post.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

const baseMember = {
  _id: 'member-123',
  email: 'test@example.com',
  status: 'active',
  role: 'member',
  magicLinkJti: 'jti-abc',
  magicLinkJtiUsed: false,
  tokenVersion: 1
}

describe('auth verify endpoint', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('rejects missing token with 400', async () => {
    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: {}
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 400,
      statusMessage: 'Token is required'
    })
  })

  it('rejects invalid JWT with 401', async () => {
    jwt.verify.mockImplementation(() => { throw new Error('invalid') })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'bad-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 401,
      statusMessage: 'Invalid or expired token'
    })
  })

  it('rejects when member not found with 401', async () => {
    jwt.verify.mockReturnValue({ memberId: 'nonexistent', jti: 'jti-abc' })
    Member.findById.mockResolvedValue(null)

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 401,
      statusMessage: 'Invalid or expired token'
    })
  })

  it('rejects suspended member with 403', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember, status: 'suspended' })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 403,
      statusMessage: 'Account is suspended'
    })
  })

  it('rejects cancelled member with 403', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember, status: 'cancelled' })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 403,
      statusMessage: 'Account is cancelled'
    })
  })

  it('rejects JTI mismatch with 401', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'wrong-jti' })
    Member.findById.mockResolvedValue({ ...baseMember })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 401,
      statusMessage: 'Invalid or expired token'
    })
  })

  it('rejects already-used JTI with 401', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember, magicLinkJtiUsed: true })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await expect(verifyHandler(event)).rejects.toMatchObject({
      statusCode: 401,
      statusMessage: 'Invalid or expired token'
    })
  })

  it('burns token atomically via findByIdAndUpdate', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await verifyHandler(event)

    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-123',
      { $set: { magicLinkJtiUsed: true, lastLogin: expect.any(Date) } },
      { runValidators: false }
    )
  })

  it('sets httpOnly session cookie with correct attributes', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    await verifyHandler(event)

    const setCookieHeader = event._testSetHeaders['set-cookie']
    expect(setCookieHeader).toBeDefined()

    const cookie = Array.isArray(setCookieHeader) ? setCookieHeader.join('; ') : setCookieHeader
    expect(cookie).toContain('auth-token=mock-session-token')
    expect(cookie).toContain('HttpOnly')
    expect(cookie).toContain('SameSite=Lax')
    expect(cookie).toContain('Path=/')
    expect(cookie).toContain('Max-Age=604800')
  })

  it('returns admin redirect for admin role', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember, role: 'admin' })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    const result = await verifyHandler(event)

    expect(result).toEqual({ success: true, redirectUrl: '/admin' })
  })

  it('returns member redirect for non-admin role', async () => {
    jwt.verify.mockReturnValue({ memberId: 'member-123', jti: 'jti-abc' })
    Member.findById.mockResolvedValue({ ...baseMember })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/auth/verify',
      body: { token: 'valid-token' }
    })

    const result = await verifyHandler(event)

    expect(result).toEqual({ success: true, redirectUrl: '/member/dashboard' })
  })
})
