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

const { mockReadPairCookie, mockClearPairCookie, mockSession, mockGetIronSession } = vi.hoisted(
  () => ({
    mockReadPairCookie: vi.fn(),
    mockClearPairCookie: vi.fn(),
    mockSession: { userId: '', isDemo: false, paired: false, pairedExpiresAt: 0, save: vi.fn() },
    mockGetIronSession: vi.fn(),
  }),
)

vi.mock('@/lib/auth/pair-cookie', () => ({
  readPairCookie: mockReadPairCookie,
  clearPairCookie: mockClearPairCookie,
  setPairCookie: vi.fn(),
}))

vi.mock('iron-session', () => ({
  getIronSession: mockGetIronSession,
}))

vi.mock('next/headers', () => ({
  cookies: vi.fn().mockResolvedValue({}),
}))

vi.mock('@/lib/prisma', () => ({
  prisma: {
    loginPairing: {
      updateMany: vi.fn(),
      findFirst: vi.fn(),
      findUnique: vi.fn(),
    },
  },
}))

import { prisma } from '@/lib/prisma'
import { hashToken } from '@/lib/auth/pairing'
import { POST } from '@/app/api/auth/pair/claim/route'

const mockPrisma = prisma as unknown as {
  loginPairing: {
    updateMany: ReturnType<typeof vi.fn>
    findFirst: ReturnType<typeof vi.fn>
    findUnique: ReturnType<typeof vi.fn>
  }
}

const COOKIE_TOKEN = 'desktop-token-abc'
const COOKIE_HASH = hashToken(COOKIE_TOKEN)
const PAIRING_ID = 'cmohmk0qpair006c001'

function makePost(body: unknown): Request {
  return new Request('http://localhost:3000/api/auth/pair/claim', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: typeof body === 'string' ? body : JSON.stringify(body),
  })
}

beforeEach(() => {
  vi.clearAllMocks()
  // Reset session-mock voor elke test
  mockSession.userId = ''
  mockSession.isDemo = false
  mockSession.paired = false
  mockSession.pairedExpiresAt = 0
  mockSession.save = vi.fn().mockResolvedValue(undefined)
  mockGetIronSession.mockResolvedValue(mockSession)
})

describe('POST /api/auth/pair/claim', () => {
  it('200: schrijft iron-session, clear s4m_pair, retourneert {ok:true}', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    mockPrisma.loginPairing.updateMany.mockResolvedValue({ count: 1 })
    mockPrisma.loginPairing.findUnique.mockResolvedValue({
      user_id: 'user-42',
      user: { is_demo: false },
    })

    const res = await POST(makePost({ pairingId: PAIRING_ID }))
    expect(res.status).toBe(200)
    expect(await res.json()).toEqual({ ok: true })

    // Atomic update aangeroepen met juiste WHERE
    expect(mockPrisma.loginPairing.updateMany).toHaveBeenCalledTimes(1)
    const where = mockPrisma.loginPairing.updateMany.mock.calls[0][0].where
    expect(where).toMatchObject({
      id: PAIRING_ID,
      status: 'approved',
      desktop_token_hash: COOKIE_HASH,
    })
    expect(where.expires_at).toMatchObject({ gt: expect.any(Date) })

    // Iron-session payload
    expect(mockSession.userId).toBe('user-42')
    expect(mockSession.isDemo).toBe(false)
    expect(mockSession.paired).toBe(true)
    const dt = mockSession.pairedExpiresAt - Date.now()
    expect(dt).toBeGreaterThan(8 * 60 * 60 * 1000 - 5_000)
    expect(dt).toBeLessThan(8 * 60 * 60 * 1000 + 5_000)
    expect(mockSession.save).toHaveBeenCalledTimes(1)

    expect(mockClearPairCookie).toHaveBeenCalledTimes(1)
  })

  it('demo-user: claim geblokkeerd met 403 (ST-1110.4)', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    mockPrisma.loginPairing.updateMany.mockResolvedValue({ count: 1 })
    mockPrisma.loginPairing.findUnique.mockResolvedValue({
      user_id: 'demo-1',
      user: { is_demo: true },
    })

    const res = await POST(makePost({ pairingId: PAIRING_ID }))
    expect(res.status).toBe(403)
    const body = await res.json()
    expect(body.error).toMatch(/demo-modus/i)
    expect(mockClearPairCookie).toHaveBeenCalledTimes(1)
  })

  it('401 zonder s4m_pair-cookie', async () => {
    mockReadPairCookie.mockResolvedValue(null)
    const res = await POST(makePost({ pairingId: PAIRING_ID }))
    expect(res.status).toBe(401)
    expect(mockPrisma.loginPairing.updateMany).not.toHaveBeenCalled()
  })

  it('400 zonder body', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    const res = await POST(makePost('not-json'))
    expect(res.status).toBe(400)
  })

  it('400 zonder pairingId', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    const res = await POST(makePost({}))
    expect(res.status).toBe(400)
  })

  it('410 op tweede claim — pairing al consumed', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    mockPrisma.loginPairing.updateMany.mockResolvedValue({ count: 0 })
    mockPrisma.loginPairing.findFirst.mockResolvedValue({ status: 'consumed' })

    const res = await POST(makePost({ pairingId: PAIRING_ID }))
    expect(res.status).toBe(410)
    expect(mockClearPairCookie).toHaveBeenCalledTimes(1)
    expect(mockSession.save).not.toHaveBeenCalled()
  })

  it('401 op cookie/hash-mismatch (pairing bestaat niet voor deze cookie)', async () => {
    mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN)
    mockPrisma.loginPairing.updateMany.mockResolvedValue({ count: 0 })
    mockPrisma.loginPairing.findFirst.mockResolvedValue(null)

    const res = await POST(makePost({ pairingId: PAIRING_ID }))
    expect(res.status).toBe(401)
    expect(mockClearPairCookie).toHaveBeenCalledTimes(1)
  })
})
