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

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

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

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

import { prisma } from '@/lib/prisma'
import { hashToken } from '@/lib/auth/pairing'
import {
  getPairingForApproval,
  approvePairing,
  cancelPairing,
} from '@/actions/pairing'

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

const VALID_PAIRING_ID = 'cmohmk0a' + 'g008bs417mzik8x9w'.padEnd(17, 'a').slice(0, 17)
const VALID_SECRET = 'A'.repeat(43) // ≥40 chars voor Zod min(40)
const SESSION_USER = { userId: 'user-1', isDemo: false }
const SESSION_DEMO = { userId: 'demo-1', isDemo: true }

beforeEach(() => {
  vi.clearAllMocks()
  mockPrisma.user.findUnique.mockResolvedValue({ username: 'lars' })
})

function pendingPairing(secret = VALID_SECRET) {
  return {
    status: 'pending' as const,
    expires_at: new Date(Date.now() + 60_000),
    secret_hash: hashToken(secret),
    desktop_ua: 'TestUA/1.0',
    desktop_ip: '198.51.100.1',
  }
}

describe('actions/pairing', () => {
  describe('getPairingForApproval', () => {
    it('ok-pad: pending + correct secret → desktop-info + username', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing())

      const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({
        ok: true,
        desktop_ua: 'TestUA/1.0',
        desktop_ip: '198.51.100.1',
        username: 'lars',
      })
    })

    it('faalt zonder sessie', async () => {
      mockGetSession.mockResolvedValue({ userId: undefined, isDemo: false })
      const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Niet ingelogd' })
    })

    it('faalt op al-approved pairing', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue({
        ...pendingPairing(),
        status: 'approved',
      })
      const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Pairing al afgehandeld' })
    })

    it('faalt op verlopen pairing', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue({
        ...pendingPairing(),
        expires_at: new Date(Date.now() - 1000),
      })
      const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Pairing verlopen' })
    })

    it('faalt op verkeerd secret', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing('echt'))
      const res = await getPairingForApproval(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Ongeldig pairing-geheim' })
    })

    it('faalt op ongeldige cuid (Zod)', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      const res = await getPairingForApproval('niet-cuid', VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Ongeldige invoer' })
      expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled()
    })
  })

  describe('approvePairing', () => {
    it('happy-pad: status pending→approved, user_id gezet, expires_at +5min', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing())
      mockPrisma.loginPairing.update.mockResolvedValue({})

      const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: true })
      expect(mockPrisma.loginPairing.update).toHaveBeenCalledTimes(1)
      const arg = mockPrisma.loginPairing.update.mock.calls[0][0]
      expect(arg.where).toEqual({ id: VALID_PAIRING_ID })
      expect(arg.data.status).toBe('approved')
      expect(arg.data.user_id).toBe('user-1')
      const dt = new Date(arg.data.expires_at).getTime() - Date.now()
      expect(dt).toBeGreaterThan(295_000)
      expect(dt).toBeLessThan(305_000)
    })

    it('demo-user wordt geblokkeerd, geen DB-write', async () => {
      mockGetSession.mockResolvedValue(SESSION_DEMO)
      const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' })
      expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled()
      expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled()
    })

    it('faalt op verkeerd secret zonder DB-write', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing('echt'))
      const res = await approvePairing(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Ongeldig pairing-geheim' })
      expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled()
    })
  })

  describe('cancelPairing', () => {
    it('happy-pad: status pending→cancelled', async () => {
      mockGetSession.mockResolvedValue(SESSION_USER)
      mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing())
      mockPrisma.loginPairing.update.mockResolvedValue({})

      const res = await cancelPairing(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: true })
      const arg = mockPrisma.loginPairing.update.mock.calls[0][0]
      expect(arg.data.status).toBe('cancelled')
    })

    it('demo-user wordt geblokkeerd, geen DB-write', async () => {
      mockGetSession.mockResolvedValue(SESSION_DEMO)
      const res = await cancelPairing(VALID_PAIRING_ID, VALID_SECRET)
      expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' })
      expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled()
      expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled()
    })
  })
})
