import type { Transaction } from '@platform-modules/db'
import { appendEntry } from './append.js'
import { getBalance, guardedDebitUpdate } from './balance.js'
import { InsufficientBalanceError } from './errors.js'
import type { LedgerSchema } from './schema.js'
import type { DebitInput, DebitResult } from './types.js'

/**
 * Optimistic guarded debit: idempotent ledger insert FIRST, then a guarded
 * `UPDATE … WHERE balance >= amount`. On a duplicate key (retry) the balance
 * mutation is skipped; on insufficient balance it throws and the just-inserted
 * ledger row must roll back.
 *
 * MUST run inside a transaction (`db.transaction((tx) => debit(tx, …))`). The
 * insert-first/throw-on-insufficient atomicity depends on the throw rolling the
 * ledger row back — passing a non-transactional `Querier` autocommits the insert
 * and leaves an orphan entry with the balance unmutated.
 */
export async function debit<S extends LedgerSchema>(
  tx: Transaction<S>,
  input: DebitInput,
): Promise<DebitResult> {
  if (input.amount <= 0n) {
    throw new Error('debit: amount must be positive')
  }

  const { inserted } = await appendEntry(tx, {
    key: input.key,
    delta: -input.amount,
    reason: input.reason,
    ref: input.ref,
  })

  if (!inserted) {
    return {
      inserted: false,
      balance: await getBalance(tx, input.target),
    }
  }

  const nextBalance = await guardedDebitUpdate(tx, input.target, input.amount)
  if (nextBalance == null) {
    throw new InsufficientBalanceError()
  }

  return {
    inserted: true,
    balance: nextBalance,
  }
}
