import { uploadImage } from '../../lib/storage'
import { AppError, NotFoundError, ValidationError } from '../../http/errors'
import { presentVehicle, presentVehicleList } from './vehicle.presenter'
import * as repo from './vehicle.repo'

const DAY_MS = 24 * 60 * 60 * 1000
const RULE_PRIORITY: Record<string, number> = {
  MANUAL_OVERRIDE: 6,
  HOLIDAY: 5,
  SPECIAL_EVENT: 4,
  SEASONAL: 3,
  WEEKEND: 2,
  DATE_RANGE: 1,
}

function isPricingStorageMissing(error: unknown) {
  if (!error || typeof error !== 'object') return false
  const candidate = error as { code?: string; message?: string }
  return (
    candidate.code === 'P2021' ||
    candidate.message?.includes('vehicle_pricing_configurations') === true ||
    candidate.message?.includes('vehicle_pricing_rules') === true ||
    candidate.message?.includes('vehicle_price_history') === true
  )
}

function normalizeLocations(locations: unknown) {
  if (!Array.isArray(locations)) return []
  const seen = new Set<string>()
  const normalized: string[] = []
  for (const entry of locations) {
    if (typeof entry !== 'string') continue
    const value = entry.trim()
    const key = value.toLocaleLowerCase()
    if (!value || seen.has(key)) continue
    seen.add(key)
    normalized.push(value)
  }
  return normalized
}

function applyLocationSettings(data: any) {
  const patch = { ...data }

  if (patch.pickupLocations !== undefined) {
    patch.pickupLocations = normalizeLocations(patch.pickupLocations)
  }
  if (patch.dropoffLocations !== undefined) {
    patch.dropoffLocations = normalizeLocations(patch.dropoffLocations)
  }

  if (patch.allowDifferentDropoff === false) {
    patch.dropoffLocations = []
  }

  if (patch.allowDifferentDropoff === true && patch.dropoffLocations !== undefined && patch.dropoffLocations.length === 0) {
    throw new ValidationError('Drop-off locations are required when different drop-off is enabled')
  }

  return patch
}

function toStartOfDay(value: string | Date) {
  const date = new Date(value)
  date.setHours(0, 0, 0, 0)
  return date
}

function toEndOfDay(value: string | Date) {
  const date = new Date(value)
  date.setHours(23, 59, 59, 999)
  return date
}

function isWeekend(date: Date) {
  const day = date.getDay()
  return day === 0 || day === 6
}

function clampRate(value: number, min?: number | null, max?: number | null) {
  if (min != null && value < min) return min
  if (max != null && value > max) return max
  return value
}

function validateRateBounds(minimum?: number | null, maximum?: number | null, label: string = 'Daily') {
  if (minimum != null && maximum != null && minimum > maximum) {
    throw new ValidationError(`${label} minimum rate must be less than or equal to the maximum rate`)
  }
}

function normalizePricingConfigurationInput(data: any) {
  const normalized = {
    pricingMode: data.pricingMode,
    baseDailyRate: data.baseDailyRate,
    weeklyRate: data.weeklyRate ?? null,
    weekendRate: data.weekendRate ?? null,
    holidayRate: data.holidayRate ?? null,
    monthlyRate: data.monthlyRate ?? null,
    longTermDailyRate: data.longTermDailyRate ?? null,
    minimumDailyRate: data.minimumDailyRate ?? null,
    maximumDailyRate: data.maximumDailyRate ?? null,
    maxDailyPriceMovementPct: data.maxDailyPriceMovementPct,
    automaticPricingEnabled: data.automaticPricingEnabled,
    approvalRequired: data.approvalRequired,
  }

  validateRateBounds(normalized.minimumDailyRate, normalized.maximumDailyRate)
  validateRateBounds(normalized.baseDailyRate, normalized.maximumDailyRate, 'Base')
  return normalized
}

