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

vi.mock('../../../server/utils/adminAlerts.js', () => ({
  computeAllAlerts: vi.fn(),
  ALERT_METADATA: {
    slack_invite_failed: { title: 'Slack invites failed', severity: 'critical' },
    no_slack_handle_week: { title: 'Active members without a Slack handle', severity: 'attention' },
    stuck_pending_payment: { title: 'Members stuck in pending payment', severity: 'attention' },
    member_suspended: { title: 'Suspended members', severity: 'attention' },
    preregistrant_selected_not_invited: { title: 'Pre-registrants selected but not invited', severity: 'attention' },
    preregistrant_expired: { title: 'Expired pre-registrant invitations', severity: 'attention' },
    event_draft_imminent: { title: 'Draft events with imminent start', severity: 'critical' },
    event_near_capacity: { title: 'Events approaching capacity', severity: 'attention' },
    tag_suggestions_pending: { title: 'Pending tag suggestions', severity: 'attention' }
  }
}))

vi.mock('../../../server/models/adminAlertDismissal.js', () => ({
  default: {
    findOneAndUpdate: vi.fn(),
    find: vi.fn(),
    deleteMany: vi.fn()
  },
  ADMIN_ALERT_TYPES: [
    'slack_invite_failed',
    'no_slack_handle_week',
    'stuck_pending_payment',
    'member_suspended',
    'preregistrant_selected_not_invited',
    'preregistrant_expired',
    'event_draft_imminent',
    'event_near_capacity',
    'tag_suggestions_pending'
  ]
}))

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

vi.stubGlobal('adminAlertDismissSchema', {})
vi.stubGlobal('adminAlertRestoreSchema', {})

import getHandler from '../../../server/api/admin/alerts/index.get.js'
import dismissHandler from '../../../server/api/admin/alerts/dismiss.post.js'
import dismissedHandler from '../../../server/api/admin/alerts/dismissed.get.js'
import restoreHandler from '../../../server/api/admin/alerts/restore.post.js'
import { computeAllAlerts } from '../../../server/utils/adminAlerts.js'
import AdminAlertDismissal from '../../../server/models/adminAlertDismissal.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

describe('GET /api/admin/alerts', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } })
  })

  describe('source inspection', () => {
    const source = readFileSync(
      resolve(import.meta.dirname, '../../../server/api/admin/alerts/index.get.js'),
      'utf-8'
    )
    it('calls requireAdmin before any business logic', () => {
      const adminIdx = source.indexOf('requireAdmin(event)')
      const computeIdx = source.indexOf('computeAllAlerts(')
      expect(adminIdx).toBeGreaterThan(-1)
      expect(computeIdx).toBeGreaterThan(-1)
      expect(adminIdx).toBeLessThan(computeIdx)
    })
  })

  it('returns the active alerts for the current admin', async () => {
    computeAllAlerts.mockResolvedValue([
      {
        type: 'slack_invite_failed',
        severity: 'critical',
        title: 'Slack invites failed',
        count: 1,
        items: [{ id: 'm1', label: 'Alex', sublabel: 'alex@example.com', href: '/admin/members/m1' }],
        signature: 'abc'
      }
    ])

    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts' })
    const result = await getHandler(event)

    expect(result).toEqual({
      alerts: [
        {
          type: 'slack_invite_failed',
          severity: 'critical',
          title: 'Slack invites failed',
          count: 1,
          items: [{ id: 'm1', label: 'Alex', sublabel: 'alex@example.com', href: '/admin/members/m1' }],
          signature: 'abc'
        }
      ]
    })
    expect(computeAllAlerts).toHaveBeenCalledWith('admin-1')
  })

  it('rejects non-admin requests', async () => {
    requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' }))
    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts' })
    await expect(getHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })
})

describe('POST /api/admin/alerts/dismiss', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } })
    validateBody.mockResolvedValue({ alertType: 'slack_invite_failed', signature: 'abc' })
  })

  describe('source inspection', () => {
    const source = readFileSync(
      resolve(import.meta.dirname, '../../../server/api/admin/alerts/dismiss.post.js'),
      'utf-8'
    )
    it('calls requireAdmin before validateBody', () => {
      const adminIdx = source.indexOf('requireAdmin(event)')
      const validateIdx = source.indexOf('validateBody(event')
      expect(adminIdx).toBeGreaterThan(-1)
      expect(validateIdx).toBeGreaterThan(-1)
      expect(adminIdx).toBeLessThan(validateIdx)
    })
  })

  it('upserts a dismissal with the current signature', async () => {
    AdminAlertDismissal.findOneAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/dismiss',
      body: { alertType: 'slack_invite_failed', signature: 'abc' }
    })
    const result = await dismissHandler(event)

    expect(result).toEqual({ ok: true })
    expect(AdminAlertDismissal.findOneAndUpdate).toHaveBeenCalledWith(
      { adminId: 'admin-1', alertType: 'slack_invite_failed' },
      { $set: { signature: 'abc', dismissedAt: expect.any(Date) } },
      { upsert: true, new: true }
    )
  })

  it('rejects non-admin requests', async () => {
    requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' }))
    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/dismiss',
      body: { alertType: 'slack_invite_failed', signature: 'abc' }
    })
    await expect(dismissHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })

  it('rejects invalid body via validateBody', async () => {
    validateBody.mockRejectedValue(createError({ statusCode: 400, statusMessage: 'Validation failed' }))
    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/dismiss',
      body: { alertType: 'unknown', signature: 'abc' }
    })
    await expect(dismissHandler(event)).rejects.toMatchObject({ statusCode: 400 })
  })
})

