import { db, walletsTable, walletTransactionsTable, usersTable } from "@workspace/db";
import { eq, asc, and, sql } from "drizzle-orm";

export type WalletTxType =
  | "deposit"
  | "order_payment"
  | "earning"
  | "commission"
  | "withdrawal"
  | "refund"
  | "escrow_in"
  | "escrow_out";

type DbOrTx = typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0];

export const COMMISSION_RATE = 0.1;

export class InsufficientFundsError extends Error {
  constructor() {
    super("Insufficient wallet balance");
    this.name = "InsufficientFundsError";
  }
}

export async function getOrCreateWallet(userId: number, conn: DbOrTx = db) {
  const [existing] = await conn.select().from(walletsTable).where(eq(walletsTable.userId, userId));
  if (existing) return existing;
  const [created] = await conn
    .insert(walletsTable)
    .values({ userId, balance: 0 })
    .onConflictDoNothing()
    .returning();
  if (created) return created;
  const [after] = await conn.select().from(walletsTable).where(eq(walletsTable.userId, userId));
  return after;
}

/**
 * Atomically adjust a user's wallet balance by a signed amount and write a
 * matching ledger row. Throws InsufficientFundsError if the result would be
 * negative. Pass an existing `tx` to compose multiple adjustments atomically.
 */
export async function adjustBalance(opts: {
  userId: number;
  amount: number;
  type: WalletTxType;
  bookingId?: number | null;
  description?: string | null;
  tx?: DbOrTx;
}) {
  const run = async (conn: DbOrTx) => {
    const wallet = await getOrCreateWallet(opts.userId, conn);
    // Atomic balance update: compute the new balance in SQL and guard against a
    // negative result in the same statement, so concurrent adjustments to the
    // same wallet cannot lose updates or overdraw the balance.
    const [updated] = await conn
      .update(walletsTable)
      .set({ balance: sql`${walletsTable.balance} + ${opts.amount}` })
      .where(and(eq(walletsTable.id, wallet.id), sql`${walletsTable.balance} + ${opts.amount} >= 0`))
      .returning();
    if (!updated) throw new InsufficientFundsError();
    await conn.insert(walletTransactionsTable).values({
      userId: opts.userId,
      type: opts.type,
      amount: opts.amount,
      balanceAfter: updated.balance,
      bookingId: opts.bookingId ?? undefined,
      description: opts.description ?? undefined,
    });
    return updated;
  };
  if (opts.tx) return run(opts.tx);
  return db.transaction(run);
}

/** The platform account that receives commission (lowest-id admin). */
export async function getPlatformUserId(): Promise<number | null> {
  const [admin] = await db
    .select({ id: usersTable.id })
    .from(usersTable)
    .where(eq(usersTable.role, "admin"))
    .orderBy(asc(usersTable.id))
    .limit(1);
  return admin?.id ?? null;
}

export function splitCommission(amount: number) {
  const commission = Math.round(amount * COMMISSION_RATE);
  const modelAmount = amount - commission;
  return { commission, modelAmount };
}
