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

vi.mock('../lib/prisma', () => ({
  prisma: {
    vehicle: {
      findFirstOrThrow: vi.fn(),
      findUniqueOrThrow: vi.fn(),
    },
    reservation: {
      findMany: vi.fn(),
    },
    vehicleCalendarBlock: {
      findMany: vi.fn(),
    },
  },
}))

import { prisma } from '../lib/prisma'
import { getVehicleAvailabilitySummary } from './vehicleAvailabilityService'

const range = {
  startDate: new Date('2026-07-10T10:00:00.000Z'),
  endDate: new Date('2026-07-12T10:00:00.000Z'),
}

describe('getVehicleAvailabilitySummary', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.setSystemTime(new Date('2026-07-01T00:00:00.000Z'))
    vi.mocked(prisma.vehicle.findFirstOrThrow).mockResolvedValue({ status: 'AVAILABLE' } as never)
    vi.mocked(prisma.reservation.findMany).mockResolvedValue([] as never)
    vi.mocked(prisma.vehicleCalendarBlock.findMany).mockResolvedValue([] as never)
  })

  it('returns unavailable immediately when the vehicle itself is out of service', async () => {
    vi.mocked(prisma.vehicle.findFirstOrThrow).mockResolvedValue({ status: 'OUT_OF_SERVICE' } as never)

    const result = await getVehicleAvailabilitySummary('vehicle_1', { range })

    expect(result).toEqual({
      available: false,
      status: 'UNAVAILABLE',
      nextAvailableAt: null,
      blockingReason: 'STATUS',
    })
    expect(prisma.reservation.findMany).not.toHaveBeenCalled()
    expect(prisma.vehicleCalendarBlock.findMany).not.toHaveBeenCalled()
  })

  it('treats overlapping confirmed reservations as reserved and computes the next free instant', async () => {
    vi.mocked(prisma.reservation.findMany).mockResolvedValue([
      { startDate: new Date('2026-07-09T10:00:00.000Z'), endDate: new Date('2026-07-11T10:00:00.000Z') },
      { startDate: new Date('2026-07-11T10:00:00.000Z'), endDate: new Date('2026-07-13T10:00:00.000Z') },
    ] as never)

    const result = await getVehicleAvailabilitySummary('vehicle_1', { range })

    expect(prisma.reservation.findMany).toHaveBeenCalledWith(expect.objectContaining({
      where: expect.objectContaining({
        vehicleId: 'vehicle_1',
        status: { in: ['DRAFT', 'CONFIRMED', 'ACTIVE'] },
        endDate: { gt: range.startDate },
      }),
    }))
    expect(result).toEqual({
      available: false,
      status: 'RESERVED',
      nextAvailableAt: new Date('2026-07-13T10:00:00.000Z'),
      blockingReason: 'RESERVATION',
    })
  })

  it('can ignore draft reservations for availability checks that only care about firm holds', async () => {
    await getVehicleAvailabilitySummary('vehicle_1', { range, includeDraftReservations: false })

    expect(prisma.reservation.findMany).toHaveBeenCalledWith(expect.objectContaining({
      where: expect.objectContaining({ status: { in: ['CONFIRMED', 'ACTIVE'] } }),
    }))
  })

  it('prioritizes maintenance over generic blocks and reservations when several sources overlap', async () => {
    vi.mocked(prisma.reservation.findMany).mockResolvedValue([
      { startDate: new Date('2026-07-10T00:00:00.000Z'), endDate: new Date('2026-07-11T00:00:00.000Z') },
    ] as never)
    vi.mocked(prisma.vehicleCalendarBlock.findMany).mockResolvedValue([
      { startDate: new Date('2026-07-10T00:00:00.000Z'), endDate: new Date('2026-07-12T00:00:00.000Z'), type: 'BLOCK' },
      { startDate: new Date('2026-07-10T00:00:00.000Z'), endDate: new Date('2026-07-13T00:00:00.000Z'), type: 'MAINTENANCE' },
    ] as never)

    const result = await getVehicleAvailabilitySummary('vehicle_1', { range })

    expect(result.status).toBe('MAINTENANCE')
    expect(result.blockingReason).toBe('MAINTENANCE')
    expect(result.nextAvailableAt).toEqual(new Date('2026-07-13T00:00:00.000Z'))
  })

  it('returns available when no source overlaps the requested range', async () => {
    vi.mocked(prisma.reservation.findMany).mockResolvedValue([
      { startDate: new Date('2026-07-01T00:00:00.000Z'), endDate: new Date('2026-07-02T00:00:00.000Z') },
    ] as never)

    const result = await getVehicleAvailabilitySummary('vehicle_1', { range })

    expect(result).toEqual({
      available: true,
      status: 'AVAILABLE',
      nextAvailableAt: null,
      blockingReason: null,
    })
  })
})
