import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { LedgerDirection, LedgerStatus, OrderStatus, UserRole, WalletTransactionType, WalletType, WithdrawalStatus } from '@prisma/client';
import { PrismaService } from '../database/prisma.service';
import { CalculateOrderFinanceDto, CreateWithdrawalDto } from './dto/finance.dto';

interface RequestUser {
  sub: string;
  role: UserRole;
}

const defaultDeliverySettings = {
  baseFee: 6,
  pricePerKm: 2,
  minimumDeliveryFee: 7,
  maximumDeliveryFee: 40,
  platformDeliveryMargin: 0,
  peakHourMultiplier: 1.18,
  rainMultiplier: 1.22,
  lowDriverSupplyMultiplier: 1.2,
  defaultRestaurantCommission: 0.18,
  serviceFeeFixed: 0,
  serviceFeePercentage: 0,
  withdrawalFee: 2.5,
  premiumMonthlyFee: 24.9,
};

const defaultSettlementPolicy = {
  settlementFlow: 'platform_collects_and_repasses',
  platformReceivesAllOnlinePayments: true,
  restaurantPayoutDelayDays: 7,
};

const weeklyRestaurantPayoutWeekday = 3;

@Injectable()
export class FinanceService {
  constructor(private readonly prisma: PrismaService) {}

  async calculateOrder(body: CalculateOrderFinanceDto) {
    const settings = await this.getDeliverySettings();
    const dynamic =
      body.deliveryFeeOverride !== undefined
        ? {
            delivery_fee_customer: Number(Number(body.deliveryFeeOverride).toFixed(2)),
            driver_earning: Number(Number(body.deliveryFeeOverride).toFixed(2)),
            platform_delivery_profit: 0,
            applied_multipliers: {
              peakMultiplier: 1,
              rainMultiplier: 1,
              lowDriverMultiplier: 1,
              demandMultiplier: 1,
              zoneMultiplier: 1,
            },
          }
        : this.calculateDynamicDeliveryFee(
            {
              distanceKm: body.distanceKm,
              availableDrivers: body.availableDrivers ?? 0,
              pendingOrders: body.pendingOrders ?? 0,
              weatherCondition: body.weatherCondition ?? 'sunny',
              currentTime: new Date(),
              cityZone: body.cityZone ?? 'default',
            },
            settings,
          );

    const commissionRate = body.restaurantCommissionPercentage ?? Number(settings.defaultRestaurantCommission);
    const restaurantCommission = Number((body.orderSubtotal * commissionRate).toFixed(2));
    const serviceFee = 0;
    const gross = Number((body.orderSubtotal + dynamic.delivery_fee_customer).toFixed(2));
    const platformNet = Number(restaurantCommission.toFixed(2));

    return {
      orderSubtotal: body.orderSubtotal,
      commissionRate,
      serviceFee,
      restaurantCommission,
      gross,
      platformNet,
      ...dynamic,
    };
  }

