import { Router } from 'express'
import { z } from 'zod'
import { prisma } from '../lib/prisma'
import { requireCompanyAuth } from '../middleware/requireCompanyAuth'
import { requireTenant } from '../middleware/requireTenant'
import { requireSubscription } from '../middleware/requireSubscription'
import { applyAdditionalDriversToReservation } from '../services/additionalDriverService'
import { applyInsurancesToReservation } from '../services/insuranceService'
import { applyPricingRules } from '../services/pricingRuleService'
import { validateAndFlagLicense, validateLicense } from '../services/licenseValidationService'
import { sendNotification } from '../services/notificationService'

const router = Router()
router.use(requireCompanyAuth, requireTenant, requireSubscription)

const additionalDriverSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email().optional(),
  phone: z.string().optional(),
  driverLicense: z.string().min(1),
  licenseExpiry: z.string().datetime().optional(),
  licenseIssuedAt: z.string().datetime().optional(),
  dateOfBirth: z.string().datetime().optional(),
  nationality: z.string().optional(),
})

const inspectionSchema = z.object({
  mileage: z.number().int().optional(),
  fuelLevel: z.enum(['FULL', 'SEVEN_EIGHTHS', 'THREE_QUARTERS', 'FIVE_EIGHTHS', 'HALF', 'THREE_EIGHTHS', 'QUARTER', 'ONE_EIGHTH', 'EMPTY']),
  fuelCharge: z.number().int().min(0).optional(),
  generalCondition: z.string().optional(),
  employeeNotes: z.string().optional(),
  customerAgreed: z.boolean().default(false),
  damagePoints: z.array(
    z.object({
      viewType: z.enum(['TOP', 'FRONT', 'REAR', 'LEFT', 'RIGHT']),
      x: z.number(),
      y: z.number(),
      damageType: z.enum(['SCRATCH', 'DENT', 'CRACK', 'CHIP', 'MISSING', 'STAIN', 'OTHER']),
      severity: z.enum(['MINOR', 'MODERATE', 'MAJOR']).default('MINOR'),
      description: z.string().optional(),
      isPreExisting: z.boolean().default(false),
    }),
  ).default([]),
})

const createSchema = z.object({
  vehicleId:      z.string().cuid(),
  customerId:     z.string().cuid(),
  startDate:      z.string().datetime(),
  endDate:        z.string().datetime(),
  pickupLocation: z.string().optional(),
  returnLocation: z.string().optional(),
  offerId:        z.string().cuid().optional(),
  promoCodeUsed:  z.string().optional(),
  depositAmount:  z.number().int().min(0).default(0),
  notes:          z.string().optional(),
  selectedInsurancePolicyIds: z.array(z.string()).default([]),
  additionalDrivers: z.array(additionalDriverSchema).default([]),
})

async function assertReservationLicenseCompliance(reservationId: string, companyId: string) {
  const reservation = await prisma.reservation.findFirstOrThrow({
    where: { id: reservationId, companyId },
    include: { customer: true, additionalDrivers: true },
  })

  const customerLicense = validateLicense(reservation.customer.licenseExpiry)
  const customerDenied = ['DENIED', 'EXPIRED'].includes(reservation.customer.licenseValidationStatus)
  if (customerDenied || customerLicense.status === 'EXPIRED') {
    throw Object.assign(new Error('Primary driver license is not valid for this reservation'), {
      statusCode: 400,
      code: 'invalid_primary_license',
    })
  }

  if (customerLicense.requiresApproval && reservation.customer.licenseValidationStatus !== 'APPROVED') {
    throw Object.assign(new Error('Primary driver license requires manager approval before this reservation can proceed'), {
      statusCode: 400,
      code: 'primary_license_requires_approval',
    })
  }

  const blockedDriver = reservation.additionalDrivers.find((driver) => {
    if (driver.licenseExpired) return true
    return driver.requiresApproval && !driver.approvedAt
  })

  if (blockedDriver) {
    throw Object.assign(new Error(`Additional driver ${blockedDriver.firstName} ${blockedDriver.lastName} requires approval before this reservation can proceed`), {
      statusCode: 400,
      code: 'additional_driver_requires_approval',
    })
  }
}