describe('GET /api/admin/alerts/dismissed', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } })
  })

  describe('source inspection', () => {
    const source = readFileSync(
      resolve(import.meta.dirname, '../../../server/api/admin/alerts/dismissed.get.js'),
      'utf-8'
    )
    it('calls requireAdmin before querying dismissals', () => {
      const adminIdx = source.indexOf('requireAdmin(event)')
      const findIdx = source.indexOf('AdminAlertDismissal.find')
      expect(adminIdx).toBeGreaterThan(-1)
      expect(findIdx).toBeGreaterThan(-1)
      expect(adminIdx).toBeLessThan(findIdx)
    })
  })

  it('returns dismissed alerts with title and severity joined from metadata', async () => {
    AdminAlertDismissal.find.mockReturnValue({
      sort: () => ({
        lean: () => Promise.resolve([
          { alertType: 'slack_invite_failed', signature: 'abc', dismissedAt: new Date('2026-04-08T10:00:00Z') },
          { alertType: 'member_suspended', signature: 'def', dismissedAt: new Date('2026-04-07T10:00:00Z') }
        ])
      })
    })

    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' })
    const result = await dismissedHandler(event)

    expect(AdminAlertDismissal.find).toHaveBeenCalledWith({ adminId: 'admin-1' })
    expect(result.dismissed).toEqual([
      {
        alertType: 'slack_invite_failed',
        title: 'Slack invites failed',
        severity: 'critical',
        dismissedAt: new Date('2026-04-08T10:00:00Z')
      },
      {
        alertType: 'member_suspended',
        title: 'Suspended members',
        severity: 'attention',
        dismissedAt: new Date('2026-04-07T10:00:00Z')
      }
    ])
  })

  it('returns empty list when no dismissals exist', async () => {
    AdminAlertDismissal.find.mockReturnValue({
      sort: () => ({ lean: () => Promise.resolve([]) })
    })
    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' })
    const result = await dismissedHandler(event)
    expect(result).toEqual({ dismissed: [] })
  })

  it('filters out orphaned slugs not present in ALERT_METADATA', async () => {
    AdminAlertDismissal.find.mockReturnValue({
      sort: () => ({
        lean: () => Promise.resolve([
          { alertType: 'slack_invite_failed', signature: 'abc', dismissedAt: new Date() },
          { alertType: 'legacy_removed_alert', signature: 'xyz', dismissedAt: new Date() }
        ])
      })
    })
    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' })
    const result = await dismissedHandler(event)
    expect(result.dismissed).toHaveLength(1)
    expect(result.dismissed[0].alertType).toBe('slack_invite_failed')
  })

  it('rejects non-admin requests', async () => {
    requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' }))
    const event = createMockEvent({ method: 'GET', path: '/api/admin/alerts/dismissed' })
    await expect(dismissedHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })
})

describe('POST /api/admin/alerts/restore', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    requireAdmin.mockResolvedValue({ _id: { toString: () => 'admin-1' } })
    validateBody.mockResolvedValue({ alertTypes: ['slack_invite_failed', 'member_suspended'] })
  })

  describe('source inspection', () => {
    const source = readFileSync(
      resolve(import.meta.dirname, '../../../server/api/admin/alerts/restore.post.js'),
      'utf-8'
    )
    it('calls requireAdmin before validateBody', () => {
      const adminIdx = source.indexOf('requireAdmin(event)')
      const validateIdx = source.indexOf('validateBody(event')
      expect(adminIdx).toBeGreaterThan(-1)
      expect(validateIdx).toBeGreaterThan(-1)
      expect(adminIdx).toBeLessThan(validateIdx)
    })
  })

  it('deletes dismissals matching the provided alertTypes and returns count', async () => {
    AdminAlertDismissal.deleteMany.mockResolvedValue({ deletedCount: 2 })

    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/restore',
      body: { alertTypes: ['slack_invite_failed', 'member_suspended'] }
    })
    const result = await restoreHandler(event)

    expect(result).toEqual({ ok: true, restored: 2 })
    expect(AdminAlertDismissal.deleteMany).toHaveBeenCalledWith({
      adminId: 'admin-1',
      alertType: { $in: ['slack_invite_failed', 'member_suspended'] }
    })
  })

  it('returns restored: 0 when deleteMany returns no deletedCount', async () => {
    AdminAlertDismissal.deleteMany.mockResolvedValue({})
    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/restore',
      body: { alertTypes: ['slack_invite_failed'] }
    })
    const result = await restoreHandler(event)
    expect(result).toEqual({ ok: true, restored: 0 })
  })

  it('rejects non-admin requests', async () => {
    requireAdmin.mockRejectedValue(createError({ statusCode: 403, statusMessage: 'Forbidden' }))
    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/restore',
      body: { alertTypes: ['slack_invite_failed'] }
    })
    await expect(restoreHandler(event)).rejects.toMatchObject({ statusCode: 403 })
  })

  it('rejects invalid body via validateBody', async () => {
    validateBody.mockRejectedValue(createError({ statusCode: 400, statusMessage: 'Validation failed' }))
    const event = createMockEvent({
      method: 'POST',
      path: '/api/admin/alerts/restore',
      body: { alertTypes: [] }
    })
    await expect(restoreHandler(event)).rejects.toMatchObject({ statusCode: 400 })
  })
})
