import { PromoValidationError } from './errors.js'
import { anyLineMatchesScope } from './scope.js'
import { assertPositiveInt, type Promo, type PromoContext, type PromoValidation } from './types.js'

export function assertPromoShape(promo: Promo): void {
  const hasMoneyField =
    promo.valueAmount !== undefined ||
    promo.minOrderAmount !== undefined ||
    promo.maxDiscountAmount !== undefined ||
    promo.maxOrderAmount !== undefined
  if (hasMoneyField && promo.currency === undefined) {
    throw new PromoValidationError('currency')
  }

  if (promo.maxOrderAmount !== undefined) {
    if (promo.maxOrderAmount <= 0n) {
      throw new PromoValidationError('maxOrderAmount')
    }
    if (
      promo.minOrderAmount !== undefined &&
      promo.maxOrderAmount <= promo.minOrderAmount
    ) {
      throw new PromoValidationError('maxOrderAmount')
    }
  }

  switch (promo.kind) {
    case 'percentage': {
      if (promo.valueBps === undefined) {
        throw new PromoValidationError('valueBps')
      }
      if (promo.valueBps <= 0 || promo.valueBps > 10000) {
        throw new PromoValidationError('valueBps')
      }
      if (promo.valueAmount !== undefined) {
        throw new PromoValidationError('valueAmount')
      }
      if (promo.bogo !== undefined) {
        throw new PromoValidationError('bogo')
      }
      if (promo.maxDiscountAmount !== undefined && promo.maxDiscountAmount <= 0n) {
        throw new PromoValidationError('maxDiscountAmount')
      }
      break
    }
    case 'fixed': {
      if (promo.valueAmount === undefined) {
        throw new PromoValidationError('valueAmount')
      }
      if (promo.valueAmount <= 0n) {
        throw new PromoValidationError('valueAmount')
      }
      if (promo.valueBps !== undefined) {
        throw new PromoValidationError('valueBps')
      }
      if (promo.bogo !== undefined) {
        throw new PromoValidationError('bogo')
      }
      if (promo.maxDiscountAmount !== undefined) {
        throw new PromoValidationError('maxDiscountAmount')
      }
      break
    }
    case 'bogo': {
      if (promo.bogo === undefined) {
        throw new PromoValidationError('bogo')
      }
      if (promo.bogo.buyQty === undefined || promo.bogo.getQty === undefined) {
        throw new PromoValidationError('bogo')
      }
      assertPositiveInt(promo.bogo.buyQty, 'bogo.buyQty')
      assertPositiveInt(promo.bogo.getQty, 'bogo.getQty')
      if (promo.valueBps !== undefined) {
        throw new PromoValidationError('valueBps')
      }
      if (promo.valueAmount !== undefined) {
        throw new PromoValidationError('valueAmount')
      }
      if (promo.maxDiscountAmount !== undefined) {
        throw new PromoValidationError('maxDiscountAmount')
      }
      break
    }
  }
}

export function validatePromo(promo: Promo, ctx: PromoContext): PromoValidation {
  assertPromoShape(promo)

  if (!promo.active) {
    return { ok: false, reason: 'inactive' }
  }

  if (promo.startsAt !== undefined && ctx.now < promo.startsAt) {
    return { ok: false, reason: 'not_started' }
  }

  if (promo.endsAt !== undefined && ctx.now > promo.endsAt) {
    return { ok: false, reason: 'expired' }
  }

  if (promo.currency !== undefined && promo.currency !== ctx.cartCurrency) {
    return { ok: false, reason: 'currency_mismatch' }
  }

  if (promo.minOrderAmount !== undefined && ctx.cartSubtotal < promo.minOrderAmount) {
    return { ok: false, reason: 'min_order_not_met' }
  }

  if (promo.maxOrderAmount !== undefined && ctx.cartSubtotal > promo.maxOrderAmount) {
    return { ok: false, reason: 'max_order_exceeded' }
  }

  if (!anyLineMatchesScope(ctx.lines, promo.scope)) {
    return { ok: false, reason: 'out_of_scope' }
  }

  if (promo.eligibility.firstPurchaseOnly && !ctx.isFirstPurchase) {
    return { ok: false, reason: 'not_first_purchase' }
  }

  if (promo.eligibility.membersOnly && !ctx.isMember) {
    return { ok: false, reason: 'not_member' }
  }

  if (promo.eligibility.allowlistOnly && !ctx.isAllowlisted) {
    return { ok: false, reason: 'not_on_allowlist' }
  }

  if (
    promo.maxUses !== undefined &&
    ctx.globalUses !== undefined &&
    ctx.globalUses >= promo.maxUses
  ) {
    return { ok: false, reason: 'quota_exhausted' }
  }

  if (
    promo.perUserCap !== undefined &&
    ctx.userRedemptionCount !== undefined &&
    ctx.userRedemptionCount >= promo.perUserCap
  ) {
    return { ok: false, reason: 'per_user_cap_reached' }
  }

  return { ok: true, promo }
}
