import { describe, it, expect, vi, beforeEach } from 'vitest'
import { AppError } from '../../http/errors'

// ── repo mock ──────────────────────────────────────────────────────────────
vi.mock('./reservation.repo', () => ({
  findMany: vi.fn(),
  findById: vi.fn(),
  findByIdSimple: vi.fn(),
  findByIdWithRelations: vi.fn(),
  findByIdForCheckout: vi.fn(),
  findForLicenseCheck: vi.fn(),
  findForClose: vi.fn(),
  findForInspection: vi.fn(),
  findConflict: vi.fn(),
  create: vi.fn(),
  updateById: vi.fn(),
  findActiveOffer: vi.fn(),
  incrementOfferRedemption: vi.fn(),
  findVehicle: vi.fn(),
  findCustomer: vi.fn(),
  findCustomerById: vi.fn(),
  updateVehicleStatus: vi.fn(),
  findCompanyWithBrand: vi.fn(),
  findBrandLocale: vi.fn(),
  findInspections: vi.fn(),
  findAdditionalDriver: vi.fn(),
  updateAdditionalDriver: vi.fn(),
}))

vi.mock('./reservation.pricing.service', () => ({
  applyPricingRules: vi.fn(),
  calculateUpdatedInsuranceCharge: vi.fn(),
  calculateUpdatedAdditionalDriverCharge: vi.fn(),
}))

vi.mock('./reservation.insurance.service', () => ({
  applyInsurancesToReservation: vi.fn(),
}))

vi.mock('./reservation.additional-driver.service', () => ({
  applyAdditionalDriversToReservation: vi.fn(),
  approveAdditionalDriver: vi.fn(),
}))

vi.mock('../../services/licenseValidationService', () => ({
  validateAndFlagLicense: vi.fn().mockResolvedValue(undefined),
  validateLicense: vi.fn(),
}))

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

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

vi.mock('./reservation.presenter', () => ({
  serializeReservationForDashboard: vi.fn((r) => r),
  parseReservationExtras: vi.fn(() => ({})),
  buildReservationWorkflow: vi.fn(),
  normalizeOptionalString: vi.fn((s) => s),
}))

import * as repo from './reservation.repo'
import * as pricingService from './reservation.pricing.service'
import * as insuranceService from './reservation.insurance.service'
import * as additionalDriverService from './reservation.additional-driver.service'
import { validateLicense } from '../../services/licenseValidationService'
import { sendNotification } from '../../services/notificationService'
import { buildReservationWorkflow } from './reservation.presenter'
import { createReservation } from './reservation.service'
import { confirmReservation, checkinReservation, checkoutReservation, closeReservation } from './reservation.lifecycle.service'
import { approveAdditionalDriver } from './reservation.additional-driver.service'

const COMPANY = 'company-1'
const RES_ID  = 'reservation-1'

function makeVehicle(overrides: object = {}) {
  return { id: 'vehicle-1', dailyRate: 100, status: 'AVAILABLE', isPublished: true, mileage: 50000, ...overrides }
}

function makeReservation(overrides: object = {}) {
  return {
    id: RES_ID,
    companyId: COMPANY,
    vehicleId: 'vehicle-1',
    customerId: 'customer-1',
    status: 'DRAFT',
    startDate: new Date('2025-06-01'),
    endDate: new Date('2025-06-04'),
    totalDays: 3,
    dailyRate: 100,
    depositAmount: 0,
    discountAmount: 0,
    totalAmount: 300,
    additionalDrivers: [],
    insurances: [],
    customer: { firstName: 'Ali', lastName: 'Ben', email: 'ali@test.com', licenseExpiry: null, licenseValidationStatus: 'APPROVED' },
    vehicle: { year: 2022, make: 'Toyota', model: 'Camry' },
    extras: {},
    ...overrides,
  }
}

beforeEach(() => {
  vi.clearAllMocks()
})