  async getDashboard(user?: RequestUser) {
    const restaurant = user?.role === UserRole.RESTAURANT
      ? await this.prisma.restaurant.findUnique({ where: { userId: user.sub }, select: { id: true, userId: true } })
      : null;
    const driver = user?.role === UserRole.DRIVER
      ? await this.prisma.driver.findUnique({ where: { userId: user.sub }, select: { id: true, userId: true } })
      : null;

    const [orders, deliveries, withdrawals, wallets, ledger] = await Promise.all([
      this.prisma.order.findMany({
        where: restaurant
          ? { restaurantId: restaurant.id }
          : driver
            ? { delivery: { driverId: driver.id } }
            : undefined,
        include: {
          restaurant: {
            select: {
              commissionPercentage: true,
            },
          },
        },
      }),
      this.prisma.delivery.findMany({
        where: restaurant
          ? { order: { restaurantId: restaurant.id } }
          : driver
            ? { driverId: driver.id }
            : undefined,
      }),
      this.prisma.withdrawal.findMany({
        where: restaurant
          ? { wallet: { restaurantId: restaurant.id } }
          : driver
            ? { wallet: { userId: user?.sub } }
            : undefined,
      }),
      this.prisma.wallet.findMany({
        where: restaurant
          ? { restaurantId: restaurant.id }
          : driver
            ? { userId: user?.sub }
            : undefined,
      }),
      this.prisma.financialLedger.findMany({
        where: restaurant
          ? { OR: [{ userId: restaurant.userId }, { order: { restaurantId: restaurant.id } }] }
          : driver
            ? { userId: user?.sub }
            : undefined,
      }),
    ]);
    const settlementPolicy = await this.getSettlementPolicy();

    const grossRevenue = orders.reduce((sum, order) => sum + Number(order.total), 0);
    const totalCommission = ledger
      .filter((entry) => entry.type === 'RESTAURANT_COMMISSION' && entry.direction === LedgerDirection.IN)
      .reduce((sum, entry) => sum + Number(entry.amount), 0);
    const restaurantPayoutTotal = ledger
      .filter((entry) => entry.type === 'RESTAURANT_REVENUE' && entry.direction === LedgerDirection.OUT)
      .reduce((sum, entry) => sum + Number(entry.amount), 0);
    const legacyDeliveryProfit = deliveries.reduce((sum, delivery) => sum + Number(delivery.platformDeliveryProfit), 0);
    const driverPayoutTotal = deliveries.reduce((sum, delivery) => sum + Number(delivery.driverEarning), 0);
    const legacyServiceFees = orders.reduce((sum, order) => sum + Number(order.serviceFee), 0);
    const deliveryProfit = 0;
    const serviceFees = 0;
    const platformRevenue = Number(totalCommission.toFixed(2));
    const payoutState = this.calculatePayoutState({
      orders,
      deliveries,
      commissionPercentage: restaurant ? undefined : undefined,
      payoutDelayDays: settlementPolicy.restaurantPayoutDelayDays,
    });
    const driverGross = deliveries.reduce((sum, delivery) => sum + Number(delivery.driverEarning), 0);
    const deliveredRuns = deliveries.filter((delivery) => delivery.status === 'DELIVERED').length;

    if (driver) {
      return {
        grossRevenue: driverGross,
        netRevenue: Number(wallets[0]?.balance ?? 0),
        platformNetRevenue: 0,
        restaurantPayoutTotal: 0,
        availablePayoutAmount: Number(wallets[0]?.balance ?? 0),
        pendingPayoutAmount: Number(wallets[0]?.blockedBalance ?? 0),
        nextPayoutDate: null,
        payoutDelayDays: 0,
        totalCommission: 0,
        deliveryProfit: 0,
        driverPayoutTotal: driverGross,
        serviceFees: 0,
        subscriptionRevenue: 0,
        adsRevenue: 0,
        withdrawalsPending: withdrawals.filter((item) => item.status === WithdrawalStatus.PENDING).length,
        walletBalances: wallets.map((wallet) => ({ type: wallet.type, balance: Number(wallet.balance), blockedBalance: Number(wallet.blockedBalance) })),
        paidOrders: deliveredRuns,
        cancelledOrders: deliveries.filter((delivery) => delivery.status === 'CANCELLED' || delivery.status === 'FAILED').length,
        refunds: 0,
        averageTicket: deliveredRuns ? Number((driverGross / deliveredRuns).toFixed(2)) : 0,
      };
    }

    return {
      grossRevenue,
      netRevenue: restaurant ? restaurantPayoutTotal : platformRevenue,
      platformNetRevenue: platformRevenue,
      restaurantPayoutTotal,
      availablePayoutAmount: payoutState.availablePayoutAmount,
      pendingPayoutAmount: payoutState.pendingPayoutAmount,
      nextPayoutDate: payoutState.nextPayoutDate,
      payoutDelayDays: settlementPolicy.restaurantPayoutDelayDays,
      totalCommission,
      deliveryProfit,
      driverPayoutTotal,
      serviceFees,
      subscriptionRevenue: ledger.filter((entry) => entry.type === 'SUBSCRIPTION_PAYMENT').reduce((sum, entry) => sum + Number(entry.amount), 0),
      adsRevenue: ledger.filter((entry) => entry.type === 'AD_PAYMENT').reduce((sum, entry) => sum + Number(entry.amount), 0),
      withdrawalsPending: withdrawals.filter((item) => item.status === WithdrawalStatus.PENDING).length,
      walletBalances: wallets.map((wallet) => ({ type: wallet.type, balance: Number(wallet.balance), blockedBalance: Number(wallet.blockedBalance) })),
      paidOrders: orders.filter((order) => order.status === OrderStatus.DELIVERED).length,
      cancelledOrders: orders.filter((order) => order.status === OrderStatus.CANCELLED || order.status === OrderStatus.REFUNDED).length,
      refunds: ledger.filter((entry) => entry.type === 'REFUND').reduce((sum, entry) => sum + Number(entry.amount), 0),
      averageTicket: orders.length ? Number((grossRevenue / orders.length).toFixed(2)) : 0,
    };
  }