function normalizePricingRuleInput(data: any) {
  const normalized = {
    name: data.name,
    ruleType: data.ruleType,
    startDate: toStartOfDay(data.startDate),
    endDate: toEndOfDay(data.endDate),
    dailyRate: data.dailyRate ?? null,
    weeklyRate: data.weeklyRate ?? null,
    weekendRate: data.weekendRate ?? null,
    monthlyRate: data.monthlyRate ?? null,
    minDailyRate: data.minDailyRate ?? null,
    maxDailyRate: data.maxDailyRate ?? null,
    automaticAdjustmentPct: data.automaticAdjustmentPct ?? null,
    priceAdjustment: data.priceAdjustment ?? null,
    isActive: data.isActive,
    sortOrder: data.sortOrder,
  }

  if (normalized.endDate < normalized.startDate) {
    throw new ValidationError('Rule end date must be on or after the start date')
  }

  validateRateBounds(normalized.minDailyRate, normalized.maxDailyRate)

  const hasPricingEffect = [
    normalized.dailyRate,
    normalized.weeklyRate,
    normalized.weekendRate,
    normalized.monthlyRate,
    normalized.minDailyRate,
    normalized.maxDailyRate,
    normalized.automaticAdjustmentPct,
    normalized.priceAdjustment,
  ].some((value) => value != null)

  if (!hasPricingEffect) {
    throw new ValidationError('Pricing rules must define at least one rate, bound, or adjustment')
  }

  return normalized
}

