import { nanoid } from "nanoid";
import type { Store } from "@/lib/data/store";
import type {
  MoneySettings,
  WalletTransaction,
  WalletTransactionType,
  WithdrawalRequest,
} from "@/lib/types";

/** Money is held in whole cents to avoid floating-point drift. */
export const toCents = (amount: number) => Math.round(amount * 100);
export const fromCents = (cents: number) => cents / 100;

/**
 * Money already promised to a pending withdrawal.
 *
 * Without this someone could request a payout and spend the same balance
 * before an admin approved it, leaving the site to cover the difference.
 */
export async function reservedFor(store: Store, userId: string) {
  const withdrawals = await store.small<WithdrawalRequest[]>("withdrawals", []);
  return withdrawals
    .filter((w) => w.userId === userId && w.status === "pending")
    .reduce((sum, w) => sum + w.amount, 0);
}

/**
 * Moves money between two people, taking the platform's commission and
 * recording a transaction for each side. Everything that touches balances
 * goes through here, so the rules can't drift apart.
 *
 * Takes the store rather than the whole database, so it works the same on
 * the demo file and on MySQL. Now async, because on a database reading a
 * balance is a query.
 */
export async function transfer(
  store: Store,
  opts: {
    fromId: string | null; // null when the platform pays out
    toId: string | null; // null when the platform collects
    amount: number;
    typeFrom: WalletTransactionType;
    typeTo: WalletTransactionType;
    description: string;
    takeCommission?: boolean;
  }
): Promise<{ ok: true } | { ok: false; error: string }> {
  const { fromId, toId, amount, description } = opts;

  if (!(amount > 0)) return { ok: false, error: "Enter an amount above zero" };

  const settings = await store.small<MoneySettings>("moneySettings", {
    enabled: false,
    commissionPercent: 0,
  } as MoneySettings);
  if (!settings.enabled) {
    return { ok: false, error: "Payments are turned off on this site" };
  }

  const sender = fromId ? await store.users.get(fromId) : null;
  const receiver = toId ? await store.users.get(toId) : null;

  if (fromId && !sender) return { ok: false, error: "Sender not found" };
  if (toId && !receiver) return { ok: false, error: "Recipient not found" };

  // Money already promised to a pending withdrawal can't be spent as well.
  if (sender && sender.walletBalance - (await reservedFor(store, sender.id)) < amount) {
    return { ok: false, error: "Not enough in your wallet" };
  }

  // Commission comes out of the receiver's side, as it would on a real platform.
  // The Payments page writes to siteSettings; moneySettings is the older
  // home for the same idea. Read the new one, fall back to the old.
  const set = await store.settings();
  const feePercent =
    set.paymentFeesEnabled === false
      ? 0
      : Number(set.paymentFeesPercentage ?? settings.commissionPercent);

  const vatPercent = set.paymentVatEnabled === true
    ? Number(set.paymentVatPercentage ?? 0)
    : 0;

  const commission = opts.takeCommission
    ? Math.round(amount * ((feePercent + vatPercent) / 100) * 100) / 100
    : 0;
  const netToReceiver = Math.round((amount - commission) * 100) / 100;

  const at = new Date().toISOString();

  // The ledger is read once and written once, however many lines this
  // transfer adds to it.
  const ledger = await store.small<WalletTransaction[]>("walletTransactions", []);

  if (sender) {
    sender.walletBalance = Math.round((sender.walletBalance - amount) * 100) / 100;
    await store.users.put(sender);
    ledger.unshift({
      id: nanoid(8),
      userId: sender.id,
      type: opts.typeFrom,
      amount: -amount,
      description,
      createdAt: at,
    });
  }

  if (receiver) {
    receiver.walletBalance =
      Math.round((receiver.walletBalance + netToReceiver) * 100) / 100;
    await store.users.put(receiver);
    ledger.unshift({
      id: nanoid(8),
      userId: receiver.id,
      type: opts.typeTo,
      amount: netToReceiver,
      description,
      createdAt: at,
    });

    if (commission > 0) {
      ledger.unshift({
        id: nanoid(8),
        userId: receiver.id,
        type: "commission",
        amount: -commission,
        description:
          vatPercent > 0
            ? `Platform fee (${feePercent}%) and VAT (${vatPercent}%)`
            : `Platform fee (${feePercent}%)`,
        createdAt: at,
      });
    }
  }

  await store.putSmall("walletTransactions", ledger);

  return { ok: true };
}

/** What someone can actually withdraw right now. */
export async function withdrawable(store: Store, userId: string) {
  const user = await store.users.get(userId);
  const pending = await reservedFor(store, userId);
  return Math.max(0, (user?.walletBalance ?? 0) - pending);
}
