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

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

vi.mock('@/lib/auth', () => ({
  getSession: mockGetSession,
}))

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

vi.mock('@/lib/prisma', () => ({
  prisma: {
    pushSubscription: {
      upsert: mockUpsert,
      deleteMany: mockDeleteMany,
    },
  },
}))

import { subscribeToPushAction, unsubscribeFromPushAction } from '@/actions/push'

const VALID_INPUT = {
  endpoint: 'https://push.example.com/subscription/abc123',
  keys: { p256dh: 'aBcDeFgH', auth: 'xYzAbC' },
}

const SESSION_USER = { userId: 'user-1', isDemo: false }
const SESSION_DEMO = { userId: 'demo-1', isDemo: true }

beforeEach(() => {
  vi.clearAllMocks()
  mockUpsert.mockResolvedValue({})
  mockDeleteMany.mockResolvedValue({ count: 1 })
})

describe('subscribeToPushAction', () => {
  it('upserts subscription for authenticated user', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    await subscribeToPushAction(VALID_INPUT)
    expect(mockUpsert).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { endpoint: VALID_INPUT.endpoint },
        create: expect.objectContaining({ user_id: 'user-1', endpoint: VALID_INPUT.endpoint }),
      })
    )
  })

  it('is idempotent — calling twice upserts twice without error', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    await subscribeToPushAction(VALID_INPUT)
    await subscribeToPushAction(VALID_INPUT)
    expect(mockUpsert).toHaveBeenCalledTimes(2)
  })

  it('returns without writing for demo user', async () => {
    mockGetSession.mockResolvedValue(SESSION_DEMO)
    await subscribeToPushAction(VALID_INPUT)
    expect(mockUpsert).not.toHaveBeenCalled()
  })

  it('returns without writing when not authenticated', async () => {
    mockGetSession.mockResolvedValue({})
    await subscribeToPushAction(VALID_INPUT)
    expect(mockUpsert).not.toHaveBeenCalled()
  })

  it('returns without writing for invalid input', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    // @ts-expect-error intentionally invalid
    await subscribeToPushAction({ endpoint: 'not-a-url', keys: {} })
    expect(mockUpsert).not.toHaveBeenCalled()
  })
})

describe('unsubscribeFromPushAction', () => {
  it('deletes subscription scoped to user_id', async () => {
    mockGetSession.mockResolvedValue(SESSION_USER)
    await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
    expect(mockDeleteMany).toHaveBeenCalledWith({
      where: { endpoint: VALID_INPUT.endpoint, user_id: 'user-1' },
    })
  })

  it('does not touch subscriptions of other users', async () => {
    mockGetSession.mockResolvedValue({ userId: 'other-user', isDemo: false })
    await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
    expect(mockDeleteMany).toHaveBeenCalledWith(
      expect.objectContaining({ where: expect.objectContaining({ user_id: 'other-user' }) })
    )
  })

  it('returns without writing when not authenticated', async () => {
    mockGetSession.mockResolvedValue({})
    await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
    expect(mockDeleteMany).not.toHaveBeenCalled()
  })
})
