import { PLAN_FEATURES, PLAN_PRICES } from '@rentaldrivego/types'
import { AppError, NotFoundError } from '../../http/errors'
import { getVehicleAvailabilitySummary } from '../../services/vehicleAvailabilityService'
import { applyInsurancesToReservation } from '../../services/insuranceService'
import { applyAdditionalDriversToReservation } from '../../services/additionalDriverService'
import { applyPricingRules } from '../../services/pricingRuleService'
import { validateLicense, validateAndFlagLicense } from '../../services/licenseValidationService'
import { getMarketplaceHomepageContent } from '../../services/platformContentService'
import { prisma } from '../../lib/prisma'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as repo from './site.repo'
import { presentBrand } from './site.presenter'

export async function getPlatformHomepage() {
  return getMarketplaceHomepageContent()
}

export async function getPlatformPricing() {
  const [configs, features] = await Promise.all([
    prisma.pricingConfig.findMany(),
    prisma.planFeature.findMany({
      orderBy: [{ plan: 'asc' }, { sortOrder: 'asc' }, { createdAt: 'asc' }],
    }),
  ])

  const prices = configs.length === 0
    ? PLAN_PRICES
    : configs.reduce<Record<string, Record<string, Record<string, number>>>>((acc, config) => {
        if (!acc[config.plan]) acc[config.plan] = {}
        acc[config.plan]![config.billingPeriod] = { MAD: config.amount }
        return acc
      }, {})

  const planFeatures = Object.fromEntries(
    Object.entries(PLAN_FEATURES).map(([plan, fallback]) => {
      const labels = features.filter((feature) => feature.plan === plan).map((feature) => feature.label)
      return [plan, labels.length > 0 ? labels : fallback]
    }),
  )

  return { prices, planFeatures }
}

export async function getBrand(slug: string) {
  const company = await repo.findCompanyBySlug(slug)
  return presentBrand(company)
}

export async function getPublicVehicles(slug: string) {
  const company = await repo.findCompanyBySlug(slug)
  const vehicles = await repo.findPublishedVehicles(company.id)
  return Promise.all(
    vehicles.map(async (v) => {
      const a = await getVehicleAvailabilitySummary(v.id)
      return { ...v, availability: a.available, availabilityStatus: a.status, nextAvailableAt: a.nextAvailableAt }
    }),
  )
}

export async function getVehicleDetail(slug: string, vehicleId: string) {
  const company = await repo.findCompanyBySlug(slug)
  const vehicle = await repo.findVehicleById(vehicleId, company.id)
  const a = await getVehicleAvailabilitySummary(vehicle.id)
  return { ...vehicle, availability: a.available, availabilityStatus: a.status, nextAvailableAt: a.nextAvailableAt }
}

export async function getOffers(slug: string) {
  const company = await repo.findCompanyBySlug(slug)
  return repo.findActiveOffers(company.id)
}

export async function getBookingOptions(slug: string) {
  const company = await repo.findCompanyBySlug(slug)
  const insurancePolicies = await repo.findInsurancePolicies(company.id)
  return { insurancePolicies, contractSettings: company.contractSettings }
}

export async function checkAvailability(slug: string, vehicleId: string, startDate: string, endDate: string) {
  await repo.findCompanyBySlug(slug)
  const availability = await getVehicleAvailabilitySummary(vehicleId, {
    range: { startDate: new Date(startDate), endDate: new Date(endDate) },
  })
  return { available: availability.available, nextAvailableAt: availability.nextAvailableAt }
}

export async function validatePromoCode(slug: string, code: string) {
  const company = await repo.findCompanyBySlug(slug)
  const offer = await repo.findOfferByPromoCode(company.id, code)
  if (!offer) throw new NotFoundError('Promo code not found or expired')
  return offer
}