  async getRestaurantsSummary() {
    const [restaurants, orders, deliveries, withdrawals] = await Promise.all([
      this.prisma.restaurant.findMany({
        include: {
          user: { select: { email: true, phone: true } },
          wallet: true,
        },
        orderBy: { name: 'asc' },
      }),
      this.prisma.order.findMany({
        include: {
          restaurant: true,
          delivery: true,
        },
      }),
      this.prisma.delivery.findMany({
        include: {
          order: {
            select: { restaurantId: true },
          },
        },
      }),
      this.prisma.withdrawal.findMany({
        include: {
          wallet: {
            select: {
              restaurantId: true,
            },
          },
        },
      }),
    ]);
    const settlementPolicy = await this.getSettlementPolicy();

    return restaurants.map((restaurant) => {
      const commissionRate = Number(restaurant.commissionPercentage ?? 0);
      const restaurantOrders = orders.filter((order) => order.restaurantId === restaurant.id);
      const restaurantDeliveries = deliveries.filter((delivery) => delivery.order.restaurantId === restaurant.id);
      const restaurantWithdrawals = withdrawals.filter((withdrawal) => withdrawal.wallet.restaurantId === restaurant.id);
      const totalSold = restaurantOrders.reduce((sum, order) => sum + Number(order.subtotal), 0);
      const customerGross = restaurantOrders.reduce((sum, order) => sum + Number(order.total), 0);
      const commissionAmount = Number((totalSold * (commissionRate / 100)).toFixed(2));
      const legacyServiceFees = restaurantOrders.reduce((sum, order) => sum + Number(order.serviceFee), 0);
      const legacyDeliveryProfit = restaurantDeliveries.reduce((sum, delivery) => sum + Number(delivery.platformDeliveryProfit), 0);
      const driverPayoutTotal = restaurantDeliveries.reduce((sum, delivery) => sum + Number(delivery.driverEarning), 0);
      const serviceFees = 0;
      const deliveryProfit = 0;
      const payoutToRestaurant = Number((totalSold - commissionAmount).toFixed(2));
      const platformNet = commissionAmount;
      const payoutState = this.calculatePayoutState({
        orders: restaurantOrders,
        deliveries: restaurantDeliveries,
        commissionPercentage: commissionRate,
        payoutDelayDays: settlementPolicy.restaurantPayoutDelayDays,
        withdrawals: restaurantWithdrawals.map((withdrawal) => ({
          amount: Number(withdrawal.amount),
          feeAmount: Number(withdrawal.feeAmount),
          status: withdrawal.status,
        })),
        walletBalance: Number(restaurant.wallet?.balance ?? 0),
      });

      return {
        id: restaurant.id,
        name: restaurant.name,
        pixKey: restaurant.pixKey,
        city: restaurant.city,
        neighborhood: restaurant.neighborhood,
        status: restaurant.status,
        ownerEmail: restaurant.user.email,
        ownerPhone: restaurant.user.phone,
        commissionPercentage: commissionRate,
        totalSold,
        customerGross,
        commissionAmount,
        payoutToRestaurant,
        serviceFees,
        deliveryProfit,
        driverPayoutTotal,
        platformNet,
        legacyServiceFees,
        legacyDeliveryProfit,
        orderCount: restaurantOrders.length,
        walletBalance: Number(restaurant.wallet?.balance ?? 0),
        availablePayoutAmount: payoutState.availablePayoutAmount,
        pendingPayoutAmount: payoutState.pendingPayoutAmount,
        nextPayoutDate: payoutState.nextPayoutDate,
        payoutDelayDays: settlementPolicy.restaurantPayoutDelayDays,
      };
    });
  }

