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

vi.mock('server-only', () => ({}))

const { mockSendNotification } = vi.hoisted(() => ({
  mockSendNotification: vi.fn(),
}))

vi.mock('web-push', () => ({
  default: {
    setVapidDetails: vi.fn(),
    sendNotification: mockSendNotification,
  },
}))

vi.hoisted(() => {
  process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY = 'pk'
  process.env.VAPID_PRIVATE_KEY = 'sk'
  process.env.VAPID_SUBJECT = 'mailto:test@example.com'
})

const { mockPushSubscription } = vi.hoisted(() => ({
  mockPushSubscription: {
    findMany: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
  },
}))
vi.mock('@/lib/prisma', () => ({
  prisma: { pushSubscription: mockPushSubscription },
}))

import { sendPushToUser } from '@/lib/push-server'

const SUB = { id: 'sub-1', endpoint: 'https://push.example.com/1', p256dh: 'p256dh', auth: 'auth' }
const PAYLOAD = { title: 'Test', body: 'Body', url: '/test' }

beforeEach(() => {
  vi.clearAllMocks()
  mockPushSubscription.findMany.mockResolvedValue([SUB])
  mockPushSubscription.update.mockResolvedValue(SUB)
  mockPushSubscription.delete.mockResolvedValue(SUB)
})

describe('sendPushToUser', () => {
  it('sends notification and updates last_used_at on success', async () => {
    mockSendNotification.mockResolvedValue({ statusCode: 201 })
    await sendPushToUser('user-1', PAYLOAD)
    expect(mockSendNotification).toHaveBeenCalledOnce()
    expect(mockPushSubscription.update).toHaveBeenCalledWith({
      where: { id: SUB.id },
      data: { last_used_at: expect.any(Date) },
    })
  })

  it('deletes subscription on 410 (expired)', async () => {
    mockSendNotification.mockRejectedValue({ statusCode: 410 })
    await sendPushToUser('user-1', PAYLOAD)
    expect(mockPushSubscription.delete).toHaveBeenCalledWith({ where: { id: SUB.id } })
    expect(mockPushSubscription.update).not.toHaveBeenCalled()
  })

  it('deletes subscription on 404 (not found)', async () => {
    mockSendNotification.mockRejectedValue({ statusCode: 404 })
    await sendPushToUser('user-1', PAYLOAD)
    expect(mockPushSubscription.delete).toHaveBeenCalledWith({ where: { id: SUB.id } })
  })

  it('logs error but does not delete on other error status', async () => {
    const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
    mockSendNotification.mockRejectedValue({ statusCode: 500 })
    await sendPushToUser('user-1', PAYLOAD)
    expect(mockPushSubscription.delete).not.toHaveBeenCalled()
    expect(consoleSpy).toHaveBeenCalled()
    consoleSpy.mockRestore()
  })
})