router.get('/', async (req, res, next) => {
  try {
    const { status, vehicleId, source, startDate, endDate, page = '1', pageSize = '20' } = req.query as Record<string, string>
    const where: any = { companyId: req.companyId }
    if (status)    where.status = status
    if (vehicleId) where.vehicleId = vehicleId
    if (source)    where.source = source
    if (startDate) where.startDate = { gte: new Date(startDate) }
    if (endDate)   where.endDate   = { lte: new Date(endDate) }

    const [reservations, total] = await Promise.all([
      prisma.reservation.findMany({
        where,
        include: { vehicle: true, customer: true },
        skip: (parseInt(page) - 1) * parseInt(pageSize),
        take: parseInt(pageSize),
        orderBy: { createdAt: 'desc' },
      }),
      prisma.reservation.count({ where }),
    ])

    res.json({ data: reservations, total, page: parseInt(page), pageSize: parseInt(pageSize), totalPages: Math.ceil(total / parseInt(pageSize)) })
  } catch (err) { next(err) }
})

router.post('/', async (req, res, next) => {
  try {
    const body = createSchema.parse(req.body)

    // Validate vehicle belongs to this company
    const vehicle = await prisma.vehicle.findFirstOrThrow({ where: { id: body.vehicleId, companyId: req.companyId } })
    const customer = await prisma.customer.findFirstOrThrow({ where: { id: body.customerId, companyId: req.companyId } })

    const start = new Date(body.startDate)
    const end   = new Date(body.endDate)
    const totalDays = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))

    // Check availability
    const conflict = await prisma.reservation.findFirst({
      where: { vehicleId: body.vehicleId, status: { in: ['CONFIRMED', 'ACTIVE'] }, startDate: { lt: end }, endDate: { gt: start } },
    })
    if (conflict) return res.status(409).json({ error: 'vehicle_unavailable', message: 'Vehicle is not available for the selected dates', statusCode: 409 })

    let discountAmount = 0
    let offerId: string | null = body.offerId ?? null

    if (body.promoCodeUsed) {
      const offer = await prisma.offer.findFirst({
        where: { companyId: req.companyId, promoCode: body.promoCodeUsed, isActive: true, validFrom: { lte: new Date() }, validUntil: { gte: new Date() } },
      })
      if (offer) {
        offerId = offer.id
        const base = vehicle.dailyRate * totalDays
        if (offer.type === 'PERCENTAGE') discountAmount = Math.round(base * offer.discountValue / 100)
        else if (offer.type === 'FIXED_AMOUNT') discountAmount = offer.discountValue
        else if (offer.type === 'FREE_DAY')  discountAmount = vehicle.dailyRate * offer.discountValue
        await prisma.offer.update({ where: { id: offer.id }, data: { redemptionCount: { increment: 1 } } })
      }
    }

    const baseAmount = vehicle.dailyRate * totalDays
    const { applied, total: pricingTotal } = await applyPricingRules(req.companyId, body.customerId, body.additionalDrivers as any[], vehicle.dailyRate, totalDays)
    const totalAmount = baseAmount - discountAmount + pricingTotal + body.depositAmount

    const reservation = await prisma.reservation.create({
      data: {
        companyId:      req.companyId,
        vehicleId:      body.vehicleId,
        customerId:     body.customerId,
        startDate:      start,
        endDate:        end,
        pickupLocation: body.pickupLocation ?? null,
        returnLocation: body.returnLocation ?? null,
        offerId,
        promoCodeUsed:  body.promoCodeUsed ?? null,
        source:         'DASHBOARD',
        dailyRate:      vehicle.dailyRate,
        discountAmount,
        totalDays,
        totalAmount,
        depositAmount:  body.depositAmount,
        notes:          body.notes ?? null,
        pricingRulesApplied: applied,
        pricingRulesTotal:   pricingTotal,
      },
      include: { vehicle: true, customer: true },
    })

    if (body.selectedInsurancePolicyIds.length > 0) {
      await applyInsurancesToReservation(reservation.id, req.companyId, body.selectedInsurancePolicyIds, totalDays, baseAmount)
    }

    if (body.additionalDrivers.length > 0) {
      await applyAdditionalDriversToReservation(reservation.id, req.companyId, body.additionalDrivers, totalDays)
    }

    // Validate customer license
    await validateAndFlagLicense(body.customerId).catch(() => null)

    res.status(201).json({ data: reservation })
  } catch (err) { next(err) }
})

