import { AppError, NotFoundError, ConflictError } from '../../http/errors'
import { getVehicleAvailabilitySummary } from '../../services/vehicleAvailabilityService'
import { sendNotification, sendTransactionalEmail } from '../../services/notificationService'
import { marketplaceReservationEmail, type Lang } from '../../lib/emailTranslations'
import * as repo from './marketplace.repo'

function normalizeLocation(value?: string | null) {
  const normalized = value?.trim()
  return normalized ? normalized : null
}

function normalizeLocationList(values?: string[] | null) {
  if (!Array.isArray(values)) return []
  return values
    .map((value) => normalizeLocation(value))
    .filter((value): value is string => Boolean(value))
}

function includesLocation(values: string[], target: string) {
  const key = target.toLocaleLowerCase()
  return values.some((value) => value.toLocaleLowerCase() === key)
}

function matchesVehicleLocationRules(
  vehicle: { pickupLocations?: string[]; dropoffLocations?: string[]; allowDifferentDropoff?: boolean },
  params: { pickupLocation?: string; dropoffLocation?: string; dropoffMode?: 'same' | 'different' },
) {
  const pickupLocation = normalizeLocation(params.pickupLocation)
  const dropoffLocation = normalizeLocation(params.dropoffLocation)
  const pickupLocations = normalizeLocationList(vehicle.pickupLocations)
  const dropoffLocations = normalizeLocationList(vehicle.dropoffLocations)

  if (pickupLocation && pickupLocations.length > 0 && !includesLocation(pickupLocations, pickupLocation)) {
    return false
  }

  if (params.dropoffMode === 'different') {
    if (!vehicle.allowDifferentDropoff) return false
    if (dropoffLocation && dropoffLocations.length > 0 && !includesLocation(dropoffLocations, dropoffLocation)) {
      return false
    }
  }

  return true
}

export async function getPublicOffers() {
  return repo.findPublicOffers()
}

export async function getCities() {
  const rows = await repo.findCitiesFromCompanies()
  const map = new Map<string, string>()
  for (const row of rows) {
    const city = row.brand?.publicCity?.trim()
    if (!city) continue
    const key = city.toLocaleLowerCase()
    if (!map.has(key)) map.set(key, city)
  }
  return Array.from(map.values()).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
}

export async function getListedCompanies(params: {
  city?: string; hasOffer?: string; page?: number; pageSize?: number
}) {
  const page     = params.page     ?? 1
  const pageSize = params.pageSize ?? 20
  const where: any = { status: { in: ['ACTIVE', 'TRIALING'] }, brand: { isListedOnMarketplace: true } }
  if (params.city) where.brand = { ...where.brand, publicCity: { contains: params.city, mode: 'insensitive' } }
  if (params.hasOffer === 'true') {
    where.offers = { some: { isPublic: true, isActive: true, validUntil: { gte: new Date() } } }
  }
  return repo.findListedCompanies(where, (page - 1) * pageSize, pageSize)
}

