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

const { mockFind, mockSort, mockLimit, mockSelect, mockLean } = vi.hoisted(() => ({
  mockFind: vi.fn(),
  mockSort: vi.fn(),
  mockLimit: vi.fn(),
  mockSelect: vi.fn(),
  mockLean: vi.fn()
}))

vi.mock('../../../server/models/event.js', () => ({
  default: { find: mockFind }
}))

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

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

import { requireAuth } from '../../../server/utils/auth.js'
import handler from '../../../server/api/events/recommended.get.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

// Wire up the chained query builder
function setupChain(result = []) {
  mockLean.mockResolvedValue(result)
  mockSelect.mockReturnValue({ lean: mockLean })
  mockLimit.mockReturnValue({ select: mockSelect })
  mockSort.mockReturnValue({ limit: mockLimit })
  mockFind.mockReturnValue({ sort: mockSort })
}

const futureDate = new Date(Date.now() + 86400000)

function makeMember(overrides = {}) {
  return {
    _id: 'member-1',
    craftTags: [],
    ...overrides
  }
}

function makeEvent(overrides = {}) {
  return {
    _id: 'event-1',
    title: 'Test Event',
    slug: '2026-05-01-test-event',
    startDate: futureDate,
    endDate: futureDate,
    tagline: 'A test event',
    tags: ['game-design'],
    eventType: 'workshop',
    isOnline: true,
    ...overrides
  }
}

describe('GET /api/events/recommended', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('returns events matching member craft tags', async () => {
    const member = makeMember({ craftTags: ['game-design', 'narrative'] })
    requireAuth.mockResolvedValue(member)

    const events = [makeEvent({ tags: ['game-design'] })]
    setupChain(events)

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })
    const result = await handler(event)

    expect(result).toEqual(events)
    expect(mockFind).toHaveBeenCalledWith(
      expect.objectContaining({
        tags: { $in: expect.arrayContaining(['game-design', 'narrative']) }
      })
    )
  })

  it('returns empty array when no tag overlap', async () => {
    const member = makeMember({ craftTags: ['audio'] })
    requireAuth.mockResolvedValue(member)

    setupChain([])

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })
    const result = await handler(event)

    expect(result).toEqual([])
  })

  it('excludes past events via startDate $gt filter', async () => {
    const member = makeMember({ craftTags: ['game-design'] })
    requireAuth.mockResolvedValue(member)

    setupChain([])

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })
    await handler(event)

    const filter = mockFind.mock.calls[0][0]
    expect(filter.startDate).toEqual({ $gt: expect.any(Date) })
    // The filter date should be approximately now (within 5 seconds)
    const filterDate = filter.startDate.$gt
    expect(filterDate.getTime()).toBeGreaterThan(Date.now() - 5000)
    expect(filterDate.getTime()).toBeLessThanOrEqual(Date.now())
  })

  it('uses default limit of 10', async () => {
    const member = makeMember({ craftTags: ['game-design'] })
    requireAuth.mockResolvedValue(member)

    setupChain([])

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })
    await handler(event)

    expect(mockLimit).toHaveBeenCalledWith(10)
  })

  it('accepts limit query param', async () => {
    const member = makeMember({ craftTags: ['game-design'] })
    requireAuth.mockResolvedValue(member)

    setupChain([])

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended?limit=5' })
    await handler(event)

    expect(mockLimit).toHaveBeenCalledWith(5)
  })

  it('caps limit at 25', async () => {
    const member = makeMember({ craftTags: ['game-design'] })
    requireAuth.mockResolvedValue(member)

    setupChain([])

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended?limit=100' })
    await handler(event)

    expect(mockLimit).toHaveBeenCalledWith(25)
  })

  it('returns empty array when member has no tags', async () => {
    const member = makeMember()
    requireAuth.mockResolvedValue(member)

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })
    const result = await handler(event)

    expect(result).toEqual([])
    // Should not query the database at all
    expect(mockFind).not.toHaveBeenCalled()
  })

  it('requires auth (401)', async () => {
    requireAuth.mockRejectedValue(
      createError({ statusCode: 401, statusMessage: 'Unauthorized' })
    )

    const event = createMockEvent({ method: 'GET', path: '/api/events/recommended' })

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