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

const mockCreate = vi.fn().mockResolvedValue({ id: '1' })
const mockGetPayload = vi.fn().mockResolvedValue({ create: mockCreate })

vi.mock('payload', () => ({
  getPayload: mockGetPayload,
}))

vi.mock('@payload-config', () => ({
  default: {},
}))

import { createMonitoringLogger, _resetPayloadCache } from '@/lib/monitoring/monitoring-logger'

/** Wait long enough for fire-and-forget promises to settle. */
async function flush(): Promise<void> {
  await new Promise((resolve) => setTimeout(resolve, 150))
}

describe('MonitoringLogger', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    _resetPayloadCache()
    mockGetPayload.mockResolvedValue({ create: mockCreate })
    mockCreate.mockResolvedValue({ id: '1' })
    delete process.env.MONITORING_LOG_LEVEL
  })

  afterEach(async () => {
    // Drain any pending fire-and-forget promises to prevent cross-test leakage
    await flush()
  })

  it('creates a logger with all five log methods', () => {
    const logger = createMonitoringLogger('cron')

    expect(logger).toHaveProperty('debug')
    expect(logger).toHaveProperty('info')
    expect(logger).toHaveProperty('warn')
    expect(logger).toHaveProperty('error')
    expect(logger).toHaveProperty('fatal')
  })

  it('writes to payload when log level meets threshold', async () => {
    const logger = createMonitoringLogger('cron')
    logger.info('Cron job completed', { duration: 3500 })

    await flush()

    expect(mockGetPayload).toHaveBeenCalled()
    expect(mockCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'monitoring-logs',
        data: expect.objectContaining({
          level: 'info',
          source: 'cron',
          message: 'Cron job completed',
          duration: 3500,
        }),
      }),
    )
  })

  it('filters out messages below the configured minimum level', async () => {
    process.env.MONITORING_LOG_LEVEL = 'warn'

    const logger = createMonitoringLogger('payload')
    logger.debug('This should not be logged')
    logger.info('This should not be logged either')

    await flush()

    expect(mockCreate).not.toHaveBeenCalled()
  })

  it('allows messages at or above the configured minimum level', async () => {
    process.env.MONITORING_LOG_LEVEL = 'warn'

    const logger = createMonitoringLogger('email')
    logger.warn('SMTP timeout')

    await flush()

    expect(mockCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        collection: 'monitoring-logs',
        data: expect.objectContaining({
          level: 'warn',
          source: 'email',
          message: 'SMTP timeout',
        }),
      }),
    )
  })

  it('passes context fields to the payload create call', async () => {
    const logger = createMonitoringLogger('sync')
    logger.info('Sync completed', { requestId: 'req-123', userId: 42, tenant: 1, duration: 1500 })

    await flush()

    expect(mockCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          requestId: 'req-123',
          userId: 42,
          tenant: 1,
          duration: 1500,
        }),
      }),
    )
  })

  it('separates extra context fields from known fields', async () => {
    const logger = createMonitoringLogger('oauth')
    logger.error('Token refresh failed', { userId: 5, provider: 'meta', errorCode: 'EXPIRED' })

    await flush()

    expect(mockCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          userId: 5,
          context: { provider: 'meta', errorCode: 'EXPIRED' },
        }),
      }),
    )
  })

  it('falls back to console when payload is unavailable', async () => {
    mockGetPayload.mockRejectedValueOnce(new Error('Not initialized'))

    const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

    const logger = createMonitoringLogger('cron')
    logger.info('Fallback test')

    await flush()

    expect(consoleSpy).toHaveBeenCalledWith('[cron][INFO]', 'Fallback test', expect.anything())

    consoleSpy.mockRestore()
  })
})