export async function createBooking(slug: string, body: {
  vehicleId: string; startDate: string; endDate: string
  firstName: string; lastName: string; email: string; phone: string
  driverLicense: string; dateOfBirth: string; licenseExpiry: string; licenseIssuedAt: string; nationality: string
  identityDocumentNumber: string; fullAddress: string; licenseCountry: string; licenseNumber?: string; licenseCategory: string; internationalLicenseNumber?: string
  offerId?: string; promoCodeUsed?: string; notes?: string; source?: string
  selectedInsurancePolicyIds?: string[]; additionalDrivers?: any[]
}) {
  const company = await repo.findCompanyBySlug(slug)
  const vehicle = await repo.findVehicleById(body.vehicleId, company.id)

  const start = new Date(body.startDate)
  const end   = new Date(body.endDate)
  if (end <= start) throw new AppError('End date must be after start date', 400, 'invalid_dates')

  const availability = await getVehicleAvailabilitySummary(vehicle.id, { range: { startDate: start, endDate: end } })
  if (!availability.available) {
    throw new AppError('Vehicle is not available for the selected dates', 409, 'unavailable', {
      nextAvailableAt: availability.nextAvailableAt?.toISOString() ?? null,
    })
  }

  const customer = await repo.upsertCustomer(company.id, {
    email: body.email,
    firstName: body.firstName,
    lastName: body.lastName,
    phone: body.phone,
    driverLicense: body.driverLicense,
    dateOfBirth: body.dateOfBirth,
    licenseExpiry: body.licenseExpiry,
    licenseIssuedAt: body.licenseIssuedAt,
    nationality: body.nationality,
    identityDocumentNumber: body.identityDocumentNumber,
    fullAddress: body.fullAddress,
    licenseCountry: body.licenseCountry,
    licenseNumber: body.licenseNumber,
    licenseCategory: body.licenseCategory,
    internationalLicenseNumber: body.internationalLicenseNumber,
  })

  const totalDays  = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 86_400_000))
  const baseAmount = vehicle.dailyRate * totalDays

  let discountAmount = 0
  if (body.promoCodeUsed) {
    const offer = await repo.findOfferByPromoCode(company.id, body.promoCodeUsed)
    if (offer) {
      if (offer.type === 'PERCENTAGE')    discountAmount = Math.round(baseAmount * offer.discountValue / 100)
      else if (offer.type === 'FIXED_AMOUNT') discountAmount = offer.discountValue
      else if (offer.type === 'FREE_DAY')     discountAmount = vehicle.dailyRate * offer.discountValue
    }
  }

  const additionalDriversList = body.additionalDrivers ?? []
  const { applied, total: pricingRulesTotal } = await applyPricingRules(
    company.id, customer.id, additionalDriversList as any[], vehicle.dailyRate, totalDays,
  )

  const primaryLicenseResult = validateLicense(body.licenseExpiry ? new Date(body.licenseExpiry) : null)
  if (primaryLicenseResult.status === 'EXPIRED') {
    throw new AppError('The primary driver license is expired', 400, 'license_expired')
  }

  const reservation = await repo.createReservation({
    companyId: company.id,
    vehicleId: vehicle.id,
    customerId: customer.id,
    offerId: body.offerId ?? null,
    promoCodeUsed: body.promoCodeUsed ?? null,
    vehicleCategory: vehicle.category,
    source: body.source ?? 'PUBLIC_SITE',
    startDate: start,
    endDate: end,
    dailyRate: vehicle.dailyRate,
    totalDays,
    totalAmount: baseAmount - discountAmount + pricingRulesTotal,
    discountAmount,
    pricingRulesApplied: applied,
    pricingRulesTotal,
    notes: body.notes ?? null,
    status: 'DRAFT',
  })

  const insuranceIds = body.selectedInsurancePolicyIds ?? []
  if (insuranceIds.length > 0) {
    await applyInsurancesToReservation(reservation.id, company.id, insuranceIds, totalDays, baseAmount)
  }
  if (additionalDriversList.length > 0) {
    await applyAdditionalDriversToReservation(reservation.id, company.id, additionalDriversList, totalDays)
  }
  if (body.licenseExpiry) {
    await validateAndFlagLicense(customer.id).catch(() => null)
  }

  const refreshed = await repo.findReservationWithDetails(reservation.id)
  return {
    ...refreshed,
    requiresManualApproval:
      primaryLicenseResult.requiresApproval ||
      refreshed.additionalDrivers.some((d) => d.requiresApproval),
  }
}

