import { floorAgorotBps } from '@/lib/money';
import type {
  DiscountBreakdown,
  DiscountBreakdownLine,
  PromoCode,
  PromoRules,
  ResolvedCart,
  ResolvedCartLine,
} from './types.js';

export function applyPromo(code: PromoCode, cart: ResolvedCart): DiscountBreakdown {
  const rules = code.rulesJson as PromoRules;
  const matchingLines = cart.lines.filter((l) => lineMatchesScope(l, rules.scope));

  switch (code.kind) {
    case 'percentage':
      return applyPercentage(code, matchingLines);
    case 'fixed_amount':
      return applyFixed(code, matchingLines);
    case 'bogo':
      return applyBogo(code, matchingLines);
  }
}

function lineMatchesScope(line: ResolvedCartLine, scope: PromoRules['scope']): boolean {
  switch (scope.kind) {
    case 'all':
      return true;
    case 'deals':
      return scope.dealIds.includes(line.dealId);
    case 'categories':
      return line.categoryIds.some((c) => scope.categoryIds.includes(c));
    case 'tags':
      return line.tagIds.some((t) => scope.tagIds.includes(t));
    case 'vendor':
      return line.vendorId === scope.vendorId;
  }
}

function applyPercentage(code: PromoCode, lines: ResolvedCartLine[]): DiscountBreakdown {
  const bps = code.valueBps ?? 0;
  const cap = code.maxCapAmount ?? Number.MAX_SAFE_INTEGER;
  const perLine: DiscountBreakdownLine[] = lines.map((l) => ({
    lineId: l.lineId,
    vendorId: l.vendorId,
    amountAgorot: floorAgorotBps(l.lineSubtotalAgorot, bps),
  }));
  let total = perLine.reduce((s, l) => s + l.amountAgorot, 0);
  if (total > cap) {
    const scale = cap / total;
    let running = 0;
    for (let i = 0; i < perLine.length - 1; i++) {
      perLine[i]!.amountAgorot = Math.floor(perLine[i]!.amountAgorot * scale);
      running += perLine[i]!.amountAgorot;
    }
    if (perLine.length > 0) perLine[perLine.length - 1]!.amountAgorot = cap - running;
    total = cap;
  }
  return { totalDiscountAgorot: total, perLine, funder: code.funder, kind: 'percentage' };
}

function applyFixed(code: PromoCode, lines: ResolvedCartLine[]): DiscountBreakdown {
  const subtotal = lines.reduce((s, l) => s + l.lineSubtotalAgorot, 0);
  const target = Math.min(code.valueAmount ?? 0, subtotal);
  const perLine: DiscountBreakdownLine[] = [];
  let running = 0;
  for (let i = 0; i < lines.length; i++) {
    const l = lines[i]!;
    const isLast = i === lines.length - 1;
    const share = isLast
      ? target - running
      : Math.floor((l.lineSubtotalAgorot * target) / subtotal);
    perLine.push({ lineId: l.lineId, vendorId: l.vendorId, amountAgorot: share });
    running += share;
  }
  return { totalDiscountAgorot: target, perLine, funder: code.funder, kind: 'fixed_amount' };
}

function applyBogo(code: PromoCode, lines: ResolvedCartLine[]): DiscountBreakdown {
  const buy = code.bogoBuy ?? 0;
  const getFree = code.bogoGetFree ?? 0;
  const groupSize = buy + getFree;

  const units: { lineId: string; vendorId: string; unitPrice: number }[] = [];
  for (const l of lines) {
    for (let i = 0; i < l.quantity; i++) {
      units.push({ lineId: l.lineId, vendorId: l.vendorId, unitPrice: l.unitPriceAgorot });
    }
  }
  units.sort((a, b) => a.unitPrice - b.unitPrice);

  const freeCount = groupSize > 0 ? Math.floor(units.length / groupSize) * getFree : 0;
  const freedUnits = units.slice(0, freeCount);

  const perLineMap = new Map<string, DiscountBreakdownLine>();
  for (const u of freedUnits) {
    const existing = perLineMap.get(u.lineId);
    if (existing) existing.amountAgorot += u.unitPrice;
    else
      perLineMap.set(u.lineId, {
        lineId: u.lineId,
        vendorId: u.vendorId,
        amountAgorot: u.unitPrice,
      });
  }
  const perLine = Array.from(perLineMap.values());
  const total = perLine.reduce((s, l) => s + l.amountAgorot, 0);
  return { totalDiscountAgorot: total, perLine, funder: code.funder, kind: 'bogo' };
}