  async getLedger(user?: RequestUser) {
    const restaurant = user?.role === UserRole.RESTAURANT
      ? await this.prisma.restaurant.findUnique({ where: { userId: user.sub }, select: { id: true, userId: true } })
      : null;
    const driver = user?.role === UserRole.DRIVER
      ? await this.prisma.driver.findUnique({ where: { userId: user.sub }, select: { id: true, userId: true } })
      : null;

    return this.prisma.financialLedger.findMany({
      where: restaurant
        ? { OR: [{ userId: restaurant.userId }, { order: { restaurantId: restaurant.id } }] }
        : driver
          ? { userId: user?.sub }
          : undefined,
      orderBy: { createdAt: 'desc' },
      take: 300,
    });
  }

  async requestWithdrawal(user: RequestUser, body: CreateWithdrawalDto) {
    const wallet = await this.ensureWallet(user.sub, this.resolveWalletType(user.role));
    const settings = await this.getDeliverySettings();
    const feeAmount = Number(settings.withdrawalFee ?? defaultDeliverySettings.withdrawalFee);
    const requestedAmount = Number(body.amount);

    if (requestedAmount <= 0) {
      throw new BadRequestException('Valor invalido para saque.');
    }
    let availableAfterHold = Number(wallet.balance);
    if (user.role === UserRole.RESTAURANT) {
      const payoutState = await this.getRestaurantPayoutAvailability(user.sub, Number(wallet.balance));
      availableAfterHold = payoutState.availablePayoutAmount;
    }
    if (availableAfterHold < requestedAmount + feeAmount || Number(wallet.balance) < requestedAmount + feeAmount) {
      throw new BadRequestException('Saldo insuficiente para sacar e cobrir a taxa.');
    }

    return this.prisma.$transaction(async (tx) => {
      const updatedWallet = await tx.wallet.update({
        where: { id: wallet.id },
        data: {
          balance: { decrement: requestedAmount + feeAmount },
          blockedBalance: { increment: requestedAmount },
        },
      });

      const withdrawal = await tx.withdrawal.create({
        data: {
          walletId: wallet.id,
          amount: requestedAmount,
          feeAmount,
          pixKey: body.pixKey,
          status: WithdrawalStatus.PENDING,
        },
      });

      await tx.walletTransaction.create({
        data: {
          walletId: wallet.id,
          type: WalletTransactionType.WITHDRAWAL,
          amount: requestedAmount + feeAmount,
          description: 'Solicitacao de saque criada',
          referenceType: 'WITHDRAWAL',
          referenceId: withdrawal.id,
          status: 'PENDING',
        },
      });

      return { withdrawal, wallet: updatedWallet };
    });
  }