export async function searchVehicles(params: {
  city?: string; pickupLocation?: string; dropoffLocation?: string; dropoffMode?: 'same' | 'different'
  startDate?: string; endDate?: string; category?: string
  maxPrice?: number; transmission?: string; make?: string; model?: string
  page?: number; pageSize?: number
}) {
  const page     = params.page     ?? 1
  const pageSize = params.pageSize ?? 20
  const where: any = {
    isPublished: true,
    status: 'AVAILABLE',
    company: { status: { in: ['ACTIVE', 'TRIALING'] }, brand: { isListedOnMarketplace: true } },
  }
  if (params.category)     where.category     = params.category
  if (params.maxPrice !== undefined) where.dailyRate = { lte: params.maxPrice }
  if (params.transmission) where.transmission = params.transmission
  if (params.make)         where.make         = { contains: params.make,  mode: 'insensitive' }
  if (params.model)        where.model        = { contains: params.model, mode: 'insensitive' }
  if (params.city) {
    where.company = { ...where.company, brand: { isListedOnMarketplace: true, publicCity: { contains: params.city, mode: 'insensitive' } } }
  }

  const vehicles = await repo.findPublishedVehicles(where)
  const locationFiltered = vehicles.filter((vehicle) => matchesVehicleLocationRules(vehicle, params))
  const pagedVehicles = locationFiltered.slice((page - 1) * pageSize, page * pageSize)

  const availability = await Promise.all(
    pagedVehicles.map(async (v) => {
      const a = await getVehicleAvailabilitySummary(v.id, {
        range: params.startDate && params.endDate
          ? { startDate: new Date(params.startDate), endDate: new Date(params.endDate) }
          : undefined,
      })
      return [v.id, a] as const
    }),
  )
  const availMap = new Map(availability)

  return pagedVehicles.map((v) => ({
    ...v,
    availability:       availMap.get(v.id)?.available          ?? null,
    availabilityStatus: availMap.get(v.id)?.status             ?? 'AVAILABLE',
    nextAvailableAt:    availMap.get(v.id)?.nextAvailableAt    ?? null,
  }))
}

export async function createMarketplaceReservation(body: {
  vehicleId: string; companySlug: string; firstName: string; lastName: string
  email: string; phone?: string; dateOfBirth?: string; nationality?: string
  identityDocumentNumber?: string; fullAddress?: string
  driverLicense?: string; licenseExpiry?: string; licenseIssuedAt?: string
  licenseCountry?: string; licenseCategory?: string; internationalLicenseNumber?: string
  startDate: string; endDate: string
  pickupLocation?: string; returnLocation?: string; notes?: string; language?: string
}) {
  const startDate = new Date(body.startDate)
  const endDate   = new Date(body.endDate)

  if (endDate <= startDate) {
    throw new AppError('End date must be after start date', 400, 'invalid_dates')
  }

  const vehicle = await repo.findVehicleForMarketplace(body.vehicleId, body.companySlug)
  if (!vehicle) throw new NotFoundError('Vehicle not found')

  const pickupLocation = normalizeLocation(body.pickupLocation)
  const returnLocation = normalizeLocation(body.returnLocation) ?? pickupLocation
  const pickupLocations = normalizeLocationList(vehicle.pickupLocations)
  const dropoffLocations = normalizeLocationList(vehicle.dropoffLocations)

  if (pickupLocation && pickupLocations.length > 0 && !includesLocation(pickupLocations, pickupLocation)) {
    throw new AppError('Selected pickup location is not available for this vehicle', 400, 'invalid_pickup_location')
  }

  if (pickupLocation && returnLocation && pickupLocation.toLocaleLowerCase() !== returnLocation.toLocaleLowerCase()) {
    if (!vehicle.allowDifferentDropoff) {
      throw new AppError('This vehicle must be returned to the same pickup location', 400, 'same_dropoff_required')
    }
    if (dropoffLocations.length > 0 && !includesLocation(dropoffLocations, returnLocation)) {
      throw new AppError('Selected return location is not available for this vehicle', 400, 'invalid_return_location')
    }
  }

  const availability = await getVehicleAvailabilitySummary(body.vehicleId, { range: { startDate, endDate } })
  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.upsertMarketplaceCustomer(vehicle.companyId, {
    email: body.email,
    firstName: body.firstName,
    lastName: body.lastName,
    phone: body.phone,
    dateOfBirth: body.dateOfBirth,
    nationality: body.nationality,
    identityDocumentNumber: body.identityDocumentNumber,
    fullAddress: body.fullAddress,
    driverLicense: body.driverLicense,
    licenseExpiry: body.licenseExpiry,
    licenseIssuedAt: body.licenseIssuedAt,
    licenseCountry: body.licenseCountry,
    licenseCategory: body.licenseCategory,
    internationalLicenseNumber: body.internationalLicenseNumber,
  })

  const totalDays   = Math.max(1, Math.ceil((endDate.getTime() - startDate.getTime()) / 86_400_000))
  const totalAmount = vehicle.dailyRate * totalDays

  const reservation = await repo.createMarketplaceReservation({
    companyId: vehicle.companyId, vehicleId: body.vehicleId, customerId: customer.id,
    startDate, endDate, pickupLocation, returnLocation, dailyRate: vehicle.dailyRate, totalDays, totalAmount, notes: body.notes,
  })

  const lang        = (body.language ?? 'fr') as Lang
  const companyName = vehicle.company.brand?.displayName ?? (vehicle.company as any).name
  const emailOpts   = {
    firstName: body.firstName, vehicleYear: vehicle.year, vehicleMake: vehicle.make, vehicleModel: vehicle.model,
    companyName, startDate, endDate, totalDays,
    rateDisplay: (vehicle.dailyRate / 100).toFixed(2),
    totalDisplay: (totalAmount / 100).toFixed(2),
    email: body.email, phone: body.phone,
  }

  sendTransactionalEmail({
    to: body.email,
    subject: marketplaceReservationEmail.subject(`${vehicle.make} ${vehicle.model}`, lang),
    html: marketplaceReservationEmail.html(emailOpts, lang),
    text: marketplaceReservationEmail.text(emailOpts, lang),
  }).catch(() => null)

  sendNotification({
    type: 'NEW_BOOKING',
    title: 'New Marketplace Reservation Request',
    body: `${body.firstName} ${body.lastName} requested ${vehicle.make} ${vehicle.model} from ${startDate.toISOString().slice(0, 10)} to ${endDate.toISOString().slice(0, 10)}.`,
    companyId: vehicle.companyId,
    channels: ['IN_APP'],
  }).catch(() => null)

  return { reservationId: reservation.id }
}