router.get('/:id', async (req, res, next) => {
  try {
    const reservation = await prisma.reservation.findFirstOrThrow({
      where: { id: req.params.id, companyId: req.companyId },
      include: { vehicle: true, customer: true, insurances: true, additionalDrivers: true, rentalPayments: true, damageReports: true },
    })
    res.json({ data: reservation })
  } catch (err) { next(err) }
})

router.post('/:id/confirm', async (req, res, next) => {
  try {
    const reservation = await prisma.reservation.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
    if (reservation.status !== 'DRAFT') return res.status(400).json({ error: 'invalid_status', message: 'Only DRAFT reservations can be confirmed', statusCode: 400 })
    await assertReservationLicenseCompliance(reservation.id, req.companyId)

    const updated = await prisma.reservation.update({ where: { id: req.params.id }, data: { status: 'CONFIRMED' } })

    // Notify
    const customer = await prisma.customer.findUniqueOrThrow({ where: { id: reservation.customerId } })
    await sendNotification({ type: 'BOOKING_CONFIRMED', title: 'Booking Confirmed', body: `Your booking has been confirmed.`, companyId: req.companyId, email: customer.email, channels: ['EMAIL', 'IN_APP'] }).catch(() => null)

    res.json({ data: updated })
  } catch (err) { next(err) }
})

router.post('/:id/checkin', async (req, res, next) => {
  try {
    const { mileage } = z.object({ mileage: z.number().int().optional() }).parse(req.body)
    const reservation = await prisma.reservation.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
    if (reservation.status !== 'CONFIRMED') return res.status(400).json({ error: 'invalid_status', message: 'Only CONFIRMED reservations can be checked in', statusCode: 400 })
    await assertReservationLicenseCompliance(reservation.id, req.companyId)
    const updated = await prisma.reservation.update({ where: { id: req.params.id }, data: { status: 'ACTIVE', checkedInAt: new Date(), checkInMileage: mileage ?? null } })
    await prisma.vehicle.update({ where: { id: reservation.vehicleId }, data: { status: 'RENTED' } })
    res.json({ data: updated })
  } catch (err) { next(err) }
})

router.post('/:id/checkout', async (req, res, next) => {
  try {
    const { mileage } = z.object({ mileage: z.number().int().optional() }).parse(req.body)
    const reservation = await prisma.reservation.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
    if (reservation.status !== 'ACTIVE') return res.status(400).json({ error: 'invalid_status', message: 'Only ACTIVE reservations can be checked out', statusCode: 400 })
    const updated = await prisma.reservation.update({ where: { id: req.params.id }, data: { status: 'COMPLETED', checkedOutAt: new Date(), checkOutMileage: mileage ?? null } })
    await prisma.vehicle.update({ where: { id: reservation.vehicleId }, data: { status: 'AVAILABLE', mileage: mileage ?? undefined } })

    const customer = await prisma.customer.findUniqueOrThrow({ where: { id: reservation.customerId } })
    await sendNotification({ type: 'REVIEW_REQUEST', title: 'How was your rental?', body: 'Please leave a review for your recent rental.', companyId: req.companyId, email: customer.email, channels: ['EMAIL'] }).catch(() => null)

    res.json({ data: updated })
  } catch (err) { next(err) }
})

