import type { NewVendorSplit } from '@platform-modules/commerce-orders'
import { SplitIntegrityError } from './errors.js'

type BucketKey = string | null

export function computeVendorSplits(input: {
  lines: { vendorId: string | null; lineTotal: bigint }[]
  subtotal: bigint
  total: bigint
  rates: Map<string, number>
}): NewVendorSplit[] {
  const { lines, subtotal, total, rates } = input

  // Precondition (enforced, not assumed): a negative total/subtotal would make floor
  // shares negative and break the amounts ≥ 0 invariant below. The caller guarantees
  // non-negative order money; assert it here so this pure seam can't silently emit
  // negative splits if that invariant ever regresses upstream.
  if (total < 0n || subtotal < 0n) {
    throw new Error('computeVendorSplits: negative total or subtotal')
  }

  // Checked before the zero-subtotal short-circuit below: a subtotal===0n order can
  // still carry offsetting non-zero lines (e.g. +200n/-200n), which the short-circuit
  // would otherwise silently drop instead of routing through this guard.
  for (const line of lines) {
    // A negative line makes its bucket's floor-share truncate toward zero (not
    // floor) and can emit a NEGATIVE split that still sums to total.
    if (line.lineTotal < 0n) {
      throw new SplitIntegrityError({ reason: 'negative lineTotal', vendorId: line.vendorId ?? undefined })
    }
  }

  if (subtotal === 0n) {
    if (total === 0n) {
      return [{ vendorId: null, amount: 0n, funder: 'platform' }]
    }
    throw new Error('computeVendorSplits: total without lineable subtotal')
  }

  const bucketOrder: BucketKey[] = []
  const bucketSums = new Map<BucketKey, bigint>()
  let lineSum = 0n

  for (const line of lines) {
    const key = line.vendorId
    if (!bucketSums.has(key)) {
      bucketOrder.push(key)
      bucketSums.set(key, 0n)
    }
    bucketSums.set(key, bucketSums.get(key)! + line.lineTotal)
    lineSum += line.lineTotal
  }

  if (lineSum !== subtotal) {
    throw new Error('computeVendorSplits: line totals do not sum to subtotal')
  }

  const floorShares = new Map<BucketKey, bigint>()
  let floorSum = 0n
  const remainderRanks: Array<{ key: BucketKey; remainder: bigint; order: number }> = []

  for (let order = 0; order < bucketOrder.length; order++) {
    const key = bucketOrder[order]!
    const bucketSum = bucketSums.get(key)!
    const product = total * bucketSum
    const floorShare = product / subtotal
    const remainder = product % subtotal
    floorShares.set(key, floorShare)
    floorSum += floorShare
    remainderRanks.push({ key, remainder, order })
  }

  const shares = new Map(floorShares)
  let leftover = total - floorSum

  remainderRanks.sort((a, b) => {
    if (a.remainder !== b.remainder) {
      return a.remainder > b.remainder ? -1 : 1
    }
    return a.order - b.order
  })

  for (let i = 0; i < Number(leftover); i++) {
    const key = remainderRanks[i]!.key
    shares.set(key, (shares.get(key) ?? 0n) + 1n)
  }

  let totalCommission = 0n
  const vendorSplits: NewVendorSplit[] = []

  for (const key of bucketOrder) {
    if (key === null) {
      continue
    }
    const share = shares.get(key)!
    const rate = rates.get(key) ?? 0
    // Money integrity: Σ shares == total is necessary but NOT sufficient — an
    // out-of-[0,10000] rate yields negative commission/amount that still sums to
    // total. Validate at this pure-function seam so commission ≤ share; with the
    // non-negative-total precondition above this makes every emitted amount ≥ 0.
    if (!Number.isInteger(rate) || rate < 0 || rate > 10000) {
      throw new Error(`computeVendorSplits: commission rate out of range for vendor ${key}: ${rate}`)
    }
    const commission = (share * BigInt(rate)) / 10000n
    totalCommission += commission
    vendorSplits.push({
      vendorId: key,
      amount: share - commission,
      funder: 'vendor',
    })
  }

  const platformOwnShare = shares.get(null) ?? 0n

  return [
    ...vendorSplits,
    {
      vendorId: null,
      amount: platformOwnShare + totalCommission,
      funder: 'platform',
    },
  ]
}