export async function getCompanyPage(slug: string) {
  const company = await repo.findCompanyPage(slug)
  if (!company) throw new NotFoundError('Company not found')

  const vehicles = await Promise.all(
    company.vehicles.map(async (v) => {
      const a = await getVehicleAvailabilitySummary(v.id)
      return { ...v, availability: a.available, availabilityStatus: a.status, nextAvailableAt: a.nextAvailableAt }
    }),
  )
  return { ...company, vehicles }
}

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

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

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

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

export async function getReviewContext(token: string) {
  const reservation = await repo.findReservationByReviewToken(token)
  if (!reservation) throw new NotFoundError('Review link is invalid or has expired')
  if (reservation.review) throw new ConflictError('A review has already been submitted for this reservation')

  return {
    reservationId:  reservation.id,
    companyName:    reservation.company.brand?.displayName ?? (reservation.company as any).name,
    companyLogoUrl: reservation.company.brand?.logoUrl ?? null,
    vehicle:        `${reservation.vehicle.year} ${reservation.vehicle.make} ${reservation.vehicle.model}`,
    vehiclePhoto:   reservation.vehicle.photos[0] ?? null,
  }
}

export async function submitReview(token: string, body: {
  overallRating: number; vehicleRating?: number; serviceRating?: number; comment?: string
}) {
  const reservation = await repo.findReservationForReviewSubmit(token)
  if (!reservation) throw new NotFoundError('Review link is invalid or has expired')
  if (reservation.review) throw new ConflictError('A review has already been submitted for this reservation')

  const review = await repo.createReview({
    reservationId: reservation.id,
    companyId:     reservation.companyId,
    renterId:      reservation.renterId,
    ...body,
  })
  await repo.invalidateReviewToken(reservation.id)
  return { reviewId: review.id }
}

export async function validateOfferCode(code: string) {
  const offer = await repo.findOfferByCode(code)
  if (!offer) throw new NotFoundError('Promo code not found or expired')
  if (offer.maxRedemptions && offer.redemptionCount >= offer.maxRedemptions) {
    throw new ConflictError('Promo code has reached its maximum redemptions')
  }
  return offer
}