router.post('/:id/cancel', async (req, res, next) => {
  try {
    const { reason } = z.object({ reason: z.string().optional() }).parse(req.body)
    const reservation = await prisma.reservation.findFirstOrThrow({ where: { id: req.params.id, companyId: req.companyId } })
    if (['COMPLETED', 'CANCELLED'].includes(reservation.status)) {
      return res.status(400).json({ error: 'invalid_status', message: 'Reservation cannot be cancelled', statusCode: 400 })
    }
    const updated = await prisma.reservation.update({ where: { id: req.params.id }, data: { status: 'CANCELLED', cancelReason: reason ?? null, cancelledBy: 'COMPANY' } })
    if (['CONFIRMED', 'ACTIVE'].includes(reservation.status)) {
      await prisma.vehicle.update({ where: { id: reservation.vehicleId }, data: { status: 'AVAILABLE' } })
    }
    res.json({ data: updated })
  } catch (err) { next(err) }
})

router.get('/:id/billing', async (req, res, next) => {
  try {
    const reservation = await prisma.reservation.findFirstOrThrow({
      where: { id: req.params.id, companyId: req.companyId },
      include: { vehicle: true, insurances: true, additionalDrivers: true },
    })

    const baseAmount = reservation.dailyRate * reservation.totalDays
    const lineItems = [
      { description: `${reservation.vehicle.year} ${reservation.vehicle.make} ${reservation.vehicle.model} — ${reservation.totalDays} day(s)`, qty: reservation.totalDays, unitPrice: reservation.dailyRate, total: baseAmount, category: 'RENTAL' },
      ...reservation.insurances.map((ins: (typeof reservation.insurances)[number]) => ({ description: ins.policyName, qty: ins.chargeType === 'PER_DAY' ? reservation.totalDays : 1, unitPrice: ins.chargeType === 'PER_DAY' ? ins.chargeValue : ins.totalCharge, total: ins.totalCharge, category: 'INSURANCE' })),
      ...reservation.additionalDrivers.filter((d: (typeof reservation.additionalDrivers)[number]) => d.totalCharge > 0).map((d: (typeof reservation.additionalDrivers)[number]) => ({ description: `Additional Driver — ${d.firstName} ${d.lastName}`, qty: 1, unitPrice: d.totalCharge, total: d.totalCharge, category: 'ADDITIONAL_DRIVER' })),
      ...(reservation.depositAmount > 0 ? [{ description: 'Security Deposit (refundable)', qty: 1, unitPrice: reservation.depositAmount, total: reservation.depositAmount, category: 'DEPOSIT' }] : []),
    ]

    const grandTotal = lineItems.reduce((s, i) => s + i.total, 0) - reservation.discountAmount

    res.json({ data: { lineItems, discountAmount: reservation.discountAmount, pricingRulesApplied: reservation.pricingRulesApplied, pricingRulesTotal: reservation.pricingRulesTotal, grandTotal } })
  } catch (err) { next(err) }
})

router.get('/:id/inspections', async (req, res, next) => {
  try {
    const inspections = await prisma.damageInspection.findMany({
      where: { reservationId: req.params.id, companyId: req.companyId },
      include: { damagePoints: true },
      orderBy: { inspectedAt: 'asc' },
    })
    res.json({ data: inspections })
  } catch (err) { next(err) }
})