  async approveWithdrawal(withdrawalId: string) {
    const withdrawal = await this.prisma.withdrawal.findUnique({ where: { id: withdrawalId } });
    if (!withdrawal) {
      throw new NotFoundException('Saque nao encontrado.');
    }

    return this.prisma.$transaction(async (tx) => {
      const updatedWithdrawal = await tx.withdrawal.update({
        where: { id: withdrawalId },
        data: { status: WithdrawalStatus.APPROVED, approvedAt: new Date(), completedAt: new Date() },
      });

      const wallet = await tx.wallet.update({
        where: { id: withdrawal.walletId },
        data: { blockedBalance: { decrement: withdrawal.amount } },
      });

      await tx.walletTransaction.create({
        data: {
          walletId: withdrawal.walletId,
          type: WalletTransactionType.WITHDRAWAL,
          amount: withdrawal.amount,
          description: 'Saque aprovado e liquidado',
          referenceType: 'WITHDRAWAL',
          referenceId: withdrawalId,
          status: 'COMPLETED',
        },
      });

      return { withdrawal: updatedWithdrawal, wallet };
    });
  }

  async getDeliverySettings() {
    const setting = await this.prisma.setting.findUnique({ where: { key: 'delivery_settings' } });
    return { ...defaultDeliverySettings, ...(setting?.value as Record<string, number | string> | undefined) };
  }

  async updateDeliverySettings(value: Record<string, unknown>) {
    const current = await this.getDeliverySettings();
    return this.prisma.setting.upsert({
      where: { key: 'delivery_settings' },
      update: { value: { ...current, ...value } },
      create: { key: 'delivery_settings', value: { ...current, ...value } },
    });
  }

  private async getSettlementPolicy() {
    const setting = await this.prisma.setting.findUnique({ where: { key: 'payment_gateway_settings' } });
    const current = (setting?.value as Record<string, unknown> | null) ?? {};
    return {
      settlementFlow: String(current.settlementFlow ?? defaultSettlementPolicy.settlementFlow),
      platformReceivesAllOnlinePayments: Boolean(current.platformReceivesAllOnlinePayments ?? defaultSettlementPolicy.platformReceivesAllOnlinePayments),
      restaurantPayoutDelayDays: Number(current.restaurantPayoutDelayDays ?? defaultSettlementPolicy.restaurantPayoutDelayDays),
    };
  }

  calculateDynamicDeliveryFee(
    input: {
      distanceKm: number;
      availableDrivers: number;
      pendingOrders: number;
      weatherCondition: string;
      currentTime: Date;
      cityZone: string;
    },
    settings = defaultDeliverySettings,
  ) {
    const isPeakHour = input.currentTime.getHours() >= 11 && input.currentTime.getHours() <= 14;
    const demandRatio = input.pendingOrders > 0 ? input.pendingOrders / Math.max(input.availableDrivers, 1) : 0;
    const peakMultiplier = isPeakHour ? Number(settings.peakHourMultiplier) : 1;
    const rainMultiplier = input.weatherCondition === 'rain' ? Number(settings.rainMultiplier) : 1;
    const lowDriverMultiplier = input.availableDrivers <= 3 ? Number(settings.lowDriverSupplyMultiplier) : 1;
    const demandMultiplier = demandRatio >= 2 ? 1.18 : demandRatio >= 1 ? 1.08 : 1;
    const zoneMultiplier = input.cityZone.toLowerCase().includes('premium') ? 1.1 : 1;

    const appliedMultipliers = { peakMultiplier, rainMultiplier, lowDriverMultiplier, demandMultiplier, zoneMultiplier };
    const base = Number(settings.baseFee) + input.distanceKm * Number(settings.pricePerKm);
    const combinedMultiplier = Object.values(appliedMultipliers).reduce((acc, value) => acc * value, 1);
    const rawCustomerFee = base * combinedMultiplier;
    const delivery_fee_customer = Number(
      Math.min(Number(settings.maximumDeliveryFee), Math.max(Number(settings.minimumDeliveryFee), rawCustomerFee)).toFixed(2),
    );
    const driver_earning = delivery_fee_customer;
    const platform_delivery_profit = 0;

    return {
      delivery_fee_customer,
      driver_earning,
      platform_delivery_profit,
      applied_multipliers: appliedMultipliers,
    };
  }

