import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { NextFunction, Request, Response } from 'express'

vi.mock('jsonwebtoken', () => ({
  default: { verify: vi.fn() },
}))

vi.mock('../lib/prisma', () => ({
  prisma: {
    renter: { findUnique: vi.fn() },
  },
}))

import jwt from 'jsonwebtoken'
import { prisma } from '../lib/prisma'
import { optionalRenterAuth, requireRenterAuth } from './requireRenterAuth'

function responseStub() {
  const res = { status: vi.fn(), json: vi.fn() }
  res.status.mockReturnValue(res)
  res.json.mockReturnValue(res)
  return res as unknown as Response & typeof res
}

describe('requireRenterAuth middleware', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    process.env.JWT_SECRET = 'test-secret'
  })

  it('rejects missing bearer tokens', async () => {
    const req = { headers: {} } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction

    await requireRenterAuth(req, res, next)

    expect(res.status).toHaveBeenCalledWith(401)
    expect(res.json).toHaveBeenCalledWith({ error: 'unauthenticated', message: 'Renter authentication required', statusCode: 401 })
    expect(next).not.toHaveBeenCalled()
  })

  it('rejects invalid tokens and wrong token types', async () => {
    vi.mocked(jwt.verify).mockReturnValue({ sub: 'emp_1', type: 'employee' } as any)
    const req = { headers: { authorization: 'Bearer employee-token' } } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction

    await requireRenterAuth(req, res, next)

    expect(res.status).toHaveBeenCalledWith(401)
    expect(res.json).toHaveBeenCalledWith({ error: 'invalid_token', message: 'Invalid or expired token', statusCode: 401 })
    expect(prisma.renter.findUnique).not.toHaveBeenCalled()
  })

  it('rejects inactive renters', async () => {
    vi.mocked(jwt.verify).mockReturnValue({ sub: 'renter_1', type: 'renter' } as any)
    vi.mocked(prisma.renter.findUnique).mockResolvedValue({ id: 'renter_1', isActive: false } as any)
    const req = { headers: { authorization: 'Bearer renter-token' } } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction

    await requireRenterAuth(req, res, next)

    expect(res.status).toHaveBeenCalledWith(401)
    expect(res.json).toHaveBeenCalledWith({ error: 'unauthenticated', message: 'Renter account not found or inactive', statusCode: 401 })
    expect(next).not.toHaveBeenCalled()
  })

  it('attaches renterId for active renters', async () => {
    vi.mocked(jwt.verify).mockReturnValue({ sub: 'renter_1', type: 'renter' } as any)
    vi.mocked(prisma.renter.findUnique).mockResolvedValue({ id: 'renter_1', isActive: true } as any)
    const req = { headers: { authorization: 'Bearer renter-token' } } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction

    await requireRenterAuth(req, res, next)

    expect(req.renterId).toBe('renter_1')
    expect(next).toHaveBeenCalledTimes(1)
  })

  it('optional auth never blocks missing or invalid tokens', async () => {
    const noTokenReq = { headers: {} } as Request
    const invalidReq = { headers: { authorization: 'Bearer bad' } } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction
    vi.mocked(jwt.verify).mockImplementation(() => { throw new Error('bad token') })

    await optionalRenterAuth(noTokenReq, res, next)
    await optionalRenterAuth(invalidReq, res, next)

    expect(next).toHaveBeenCalledTimes(2)
  })

  it('optional auth attaches renterId only for renter tokens', async () => {
    vi.mocked(jwt.verify).mockReturnValue({ sub: 'renter_2', type: 'renter' } as any)
    const req = { headers: { authorization: 'Bearer renter-token' } } as Request
    const res = responseStub()
    const next = vi.fn() as NextFunction

    await optionalRenterAuth(req, res, next)

    expect(req.renterId).toBe('renter_2')
    expect(next).toHaveBeenCalledTimes(1)
  })
})