export async function getBooking(slug: string, reservationId: string) {
  const company = await repo.findCompanyBySlug(slug)
  return repo.findBooking(reservationId, company.id)
}

export async function initPayment(slug: string, reservationId: string, body: {
  provider: 'AMANPAY' | 'PAYPAL'; currency?: 'MAD'; successUrl: string; failureUrl: string
}) {
  const company     = await repo.findCompanyBySlug(slug)
  const reservation = await repo.findReservationForPayment(reservationId, company.id)

  if (reservation.paymentStatus === 'PAID') {
    throw new AppError('This reservation is already paid', 409, 'already_paid')
  }

  const customerLicenseResult = validateLicense(reservation.customer.licenseExpiry)
  const licenseBlocked =
    reservation.customer.licenseValidationStatus === 'DENIED' ||
    customerLicenseResult.status === 'EXPIRED' ||
    (customerLicenseResult.requiresApproval && reservation.customer.licenseValidationStatus !== 'APPROVED') ||
    reservation.additionalDrivers.some((d) => d.licenseExpired || (d.requiresApproval && !d.approvedAt))

  if (licenseBlocked) {
    throw new AppError('This reservation requires license review before payment can be processed', 409, 'license_review_required')
  }

  const currency    = body.currency ?? 'MAD'
  const amount      = reservation.totalAmount
  const description = `Rental: ${reservation.vehicle.make} ${reservation.vehicle.model}`
  const orderId     = `res-${reservation.id}-${Date.now()}`
  const webhookBase = process.env.API_URL ?? 'http://localhost:4000'

  let checkoutUrl: string
  let amanpayTransactionId: string | null = null
  let paypalCaptureId: string | null      = null

  if (body.provider === 'AMANPAY') {
    if (!amanpay.isConfigured()) {
      throw new AppError('Online payment is not available for this company', 503, 'provider_not_configured')
    }
    const brand      = company.brand as any
    const merchantId = brand?.amanpayMerchantId ?? process.env.AMANPAY_MERCHANT_ID ?? ''
    const secretKey  = brand?.amanpaySecretKey  ?? process.env.AMANPAY_SECRET_KEY  ?? ''
    if (!merchantId || !secretKey) {
      throw new AppError('AmanPay is not configured for this company', 503, 'provider_not_configured')
    }
    const result = await amanpay.createCheckout({
      amount, currency, orderId, description,
      customerEmail: reservation.customer.email,
      customerName:  `${reservation.customer.firstName} ${reservation.customer.lastName}`,
      successUrl: body.successUrl,
      failureUrl: body.failureUrl,
      webhookUrl: `${webhookBase}/api/v1/payments/webhooks/amanpay`,
    })
    checkoutUrl           = result.checkoutUrl
    amanpayTransactionId  = result.transactionId
  } else {
    if (!paypal.isConfigured()) {
      throw new AppError('PayPal is not available for this company', 503, 'provider_not_configured')
    }
    const result = await paypal.createOrder({
      amount, currency, orderId, description,
      returnUrl: body.successUrl,
      cancelUrl: body.failureUrl,
    })
    checkoutUrl    = result.approveUrl
    paypalCaptureId = result.orderId
  }

  await repo.createRentalPayment({
    companyId: company.id,
    reservationId: reservation.id,
    amount,
    currency,
    paymentProvider: body.provider,
    amanpayTransactionId,
    paypalCaptureId,
  })

  return { checkoutUrl }
}

export async function capturePaypal(slug: string, paypalOrderId: string) {
  const company = await repo.findCompanyBySlug(slug)
  const payment = await repo.findPaymentByPaypalOrderId(paypalOrderId, company.id)
  const capture = await paypal.captureOrder(paypalOrderId) as Record<string, any>
  const captureId = capture.purchase_units?.[0]?.payments?.captures?.[0]?.id ?? paypalOrderId
  await repo.capturePaypalPayment(payment.id, captureId, payment.reservationId, payment.amount)
  return { success: true }
}

export async function handleContact(slug: string, body: { name: string; email: string; message: string }) {
  const company = await repo.findCompanyBySlug(slug)
  return {
    success: true,
    deliveredTo: company.brand?.publicEmail ?? company.email,
    preview: body,
  }
}