async function ensurePricingConfigurationForVehicle(vehicleId: string, companyId: string) {
  const vehicle = await repo.findById(vehicleId, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')

  let configuration: any = null
  let persistenceAvailable = true
  try {
    configuration = await repo.findPricingConfiguration(vehicleId)
  } catch (error) {
    if (!isPricingStorageMissing(error)) throw error
    persistenceAvailable = false
  }

  if (!persistenceAvailable) {
    return {
      vehicle,
      persistenceAvailable,
      configuration: {
        id: `transient-${vehicle.id}`,
        vehicleId: vehicle.id,
        pricingMode: 'MANUAL',
        baseDailyRate: vehicle.dailyRate,
        weeklyRate: vehicle.dailyRate * 7,
        weekendRate: null,
        holidayRate: null,
        monthlyRate: null,
        longTermDailyRate: null,
        minimumDailyRate: null,
        maximumDailyRate: null,
        maxDailyPriceMovementPct: 10,
        automaticPricingEnabled: false,
        approvalRequired: false,
        rules: [],
        history: [],
        createdAt: vehicle.createdAt,
        updatedAt: vehicle.updatedAt,
      },
    }
  }

  if (!configuration) {
    configuration = await repo.createPricingConfiguration({
      vehicleId,
      pricingMode: 'MANUAL',
      baseDailyRate: vehicle.dailyRate,
      weeklyRate: vehicle.dailyRate * 7,
      automaticPricingEnabled: false,
      approvalRequired: false,
      maxDailyPriceMovementPct: 10,
    })
  }

  return { vehicle, configuration, persistenceAvailable }
}

async function syncPricingBaseRateFromVehicle(vehicleId: string, previousDailyRate: number, nextDailyRate: number) {
  const configuration = await repo.findPricingConfiguration(vehicleId)
  if (!configuration) return

  const currentBaseRate = configuration.baseDailyRate ?? previousDailyRate
  if (currentBaseRate === nextDailyRate) return

  await repo.updatePricingConfiguration(configuration.id, { baseDailyRate: nextDailyRate })
  await repo.createPriceHistory({
    configurationId: configuration.id,
    vehicleId,
    source: 'CONFIG_UPDATE',
    previousDailyRate: currentBaseRate,
    nextDailyRate,
    previousWeeklyRate: configuration.weeklyRate ?? null,
    nextWeeklyRate: configuration.weeklyRate ?? null,
    note: 'Base daily rate synced from vehicle details',
  })
}

function buildPricePreview(vehicle: any, configuration: any) {
  const baseDailyRate = configuration.baseDailyRate ?? vehicle.dailyRate
  const activeRules = (configuration.rules ?? []).filter((rule: any) => rule.isActive)
  const items = Array.from({ length: 14 }, (_, index) => {
    const date = new Date(Date.now() + (index * DAY_MS))
    date.setHours(12, 0, 0, 0)
    let dailyRate = baseDailyRate
    let pricingType = configuration.pricingMode === 'AUTOMATIC' && configuration.automaticPricingEnabled
      ? 'AUTOMATIC_BASE'
      : 'MANUAL_BASE'

    if (isWeekend(date) && configuration.weekendRate != null) {
      dailyRate = configuration.weekendRate
      pricingType = 'WEEKEND_RATE'
    }

    const matchingRule = activeRules
      .filter((rule: any) => date >= new Date(rule.startDate) && date <= new Date(rule.endDate))
      .sort((a: any, b: any) => {
        const priorityDiff = (RULE_PRIORITY[b.ruleType] ?? 0) - (RULE_PRIORITY[a.ruleType] ?? 0)
        if (priorityDiff !== 0) return priorityDiff
        if (a.sortOrder !== b.sortOrder) return b.sortOrder - a.sortOrder
        return new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
      })[0]

    if (matchingRule) {
      if (matchingRule.dailyRate != null) {
        dailyRate = matchingRule.dailyRate
      } else {
        if (matchingRule.weekendRate != null && isWeekend(date)) {
          dailyRate = matchingRule.weekendRate
        }
        if (matchingRule.priceAdjustment != null) {
          dailyRate += matchingRule.priceAdjustment
        }
        if (matchingRule.automaticAdjustmentPct != null) {
          dailyRate = Math.round(dailyRate * (100 + matchingRule.automaticAdjustmentPct) / 100)
        }
      }
      pricingType = configuration.pricingMode === 'AUTOMATIC' ? 'AUTOMATIC_RULE' : 'RULE_RATE'
      dailyRate = clampRate(
        dailyRate,
        matchingRule.minDailyRate ?? configuration.minimumDailyRate,
        matchingRule.maxDailyRate ?? configuration.maximumDailyRate,
      )
    } else {
      dailyRate = clampRate(dailyRate, configuration.minimumDailyRate, configuration.maximumDailyRate)
    }

    return {
      date: date.toISOString().slice(0, 10),
      dailyRate,
      pricingType,
      matchedRuleName: matchingRule?.name ?? null,
    }
  })

  return {
    today: items[0],
    next14Days: items,
  }
}

function presentVehiclePricing(vehicle: any, configuration: any) {
  return {
    configuration: {
      id: configuration.id,
      vehicleId: configuration.vehicleId,
      pricingMode: configuration.pricingMode,
      baseDailyRate: configuration.baseDailyRate ?? vehicle.dailyRate,
      weeklyRate: configuration.weeklyRate ?? null,
      weekendRate: configuration.weekendRate ?? null,
      holidayRate: configuration.holidayRate ?? null,
      monthlyRate: configuration.monthlyRate ?? null,
      longTermDailyRate: configuration.longTermDailyRate ?? null,
      minimumDailyRate: configuration.minimumDailyRate ?? null,
      maximumDailyRate: configuration.maximumDailyRate ?? null,
      maxDailyPriceMovementPct: configuration.maxDailyPriceMovementPct,
      automaticPricingEnabled: configuration.automaticPricingEnabled,
      approvalRequired: configuration.approvalRequired,
      createdAt: configuration.createdAt,
      updatedAt: configuration.updatedAt,
    },
    rules: (configuration.rules ?? []).map((rule: any) => ({
      id: rule.id,
      name: rule.name,
      ruleType: rule.ruleType,
      startDate: rule.startDate,
      endDate: rule.endDate,
      dailyRate: rule.dailyRate,
      weeklyRate: rule.weeklyRate,
      weekendRate: rule.weekendRate,
      monthlyRate: rule.monthlyRate,
      minDailyRate: rule.minDailyRate,
      maxDailyRate: rule.maxDailyRate,
      automaticAdjustmentPct: rule.automaticAdjustmentPct,
      priceAdjustment: rule.priceAdjustment,
      isActive: rule.isActive,
      sortOrder: rule.sortOrder,
      createdAt: rule.createdAt,
      updatedAt: rule.updatedAt,
    })),
    history: (configuration.history ?? []).map((entry: any) => ({
      id: entry.id,
      source: entry.source,
      changedByEmployeeId: entry.changedByEmployeeId,
      previousDailyRate: entry.previousDailyRate,
      nextDailyRate: entry.nextDailyRate,
      previousWeeklyRate: entry.previousWeeklyRate,
      nextWeeklyRate: entry.nextWeeklyRate,
      note: entry.note,
      effectiveFrom: entry.effectiveFrom,
      createdAt: entry.createdAt,
    })),
    preview: buildPricePreview(vehicle, configuration),
  }
}

export async function listVehicles(companyId: string, query: { status?: string; category?: string; published?: string; page?: number; pageSize?: number }) {
  const page     = query.page     ?? 1
  const pageSize = query.pageSize ?? 20
  const { status, category, published } = query
  const where: any = { companyId }
  if (status)              where.status     = status
  if (category)            where.category   = category
  if (published !== undefined) where.isPublished = published === 'true'
  const [vehicles, total] = await repo.findMany(where, (page - 1) * pageSize, pageSize)
  return presentVehicleList(vehicles.map(presentVehicle), { total, page, pageSize, totalPages: Math.ceil(total / pageSize) })
}

export async function getVehicle(id: string, companyId: string) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  return presentVehicle(vehicle)
}

