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

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

vi.mock('../../services/notificationService', () => ({
  sendNotification: vi.fn().mockResolvedValue(undefined),
  sendTransactionalEmail: vi.fn().mockResolvedValue(undefined),
}))

vi.mock('./reservation.repo', () => ({
  findByIdSimple: vi.fn(),
  findForLicenseCheck: vi.fn(),
  updateById: vi.fn(),
  updateVehicleStatus: vi.fn(),
  findCustomerById: vi.fn(),
  findCompanyWithBrand: vi.fn(),
  findVehicle: vi.fn(),
  findByIdForCheckout: vi.fn(),
  findForClose: vi.fn(),
  findConflict: vi.fn(),
}))

import { AppError } from '../../http/errors'
import { prisma } from '../../lib/prisma'
import * as repo from './reservation.repo'
import { cancelReservation, checkinReservation, confirmReservation, extendReservation } from './reservation.lifecycle.service'

const validLicenseReservation = {
  customer: { licenseExpiry: new Date('2027-06-09T12:00:00.000Z'), licenseValidationStatus: 'VALID' },
  additionalDrivers: [],
}

describe('reservation.lifecycle.service', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.setSystemTime(new Date('2026-06-09T12:00:00.000Z'))
  })

  it('confirms only draft reservations after license compliance and reserves the vehicle', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({
      id: 'reservation_1',
      status: 'DRAFT',
      vehicleId: 'vehicle_1',
      customerId: 'customer_1',
      renterId: null,
      pickupLocation: 'Airport desk',
      startDate: new Date('2026-06-12T10:00:00.000Z'),
      endDate: new Date('2026-06-15T10:00:00.000Z'),
    } as never)
    vi.mocked(repo.findForLicenseCheck).mockResolvedValue(validLicenseReservation as never)
    vi.mocked(repo.updateById).mockResolvedValue({ id: 'reservation_1', status: 'CONFIRMED' } as never)
    vi.mocked(repo.findCustomerById).mockResolvedValue({ firstName: 'Sara', email: 'sara@example.com' } as never)
    vi.mocked(repo.findCompanyWithBrand).mockResolvedValue({ name: 'Atlas Cars', brand: { displayName: 'Atlas', defaultLocale: 'en' } } as never)
    vi.mocked(repo.findVehicle).mockResolvedValue({ year: 2024, make: 'Toyota', model: 'Yaris' } as never)

    await expect(confirmReservation('reservation_1', 'company_1')).resolves.toEqual({ id: 'reservation_1', status: 'CONFIRMED' })
    expect(repo.updateById).toHaveBeenCalledWith('reservation_1', { status: 'CONFIRMED' })
    expect(repo.updateVehicleStatus).toHaveBeenCalledWith('vehicle_1', 'RESERVED')
  })

  it('blocks check-in when the primary driver license is expired', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({ id: 'reservation_1', status: 'CONFIRMED', vehicleId: 'vehicle_1' } as never)
    vi.mocked(repo.findForLicenseCheck).mockResolvedValue({
      customer: { licenseExpiry: new Date('2026-06-01T12:00:00.000Z'), licenseValidationStatus: 'VALID' },
      additionalDrivers: [],
    } as never)

    await expect(checkinReservation('reservation_1', 'company_1')).rejects.toMatchObject({ error: 'invalid_primary_license' })
    expect(repo.updateById).not.toHaveBeenCalled()
  })

  it('extends active reservations only when the new period is conflict-free and after the current end date', async () => {
    const currentEnd = new Date('2026-06-15T10:00:00.000Z')
    const newEnd = new Date('2026-06-18T10:00:00.000Z')
    vi.mocked(repo.findByIdSimple).mockResolvedValue({
      id: 'reservation_1',
      status: 'ACTIVE',
      vehicleId: 'vehicle_1',
      endDate: currentEnd,
      totalDays: 5,
      dailyRate: 100,
      totalAmount: 500,
      extras: {},
    } as never)
    vi.mocked(repo.findConflict).mockResolvedValue(null as never)
    vi.mocked(prisma.reservation.update).mockResolvedValue({ id: 'reservation_1', totalDays: 8, totalAmount: 800 } as never)

    await extendReservation('reservation_1', 'company_1', newEnd, 'Customer requested extra days')

    expect(repo.findConflict).toHaveBeenCalledWith('vehicle_1', currentEnd, newEnd, 'reservation_1')
    expect(prisma.reservation.update).toHaveBeenCalledWith({
      where: { id: 'reservation_1' },
      data: expect.objectContaining({
        endDate: newEnd,
        totalDays: 8,
        totalAmount: 800,
        extras: expect.objectContaining({ extensions: [expect.objectContaining({ additionalDays: 3, additionalAmount: 300 })] }),
      }),
    })
  })

  it('rejects extensions that overlap another reservation', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({
      id: 'reservation_1', status: 'CONFIRMED', vehicleId: 'vehicle_1', endDate: new Date('2026-06-15'), dailyRate: 100, totalDays: 5, totalAmount: 500, extras: {},
    } as never)
    vi.mocked(repo.findConflict).mockResolvedValue({ id: 'reservation_2' } as never)

    await expect(extendReservation('reservation_1', 'company_1', new Date('2026-06-16'), 'Need more time')).rejects.toBeInstanceOf(AppError)
    expect(prisma.reservation.update).not.toHaveBeenCalled()
  })

  it('cancels cancellable reservations and releases the vehicle back to availability', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue({ id: 'reservation_1', status: 'CONFIRMED', vehicleId: 'vehicle_1' } as never)
    vi.mocked(repo.updateById).mockResolvedValue({ id: 'reservation_1', status: 'CANCELLED' } as never)

    await cancelReservation('reservation_1', 'company_1', 'Customer changed plans')

    expect(repo.updateById).toHaveBeenCalledWith('reservation_1', {
      status: 'CANCELLED',
      cancelReason: 'Customer changed plans',
      cancelledBy: 'COMPANY',
    })
    expect(repo.updateVehicleStatus).toHaveBeenCalledWith('vehicle_1', 'AVAILABLE')
  })
})