  async recordOrderSettlement(orderId: string) {
    const order = await this.prisma.order.findUnique({
      where: { id: orderId },
      include: {
        restaurant: { include: { wallet: true } },
        delivery: { include: { driver: { include: { wallet: true } } } },
      },
    });
    if (!order || !order.delivery) {
      throw new NotFoundException('Pedido ou entrega nao encontrados para repasse.');
    }

    const settings = await this.getDeliverySettings();
    const commissionRate = Number(order.restaurant.commissionPercentage ?? Number(settings.defaultRestaurantCommission) * 100) / 100;
    const commission = Number((Number(order.subtotal) * commissionRate).toFixed(2));
    const restaurantNet = Number((Number(order.subtotal) - commission).toFixed(2));
    const serviceFee = 0;
    const deliveryProfit = 0;
    const driverEarning = Number(
      order.delivery.deliveryFeeCustomer ?? order.delivery.driverEarning ?? order.deliveryFee,
    );

    const existingSettlement = await this.prisma.financialLedger.findFirst({
      where: {
        orderId: order.id,
        type: 'RESTAURANT_COMMISSION',
        status: LedgerStatus.POSTED,
      },
    });

    if (existingSettlement) {
      return { ok: true, orderId: order.id, restaurantNet, driverEarning, commission, serviceFee, deliveryProfit, alreadySettled: true };
    }

    return this.prisma.$transaction(async (tx) => {
      const restaurantWallet = order.restaurant.wallet ?? (await tx.wallet.create({ data: { restaurantId: order.restaurant.id, userId: order.restaurant.userId, type: WalletType.RESTAURANT } }));
      const driverWallet = order.delivery?.driver?.wallet ?? (order.delivery?.driver ? await tx.wallet.create({ data: { driverId: order.delivery.driver.id, userId: order.delivery.driver.userId, type: WalletType.DRIVER } }) : null);
      const platformWallet = (await tx.wallet.findFirst({ where: { type: WalletType.PLATFORM } })) ?? (await tx.wallet.create({ data: { type: WalletType.PLATFORM } }));

      await tx.wallet.update({ where: { id: restaurantWallet.id }, data: { balance: { increment: restaurantNet } } });
      if (driverWallet) {
        await tx.wallet.update({ where: { id: driverWallet.id }, data: { balance: { increment: driverEarning } } });
      }
      await tx.wallet.update({ where: { id: platformWallet.id }, data: { balance: { increment: commission } } });

      await tx.financialLedger.createMany({
        data: [
          { orderId: order.id, userId: order.restaurant.userId, type: 'RESTAURANT_REVENUE', amount: restaurantNet, direction: LedgerDirection.OUT, description: 'Repasse ao restaurante', status: LedgerStatus.POSTED },
          ...(order.delivery?.driver ? [{ orderId: order.id, userId: order.delivery.driver.userId, type: 'DRIVER_EARNING', amount: driverEarning, direction: LedgerDirection.OUT, description: 'Repasse ao entregador', status: LedgerStatus.POSTED }] : []),
          { orderId: order.id, userId: null, type: 'RESTAURANT_COMMISSION', amount: commission, direction: LedgerDirection.IN, description: 'Comissao da plataforma', status: LedgerStatus.POSTED },
        ],
      });

      return { ok: true, orderId: order.id, restaurantNet, driverEarning, commission, serviceFee, deliveryProfit, alreadySettled: false };
    });
  }

  private async getRestaurantPayoutAvailability(userId: string, walletBalance: number) {
    const restaurant = await this.prisma.restaurant.findUnique({
      where: { userId },
      include: {
        orders: true,
        wallet: {
          include: {
            withdrawals: true,
          },
        },
      },
    });
    if (!restaurant) {
      throw new NotFoundException('Restaurante nao encontrado para calcular repasse.');
    }
    const deliveries = await this.prisma.delivery.findMany({
      where: {
        order: {
          restaurantId: restaurant.id,
        },
      },
    });
    const settlementPolicy = await this.getSettlementPolicy();

    return this.calculatePayoutState({
      orders: restaurant.orders,
      deliveries,
      commissionPercentage: Number(restaurant.commissionPercentage ?? 0),
      payoutDelayDays: settlementPolicy.restaurantPayoutDelayDays,
      withdrawals: (restaurant.wallet?.withdrawals ?? []).map((withdrawal) => ({
        amount: Number(withdrawal.amount),
        feeAmount: Number(withdrawal.feeAmount),
        status: withdrawal.status,
      })),
      walletBalance,
    });
  }