export async function createVehicle(data: any, companyId: string) {
  return presentVehicle(await repo.create({
    dailyRate: 0,
    ...applyLocationSettings(data),
    companyId,
  }))
}

const PUBLISHED_STATUSES = new Set(['AVAILABLE', 'RESERVED', 'READY', 'RENTED'])

export async function updateVehicle(id: string, companyId: string, data: any) {
  const patch = applyLocationSettings(data)
  if (patch.status) {
    patch.isPublished = PUBLISHED_STATUSES.has(patch.status)
  }
  const existing = await repo.findFirst(id, companyId)
  if (!existing) throw new NotFoundError('Vehicle not found')
  const updated = await repo.updateById(id, patch)
  if (patch.dailyRate !== undefined && typeof patch.dailyRate === 'number') {
    await syncPricingBaseRateFromVehicle(id, existing.dailyRate, patch.dailyRate)
  }
  return presentVehicle(updated)
}

export async function setStatus(id: string, companyId: string, status: string) {
  const existing = await repo.findFirst(id, companyId)
  if (!existing) throw new NotFoundError('Vehicle not found')
  const isPublished = PUBLISHED_STATUSES.has(status)
  return presentVehicle(await repo.updateById(id, { status, isPublished }))
}

export async function deleteVehicle(id: string, companyId: string) {
  return repo.softDelete(id, companyId)
}

export async function uploadPhotos(id: string, companyId: string, files: Express.Multer.File[]) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  const urls = await Promise.all(files.map((f) => uploadImage(f.buffer, `companies/${companyId}/vehicles`)))
  return presentVehicle(await repo.updateById(id, { photos: [...vehicle.photos, ...urls] }))
}

export async function deletePhoto(id: string, companyId: string, idx: number) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  const photos = vehicle.photos.filter((_: string, i: number) => i !== idx)
  return presentVehicle(await repo.updateById(id, { photos }))
}

export async function setPublished(id: string, companyId: string, isPublished: boolean) {
  await repo.setPublished(id, companyId, isPublished)
}

export async function checkAvailability(id: string, companyId: string, startDate: string, endDate: string) {
  const start = new Date(startDate)
  const end   = new Date(endDate)
  const [conflicts, blocks] = await Promise.all([
    repo.findReservationConflicts(id, companyId, start, end),
    repo.findCalendarBlockConflicts(id, start, end),
  ])
  return { available: conflicts.length === 0 && blocks.length === 0, conflicts, blocks }
}