// ────────────────────────────────────────────────────────────────────────────
describe('createReservation', () => {
  it('creates a reservation and returns it', async () => {
    vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
    vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
    vi.mocked(repo.findConflict).mockResolvedValue(null)
    vi.mocked(repo.findActiveOffer).mockResolvedValue(null)
    vi.mocked(pricingService.applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
    vi.mocked(repo.create).mockResolvedValue(makeReservation() as any)

    const result = await createReservation(COMPANY, {
      vehicleId: 'vehicle-1',
      customerId: 'customer-1',
      startDate: '2025-06-01T00:00:00.000Z',
      endDate: '2025-06-04T00:00:00.000Z',
      depositAmount: 0,
      selectedInsurancePolicyIds: [],
      additionalDrivers: [],
    })

    expect(repo.create).toHaveBeenCalledOnce()
    expect(result.id).toBe(RES_ID)
  })

  it('throws conflict error when vehicle is unavailable', async () => {
    vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
    vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
    vi.mocked(repo.findConflict).mockResolvedValue({ id: 'other-res' } as any)

    await expect(
      createReservation(COMPANY, {
        vehicleId: 'vehicle-1',
        customerId: 'customer-1',
        startDate: '2025-06-01T00:00:00.000Z',
        endDate: '2025-06-04T00:00:00.000Z',
        selectedInsurancePolicyIds: [],
        additionalDrivers: [],
      }),
    ).rejects.toThrow('Vehicle is not available for the selected dates')

    expect(repo.create).not.toHaveBeenCalled()
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('confirmReservation', () => {
  it('confirms a DRAFT reservation', async () => {
    const res = makeReservation({ status: 'DRAFT', pickupLocation: 'Casablanca Airport Terminal 1' })
    vi.mocked(repo.findByIdSimple).mockResolvedValue(res as any)
    vi.mocked(repo.findForLicenseCheck).mockResolvedValue({
      ...res,
      customer: { ...res.customer, licenseExpiry: null, licenseValidationStatus: 'APPROVED' },
      additionalDrivers: [],
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
    vi.mocked(repo.updateById).mockResolvedValue({ ...res, status: 'CONFIRMED' } as any)
    vi.mocked(repo.findCustomerById).mockResolvedValue({ email: 'ali@test.com', firstName: 'Ali' } as any)
    vi.mocked(repo.findCompanyWithBrand).mockResolvedValue({ name: 'TestCo', brand: { displayName: 'TestCo', defaultLocale: 'fr' } } as any)
    vi.mocked(repo.findVehicle).mockResolvedValue({ year: 2022, make: 'Toyota', model: 'Camry' } as any)

    const result = await confirmReservation(RES_ID, COMPANY)

    expect(repo.updateById).toHaveBeenCalledWith(RES_ID, { status: 'CONFIRMED' })
    expect(sendNotification).toHaveBeenCalledWith(expect.objectContaining({
      templateKey: 'booking.confirmed',
      templateVariables: expect.objectContaining({
        pickupLocation: 'Casablanca Airport Terminal 1',
        startDate: expect.objectContaining({
          type: 'date',
          value: res.startDate,
        }),
        endDate: expect.objectContaining({
          type: 'date',
          value: res.endDate,
        }),
      }),
    }))
    expect((result as any).status).toBe('CONFIRMED')
  })

  it('rejects non-DRAFT reservation', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue(makeReservation({ status: 'CONFIRMED' }) as any)

    await expect(confirmReservation(RES_ID, COMPANY)).rejects.toThrow('Only DRAFT reservations can be confirmed')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('checkinReservation', () => {
  it('transitions CONFIRMED → ACTIVE and marks vehicle RENTED', async () => {
    const res = makeReservation({ status: 'CONFIRMED' })
    vi.mocked(repo.findByIdSimple).mockResolvedValue(res as any)
    vi.mocked(repo.findForLicenseCheck).mockResolvedValue({
      ...res,
      customer: { ...res.customer, licenseExpiry: null, licenseValidationStatus: 'APPROVED' },
      additionalDrivers: [],
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
    vi.mocked(repo.updateById).mockResolvedValue({ ...res, status: 'ACTIVE' } as any)
    vi.mocked(repo.updateVehicleStatus).mockResolvedValue(undefined as any)

    await checkinReservation(RES_ID, COMPANY, 55000)

    expect(repo.updateById).toHaveBeenCalledWith(RES_ID, expect.objectContaining({ status: 'ACTIVE', checkInMileage: 55000 }))
    expect(repo.updateVehicleStatus).toHaveBeenCalledWith('vehicle-1', 'RENTED')
  })

  it('rejects non-CONFIRMED reservation', async () => {
    vi.mocked(repo.findByIdSimple).mockResolvedValue(makeReservation({ status: 'ACTIVE' }) as any)

    await expect(checkinReservation(RES_ID, COMPANY)).rejects.toThrow('Only CONFIRMED reservations can be checked in')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('checkoutReservation', () => {
  it('transitions ACTIVE → COMPLETED and marks vehicle RETURNED', async () => {
    const res = makeReservation({ status: 'ACTIVE' })
    vi.mocked(repo.findByIdForCheckout).mockResolvedValue(res as any)
    vi.mocked(repo.updateById).mockResolvedValue({ ...res, status: 'COMPLETED' } as any)
    vi.mocked(repo.updateVehicleStatus).mockResolvedValue(undefined as any)
    vi.mocked(repo.findCustomerById).mockResolvedValue({ email: 'ali@test.com', firstName: 'Ali' } as any)
    vi.mocked(repo.findCompanyWithBrand).mockResolvedValue({ name: 'TestCo', brand: { displayName: 'TestCo', defaultLocale: 'fr' } } as any)

    await checkoutReservation(RES_ID, COMPANY, 58000)

    expect(repo.updateById).toHaveBeenCalledWith(RES_ID, expect.objectContaining({ status: 'COMPLETED', checkOutMileage: 58000 }))
    expect(repo.updateVehicleStatus).toHaveBeenCalledWith('vehicle-1', 'RETURNED', 58000)
  })

  it('rejects non-ACTIVE reservation', async () => {
    vi.mocked(repo.findByIdForCheckout).mockResolvedValue(makeReservation({ status: 'CONFIRMED' }) as any)

    await expect(checkoutReservation(RES_ID, COMPANY)).rejects.toThrow('Only ACTIVE reservations can be checked out')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('closeReservation', () => {
  it('closes a COMPLETED reservation', async () => {
    const res = makeReservation({ status: 'COMPLETED' })
    vi.mocked(repo.findForClose).mockResolvedValue(res as any)
    vi.mocked(buildReservationWorkflow).mockReturnValue({ closed: false } as any)
    const { prisma } = await import('../../lib/prisma')
    vi.mocked(prisma.reservation.update).mockResolvedValue({ ...res, extras: { reservationClosedBy: 'Admin User' } } as any)

    await closeReservation(RES_ID, COMPANY, 'Admin User')

    expect(prisma.reservation.update).toHaveBeenCalledWith(
      expect.objectContaining({ where: { id: RES_ID } }),
    )
  })

  it('rejects already closed reservation', async () => {
    vi.mocked(repo.findForClose).mockResolvedValue(makeReservation({ status: 'COMPLETED' }) as any)
    vi.mocked(buildReservationWorkflow).mockReturnValue({ closed: true } as any)

    await expect(closeReservation(RES_ID, COMPANY, 'Admin')).rejects.toThrow('already closed')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('approveAdditionalDriver', () => {
  it('calls through to the additional driver service', async () => {
    vi.mocked(additionalDriverService.approveAdditionalDriver).mockResolvedValue({ id: 'driver-1', approvedAt: new Date() } as any)

    const result = await approveAdditionalDriver(RES_ID, 'driver-1', COMPANY, true, 'Looks good', 'Manager')

    expect(additionalDriverService.approveAdditionalDriver).toHaveBeenCalledWith(RES_ID, 'driver-1', COMPANY, true, 'Looks good', 'Manager')
    expect((result as any).id).toBe('driver-1')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('pricing rule application', () => {
  it('applies pricing rules and adjusts totalAmount', async () => {
    vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle({ dailyRate: 100 }))
    vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
    vi.mocked(repo.findConflict).mockResolvedValue(null)
    vi.mocked(repo.findActiveOffer).mockResolvedValue(null)
    vi.mocked(pricingService.applyPricingRules).mockResolvedValue({ applied: [{ id: 'rule-1' }], total: 50 })
    vi.mocked(repo.create).mockResolvedValue(makeReservation({ totalAmount: 350 }) as any)

    await createReservation(COMPANY, {
      vehicleId: 'vehicle-1',
      customerId: 'customer-1',
      startDate: '2025-06-01T00:00:00.000Z',
      endDate: '2025-06-04T00:00:00.000Z',
      selectedInsurancePolicyIds: [],
      additionalDrivers: [],
    })

    expect(pricingService.applyPricingRules).toHaveBeenCalledOnce()
    const createCall = vi.mocked(repo.create).mock.calls[0][0] as any
    expect(createCall.pricingRulesApplied).toEqual([{ id: 'rule-1' }])
    expect(createCall.totalAmount).toBe(350)
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('insurance application', () => {
  it('calls applyInsurancesToReservation when insurance IDs are provided', async () => {
    vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
    vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
    vi.mocked(repo.findConflict).mockResolvedValue(null)
    vi.mocked(repo.findActiveOffer).mockResolvedValue(null)
    vi.mocked(pricingService.applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
    vi.mocked(repo.create).mockResolvedValue(makeReservation() as any)
    vi.mocked(insuranceService.applyInsurancesToReservation).mockResolvedValue(undefined as any)

    await createReservation(COMPANY, {
      vehicleId: 'vehicle-1',
      customerId: 'customer-1',
      startDate: '2025-06-01T00:00:00.000Z',
      endDate: '2025-06-04T00:00:00.000Z',
      selectedInsurancePolicyIds: ['policy-1', 'policy-2'],
      additionalDrivers: [],
    })

    expect(insuranceService.applyInsurancesToReservation).toHaveBeenCalledWith(
      RES_ID, COMPANY, ['policy-1', 'policy-2'], 3, 300,
    )
  })

  it('skips insurance call when no IDs provided', async () => {
    vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
    vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
    vi.mocked(repo.findConflict).mockResolvedValue(null)
    vi.mocked(repo.findActiveOffer).mockResolvedValue(null)
    vi.mocked(pricingService.applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
    vi.mocked(repo.create).mockResolvedValue(makeReservation() as any)

    await createReservation(COMPANY, {
      vehicleId: 'vehicle-1',
      customerId: 'customer-1',
      startDate: '2025-06-01T00:00:00.000Z',
      endDate: '2025-06-04T00:00:00.000Z',
      selectedInsurancePolicyIds: [],
      additionalDrivers: [],
    })

    expect(insuranceService.applyInsurancesToReservation).not.toHaveBeenCalled()
  })
})