router.put('/:id/inspections/:type', async (req, res, next) => {
  try {
    const type = z.enum(['CHECKIN', 'CHECKOUT']).parse(req.params.type.toUpperCase())
    const body = inspectionSchema.parse(req.body)
    const reservation = await prisma.reservation.findFirstOrThrow({
      where: { id: req.params.id, companyId: req.companyId },
      include: { vehicle: true, customer: true },
    })

    const inspection = await prisma.damageInspection.upsert({
      where: { reservationId_type: { reservationId: reservation.id, type } },
      update: {
        mileage: body.mileage ?? null,
        fuelLevel: body.fuelLevel,
        fuelCharge: body.fuelCharge ?? null,
        generalCondition: body.generalCondition ?? null,
        employeeNotes: body.employeeNotes ?? null,
        customerAgreed: body.customerAgreed,
        inspectedBy: `${req.employee.firstName} ${req.employee.lastName}`,
        inspectedAt: new Date(),
        damagePoints: {
          deleteMany: {},
          create: body.damagePoints,
        },
      },
      create: {
        reservationId: reservation.id,
        companyId: req.companyId,
        type,
        mileage: body.mileage ?? null,
        fuelLevel: body.fuelLevel,
        fuelCharge: body.fuelCharge ?? null,
        generalCondition: body.generalCondition ?? null,
        employeeNotes: body.employeeNotes ?? null,
        customerAgreed: body.customerAgreed,
        inspectedBy: `${req.employee.firstName} ${req.employee.lastName}`,
        damagePoints: {
          create: body.damagePoints,
        },
      },
      include: { damagePoints: true },
    })

    await prisma.damageReport.upsert({
      where: { reservationId_type: { reservationId: reservation.id, type } },
      update: {
        damages: body.damagePoints.map((point) => ({
          viewType: point.viewType,
          x: point.x,
          y: point.y,
          damageType: point.damageType,
          severity: point.severity,
          note: point.description ?? '',
          isPreExisting: point.isPreExisting,
        })),
        fuelLevel: body.fuelLevel,
        mileage: body.mileage ?? null,
        inspectedAt: new Date(),
        inspectedBy: `${req.employee.firstName} ${req.employee.lastName}`,
        customerSignedAt: body.customerAgreed ? new Date() : null,
        customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`,
      },
      create: {
        reservationId: reservation.id,
        companyId: req.companyId,
        type,
        damages: body.damagePoints.map((point) => ({
          viewType: point.viewType,
          x: point.x,
          y: point.y,
          damageType: point.damageType,
          severity: point.severity,
          note: point.description ?? '',
          isPreExisting: point.isPreExisting,
        })),
        photos: [],
        fuelLevel: body.fuelLevel,
        mileage: body.mileage ?? null,
        inspectedAt: new Date(),
        inspectedBy: `${req.employee.firstName} ${req.employee.lastName}`,
        customerSignedAt: body.customerAgreed ? new Date() : null,
        customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`,
      },
    })

    await prisma.reservation.update({
      where: { id: reservation.id },
      data: type === 'CHECKIN'
        ? {
            checkInMileage: body.mileage ?? null,
            checkInFuelLevel: body.fuelLevel,
          }
        : {
            checkOutMileage: body.mileage ?? null,
            checkOutFuelLevel: body.fuelLevel,
          },
    })

    res.json({ data: inspection })
  } catch (err) { next(err) }
})

router.patch('/:id/additional-drivers/:driverId/approval', async (req, res, next) => {
  try {
    const { approved, note } = z.object({ approved: z.boolean(), note: z.string().optional() }).parse(req.body)
    const driver = await prisma.additionalDriver.findFirstOrThrow({
      where: { id: req.params.driverId, reservationId: req.params.id, companyId: req.companyId },
    })

    const updated = await prisma.additionalDriver.update({
      where: { id: driver.id },
      data: {
        approvedAt: approved ? new Date() : null,
        approvedBy: approved ? `${req.employee.firstName} ${req.employee.lastName}` : null,
        approvalNote: note ?? driver.approvalNote,
      },
    })

    res.json({ data: updated })
  } catch (err) { next(err) }
})

export default router