  private calculatePayoutState(input: {
    orders: Array<{ id: string; subtotal: unknown; total: unknown; serviceFee: unknown; restaurant?: { commissionPercentage?: unknown } | null }>;
    deliveries: Array<{ orderId: string; deliveredAt: Date | null }>;
    commissionPercentage?: number;
    payoutDelayDays: number;
    withdrawals?: Array<{ amount: number; feeAmount: number; status: WithdrawalStatus }>;
    walletBalance?: number;
  }) {
    const deliveredAtByOrderId = new Map(input.deliveries.map((delivery) => [delivery.orderId, delivery.deliveredAt]));
    const now = Date.now();
    const delayMs = Math.max(0, input.payoutDelayDays) * 24 * 60 * 60 * 1000;
    let availablePayoutAmount = 0;
    let pendingPayoutAmount = 0;
    let nextPayoutDate: string | null = null;

    for (const order of input.orders) {
      const deliveredAt = deliveredAtByOrderId.get(order.id);
      if (!deliveredAt) {
        continue;
      }
      const orderCommissionPercentage = input.commissionPercentage ?? Number(order.restaurant?.commissionPercentage ?? 0);
      const commissionRate = orderCommissionPercentage / 100;
      const orderNet = Number((Number(order.subtotal) - Number(order.subtotal) * commissionRate).toFixed(2));
      const eligibleAt = deliveredAt.getTime() + delayMs;
      const scheduledPayoutAt = this.alignToWeeklyPayout(eligibleAt);
      if (scheduledPayoutAt <= now) {
        availablePayoutAmount += orderNet;
      } else {
        pendingPayoutAmount += orderNet;
        const iso = new Date(scheduledPayoutAt).toISOString();
        if (!nextPayoutDate || iso < nextPayoutDate) {
          nextPayoutDate = iso;
        }
      }
    }

    const committedWithdrawals = (input.withdrawals ?? [])
      .filter((withdrawal) => withdrawal.status !== WithdrawalStatus.REJECTED)
      .reduce((sum, withdrawal) => sum + withdrawal.amount + withdrawal.feeAmount, 0);
    const grossAvailable = Number(availablePayoutAmount.toFixed(2));
    const cappedAvailable =
      input.walletBalance === undefined
        ? grossAvailable
        : Math.max(0, Math.min(Number(input.walletBalance), Number((grossAvailable - committedWithdrawals).toFixed(2))));

    return {
      availablePayoutAmount: Number(cappedAvailable.toFixed(2)),
      pendingPayoutAmount: Number(pendingPayoutAmount.toFixed(2)),
      nextPayoutDate,
    };
  }

  private async ensureWallet(userId: string, type: WalletType) {
    const wallet = await this.prisma.wallet.findFirst({ where: { userId } });
    if (wallet) {
      return wallet;
    }
    return this.prisma.wallet.create({ data: { userId, type } });
  }

  private resolveWalletType(role: UserRole) {
    if (role === UserRole.DRIVER) {
      return WalletType.DRIVER;
    }
    if (role === UserRole.RESTAURANT) {
      return WalletType.RESTAURANT;
    }
    return WalletType.CUSTOMER;
  }

  private alignToWeeklyPayout(timestamp: number) {
    const scheduled = new Date(timestamp);
    scheduled.setHours(9, 0, 0, 0);

    while (scheduled.getDay() !== weeklyRestaurantPayoutWeekday) {
      scheduled.setDate(scheduled.getDate() + 1);
    }

    if (scheduled.getTime() < timestamp) {
      scheduled.setDate(scheduled.getDate() + 7);
    }

    return scheduled.getTime();
  }
}
