import crypto from 'crypto'
import { prisma } from '../../lib/prisma'
import { AppError } from '../../http/errors'
import { validateLicense } from '../../services/licenseValidationService'
import { sendNotification, sendTransactionalEmail } from '../../services/notificationService'
import { reviewRequestEmail, type Lang } from '../../lib/emailTranslations'
import { coerceNotificationLocale } from '../../services/notificationLocalizationService'
import { buildBookingRequestProgress, buildReservationWorkflow, parseReservationExtras, serializeReservationForDashboard } from './reservation.presenter'
import * as repo from './reservation.repo'

function buildPickupAddress(value: unknown) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return ''

  const address = value as Record<string, unknown>
  return [
    typeof address.streetAddress === 'string' ? address.streetAddress.trim() : '',
    typeof address.city === 'string' ? address.city.trim() : '',
    typeof address.country === 'string' ? address.country.trim() : '',
  ].filter(Boolean).join(', ')
}

async function assertLicenseCompliance(reservationId: string, companyId: string) {
  const reservation = await repo.findForLicenseCheck(reservationId, companyId)

  const customerLicense = validateLicense(reservation.customer.licenseExpiry)
  const customerDenied  = ['DENIED', 'EXPIRED'].includes(reservation.customer.licenseValidationStatus)
  if (customerDenied || customerLicense.status === 'EXPIRED') {
    throw new AppError('Primary driver license is not valid for this reservation', 400, 'invalid_primary_license')
  }
  if (customerLicense.requiresApproval && reservation.customer.licenseValidationStatus !== 'APPROVED') {
    throw new AppError('Primary driver license requires manager approval before this reservation can proceed', 400, 'primary_license_requires_approval')
  }

  const blockedDriver = reservation.additionalDrivers.find((d: any) => {
    if (d.licenseExpired) return true
    return d.requiresApproval && !d.approvedAt
  })
  if (blockedDriver) {
    throw new AppError(
      `Additional driver ${blockedDriver.firstName} ${blockedDriver.lastName} requires approval before this reservation can proceed`,
      400,
      'additional_driver_requires_approval',
    )
  }
}

export async function confirmReservation(id: string, companyId: string) {
  const reservation = await repo.findByIdSimple(id, companyId)
  if (reservation.status !== 'DRAFT') throw new AppError('Only DRAFT reservations can be confirmed', 400, 'invalid_status')

  const updated = await repo.updateById(id, { status: 'CONFIRMED' })
  await repo.updateVehicleStatus(reservation.vehicleId, 'RESERVED')

  const [customer, company, vehicle] = await Promise.all([
    repo.findCustomerById(reservation.customerId),
    repo.findCompanyWithBrand(companyId),
    repo.findVehicle(reservation.vehicleId, companyId),
  ])
  const fallbackLocale = coerceNotificationLocale(company?.brand?.defaultLocale)
  const pickupLocation = reservation.pickupLocation?.trim() || buildPickupAddress(company?.address)
  await sendNotification({
    type: 'BOOKING_CONFIRMED',
    companyId,
    renterId: reservation.renterId ?? undefined,
    email: customer.email,
    channels: ['EMAIL', 'IN_APP'],
    locale: reservation.renterId ? undefined : fallbackLocale,
    templateKey: 'booking.confirmed',
    templateVariables: {
      firstName: customer.firstName,
      companyName: company?.brand?.displayName ?? company?.name ?? 'RentalDriveGo',
      startDate: {
        type: 'date',
        value: reservation.startDate,
        options: {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
          hour: '2-digit',
          minute: '2-digit',
        },
      },
      endDate: {
        type: 'date',
        value: reservation.endDate,
        options: {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
          hour: '2-digit',
          minute: '2-digit',
        },
      },
      pickupLocation,
      vehicleName: `${vehicle.year} ${vehicle.make} ${vehicle.model}`,
    },
  }).catch(() => null)

  if (reservation.source === 'CARPLACE') {
    const progress = buildBookingRequestProgress({
      source: reservation.source,
      status: 'CONFIRMED',
      paymentStatus: updated.paymentStatus,
      customer,
    })
    const reminderChannels = reservation.renterId ? ['EMAIL', 'IN_APP'] as const : ['EMAIL'] as const

    if (progress.documentsRequired) {
      await sendNotification({
        type: 'DOCUMENTS_REQUIRED',
        companyId,
        renterId: reservation.renterId ?? undefined,
        email: customer.email,
        channels: [...reminderChannels],
        locale: reservation.renterId ? undefined : fallbackLocale,
        title: 'Driver documents still needed',
        body: `${company?.brand?.displayName ?? company?.name ?? 'The rental company'} confirmed availability. Please send the remaining driver documents so the booking can move forward.`,
        data: { reservationId: reservation.id, missingItems: progress.documentsMissing },
      }).catch(() => null)
    }

    if (progress.paymentRequired) {
      await sendNotification({
        type: 'PAYMENT_REQUIRED',
        companyId,
        renterId: reservation.renterId ?? undefined,
        email: customer.email,
        channels: [...reminderChannels],
        locale: reservation.renterId ? undefined : fallbackLocale,
        title: 'Payment or deposit is still pending',
        body: `${company?.brand?.displayName ?? company?.name ?? 'The rental company'} accepted your request. Complete any required payment or deposit to finalize the booking.`,
        data: { reservationId: reservation.id, paymentStatus: updated.paymentStatus },
      }).catch(() => null)
    }
  }

  return updated
}