export async function getCalendar(id: string, companyId: string, year: number, month: number) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  const rangeStart = new Date(year, month - 1, 1)
  const rangeEnd   = new Date(year, month, 1)
  const [reservations, blocks] = await repo.findCalendarEvents(id, companyId, rangeStart, rangeEnd)
  return [
    ...reservations.map((r: any) => ({
      id: r.id,
      type: 'RESERVATION' as const,
      startDate: r.startDate,
      endDate: r.endDate,
      status: r.status,
      label: r.customer ? `${r.customer.firstName} ${r.customer.lastName}` : 'Reserved',
    })),
    ...blocks.map((b: any) => ({
      id: b.id,
      type: b.type === 'MAINTENANCE' ? ('MAINTENANCE' as const) : ('BLOCK' as const),
      startDate: b.startDate,
      endDate: b.endDate,
      status: null,
      label: b.reason ?? (b.type === 'MAINTENANCE' ? 'Maintenance' : 'Blocked'),
    })),
  ]
}

export async function createCalendarBlock(id: string, companyId: string, data: { startDate: string; endDate: string; reason?: string; type?: 'MANUAL' | 'MAINTENANCE' }) {
  const start = new Date(data.startDate)
  const end   = new Date(data.endDate)
  if (end <= start) throw new ValidationError('endDate must be after startDate')
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  return repo.createCalendarBlock({ vehicleId: id, startDate: start, endDate: end, reason: data.reason, type: data.type ?? 'MANUAL' })
}

export async function deleteCalendarBlock(vehicleId: string, companyId: string, blockId: string) {
  const vehicle = await repo.findById(vehicleId, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  await repo.deleteCalendarBlock(blockId, vehicleId)
}

export async function getMaintenanceLogs(id: string, companyId: string) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  return repo.findMaintenanceLogs(id)
}

export async function createMaintenanceLog(id: string, companyId: string, data: any) {
  const vehicle = await repo.findById(id, companyId)
  if (!vehicle) throw new NotFoundError('Vehicle not found')
  return repo.createMaintenanceLog({
    ...data,
    vehicleId:   id,
    performedAt: new Date(data.performedAt),
    nextDueAt:   data.nextDueAt ? new Date(data.nextDueAt) : undefined,
  })
}

export async function getVehiclePricing(id: string, companyId: string) {
  const { vehicle, configuration } = await ensurePricingConfigurationForVehicle(id, companyId)
  return presentVehiclePricing(vehicle, configuration)
}

export async function updateVehiclePricing(id: string, companyId: string, employeeId: string, data: any) {
  const { vehicle, configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId)
  if (!persistenceAvailable) {
    throw new AppError(
      'Vehicle pricing tables are not available yet. Run the latest database migration, then retry.',
      503,
      'pricing_migration_required',
    )
  }
  const nextConfig = normalizePricingConfigurationInput(data)
  const currentBaseDailyRate = configuration.baseDailyRate ?? vehicle.dailyRate
  const currentWeeklyRate = configuration.weeklyRate ?? null

  const changed = [
    'pricingMode',
    'baseDailyRate',
    'weeklyRate',
    'weekendRate',
    'holidayRate',
    'monthlyRate',
    'longTermDailyRate',
    'minimumDailyRate',
    'maximumDailyRate',
    'maxDailyPriceMovementPct',
    'automaticPricingEnabled',
    'approvalRequired',
  ].some((key) => ((configuration as any)[key] ?? null) !== ((nextConfig as any)[key] ?? null))

  await repo.updatePricingConfiguration(configuration.id, nextConfig)

  if (vehicle.dailyRate !== nextConfig.baseDailyRate) {
    await repo.updateById(id, { dailyRate: nextConfig.baseDailyRate })
  }

  if (changed) {
    await repo.createPriceHistory({
      configurationId: configuration.id,
      vehicleId: id,
      source: 'CONFIG_UPDATE',
      changedByEmployeeId: employeeId,
      previousDailyRate: currentBaseDailyRate,
      nextDailyRate: nextConfig.baseDailyRate,
      previousWeeklyRate: currentWeeklyRate,
      nextWeeklyRate: nextConfig.weeklyRate ?? null,
      note: data.note,
    })
  }

  return getVehiclePricing(id, companyId)
}