export async function checkinReservation(id: string, companyId: string, mileage?: number) {
  const reservation = await repo.findByIdSimple(id, companyId)
  if (reservation.status !== 'CONFIRMED') throw new AppError('Only CONFIRMED reservations can be checked in', 400, 'invalid_status')
  await assertLicenseCompliance(reservation.id, companyId)

  const updated = await repo.updateById(id, {
    status: 'ACTIVE',
    checkedInAt: new Date(),
    checkInMileage: mileage ?? null,
  })
  await repo.updateVehicleStatus(reservation.vehicleId, 'RENTED')
  return updated
}

export async function checkoutReservation(id: string, companyId: string, mileage?: number) {
  const reservation = await repo.findByIdForCheckout(id, companyId)
  if (reservation.status !== 'ACTIVE') throw new AppError('Only ACTIVE reservations can be checked out', 400, 'invalid_status')

  const reviewToken = reservation.reviewToken ?? crypto.randomBytes(32).toString('hex')
  const updated = await repo.updateById(id, {
    status: 'COMPLETED',
    checkedOutAt: new Date(),
    checkOutMileage: mileage ?? null,
    reviewToken,
  })
  await repo.updateVehicleStatus(reservation.vehicleId, 'RETURNED', mileage)

  return updated
}

export async function closeReservation(id: string, companyId: string, closedBy: string) {
  const reservation = await repo.findForClose(id, companyId)
  const workflow    = buildReservationWorkflow(reservation)

  if (reservation.status !== 'COMPLETED') throw new AppError('Only completed reservations can be closed', 400, 'invalid_status')
  if (workflow.closed) throw new AppError('This reservation is already closed', 400, 'already_closed')

  const extras = parseReservationExtras(reservation.extras)
  extras.reservationClosedAt = new Date().toISOString()
  extras.reservationClosedBy = closedBy

  const updated = await prisma.reservation.update({
    where: { id: reservation.id },
    data: { extras: extras as any },
    include: { vehicle: true, customer: true, insurances: true, additionalDrivers: true, rentalPayments: true, damageReports: true },
  })

  // Send review request email if eligible
  if (reservation.reviewToken && !(reservation as any).reviewPaused) {
    try {
      const customer = updated.customer as any
      if (!customer.reviewOptOut && customer.email) {
        const company = await repo.findCompanyWithBrand(companyId)
        const lang = (company?.brand?.defaultLocale ?? 'fr') as Lang
        const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company'
        const vehicleLabel = `${updated.vehicle.year} ${updated.vehicle.make} ${updated.vehicle.model}`
        const reviewUrl = `${process.env.CARPLACE_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}`

        sendTransactionalEmail({
          to: customer.email,
          subject: reviewRequestEmail.subject(vehicleLabel, lang),
          html: reviewRequestEmail.html({ firstName: customer.firstName, companyName, vehicleLabel, reviewUrl }, lang),
          text: reviewRequestEmail.text({ firstName: customer.firstName, companyName, vehicleLabel, reviewUrl }, lang),
        }).catch(() => null)

        await prisma.reservation.update({
          where: { id: reservation.id },
          data: { reviewRequestSentAt: new Date() },
        })
      }
    } catch {
      // Non-blocking: do not fail close if email fails
    }
  }

  return serializeReservationForDashboard(updated)
}

export async function extendReservation(id: string, companyId: string, newEndDate: Date, reason: string) {
  const reservation = await repo.findByIdSimple(id, companyId)
  if (!['CONFIRMED', 'ACTIVE'].includes(reservation.status)) {
    throw new AppError('Only CONFIRMED or ACTIVE reservations can be extended', 400, 'invalid_status')
  }
  if (newEndDate <= reservation.endDate) {
    throw new AppError('New end date must be after the current end date', 400, 'invalid_end_date')
  }
  const conflict = await repo.findConflict(reservation.vehicleId, reservation.endDate, newEndDate, id)
  if (conflict) {
    throw new AppError('Vehicle is unavailable for the requested extension period', 409, 'vehicle_unavailable')
  }
  const additionalDays = Math.ceil((newEndDate.getTime() - reservation.endDate.getTime()) / (1000 * 60 * 60 * 24))
  const additionalAmount = additionalDays * reservation.dailyRate
  const extras = parseReservationExtras(reservation.extras)
  if (!Array.isArray(extras.extensions)) extras.extensions = []
  ;(extras.extensions as unknown[]).push({
    previousEndDate: reservation.endDate.toISOString(),
    newEndDate: newEndDate.toISOString(),
    additionalDays,
    additionalAmount,
    reason,
    approvedAt: new Date().toISOString(),
  })
  const updated = await prisma.reservation.update({
    where: { id },
    data: {
      endDate: newEndDate,
      totalDays: reservation.totalDays + additionalDays,
      totalAmount: reservation.totalAmount + additionalAmount,
      extras: extras as any,
    },
  })
  return updated
}

export async function cancelReservation(id: string, companyId: string, reason?: string) {
  const reservation = await repo.findByIdSimple(id, companyId)
  if (['COMPLETED', 'CANCELLED'].includes(reservation.status)) {
    throw new AppError('Reservation cannot be cancelled', 400, 'invalid_status')
  }
  const updated = await repo.updateById(id, { status: 'CANCELLED', cancelReason: reason ?? null, cancelledBy: 'COMPANY' })
  if (['CONFIRMED', 'ACTIVE', 'DRAFT'].includes(reservation.status)) {
    await repo.updateVehicleStatus(reservation.vehicleId, 'AVAILABLE')
  }
  return updated
}