export async function createVehiclePricingRule(id: string, companyId: string, employeeId: string, data: any) {
  const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId)
  if (!persistenceAvailable) {
    throw new AppError(
      'Vehicle pricing tables are not available yet. Run the latest database migration, then retry.',
      503,
      'pricing_migration_required',
    )
  }
  const payload = normalizePricingRuleInput(data)

  const rule = await repo.createPricingRule({
    configurationId: configuration.id,
    ...payload,
  })

  await repo.createPriceHistory({
    configurationId: configuration.id,
    vehicleId: id,
    source: payload.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_CREATED',
    changedByEmployeeId: employeeId,
    previousDailyRate: configuration.baseDailyRate ?? null,
    nextDailyRate: payload.dailyRate,
    previousWeeklyRate: configuration.weeklyRate ?? null,
    nextWeeklyRate: payload.weeklyRate,
    note: `Created pricing rule: ${rule.name}`,
    effectiveFrom: rule.startDate,
  })

  return getVehiclePricing(id, companyId)
}

export async function updateVehiclePricingRule(id: string, companyId: string, ruleId: string, employeeId: string, data: any) {
  const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId)
  if (!persistenceAvailable) {
    throw new AppError(
      'Vehicle pricing tables are not available yet. Run the latest database migration, then retry.',
      503,
      'pricing_migration_required',
    )
  }
  const existingRule = await repo.findPricingRule(ruleId, configuration.id)
  if (!existingRule) throw new NotFoundError('Pricing rule not found')

  const mergedRule = normalizePricingRuleInput({
    ...existingRule,
    ...data,
    startDate: data.startDate ?? existingRule.startDate,
    endDate: data.endDate ?? existingRule.endDate,
  })

  const updatedRule = await repo.updatePricingRule(ruleId, mergedRule)
  await repo.createPriceHistory({
    configurationId: configuration.id,
    vehicleId: id,
    source: mergedRule.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_UPDATED',
    changedByEmployeeId: employeeId,
    previousDailyRate: existingRule.dailyRate,
    nextDailyRate: updatedRule.dailyRate,
    previousWeeklyRate: existingRule.weeklyRate,
    nextWeeklyRate: updatedRule.weeklyRate,
    note: `Updated pricing rule: ${updatedRule.name}`,
    effectiveFrom: updatedRule.startDate,
  })

  return getVehiclePricing(id, companyId)
}

export async function deleteVehiclePricingRule(id: string, companyId: string, ruleId: string, employeeId: string) {
  const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId)
  if (!persistenceAvailable) {
    throw new AppError(
      'Vehicle pricing tables are not available yet. Run the latest database migration, then retry.',
      503,
      'pricing_migration_required',
    )
  }
  const existingRule = await repo.findPricingRule(ruleId, configuration.id)
  if (!existingRule) throw new NotFoundError('Pricing rule not found')

  await repo.deletePricingRule(ruleId, configuration.id)
  await repo.createPriceHistory({
    configurationId: configuration.id,
    vehicleId: id,
    source: existingRule.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_DELETED',
    changedByEmployeeId: employeeId,
    previousDailyRate: existingRule.dailyRate,
    nextDailyRate: configuration.baseDailyRate ?? null,
    previousWeeklyRate: existingRule.weeklyRate,
    nextWeeklyRate: configuration.weeklyRate ?? null,
    note: `Deleted pricing rule: ${existingRule.name}`,
    effectiveFrom: existingRule.startDate,
  })

  return getVehiclePricing(id, companyId)
}
