/* eslint-disable no-nested-ternary */
import { env } from '@blocklet/sdk/lib/config';
import type { TransactionInput } from '@ocap/client';
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';
import pAll from 'p-all';
import { omit } from 'lodash';
import dayjs from './dayjs';
import { validCoupon } from './discount/coupon';
import { getPriceUintAmountByCurrency, getPriceCurrencyOptions } from './price';

import {
  Coupon,
  Customer,
  Invoice,
  PaymentCurrency,
  PaymentMethod,
  PriceQuote,
  SetupIntent,
  Subscription,
  SubscriptionItem,
  type CheckoutSession,
  type TLineItemExpanded,
  type TPaymentCurrency,
  type TPaymentMethodExpanded,
} from '../store/models';
import { Price, Price as TPrice } from '../store/models/price';
import type { Product } from '../store/models/product';
import type {
  ChainType,
  PaymentBeneficiary,
  PriceRecurring,
  SubscriptionBillingThresholds,
} from '../store/models/types';
import { wallet } from './auth';
import logger from './logger';
import { getMigratedFromList } from './wallet-migration';
import { applyDiscountsToLineItems } from './discount/discount';
import { getQuoteService, QuoteResponse } from './quote-service';
import { getExchangeRateService } from './exchange-rate';
import { getExchangeRateSymbol } from './exchange-rate/token-address-mapping';
import { trimDecimals } from './math-utils';
import { normalizeSlippageConfigFromMetadata } from './slippage';
import { getSubscriptionItemPrice } from './credit-utils';

export function getStatementDescriptor(items: any[]) {
  for (const item of items) {
    if (item.price?.product?.statement_descriptor) {
      return item.price?.product?.statement_descriptor;
    }
  }

  return env.appName;
}

export function getCheckoutMode(items: TLineItemExpanded[] = []) {
  if (items.length === 0) {
    return 'setup';
  }

  // all metered
  if (items.every((x) => x.price.type === 'recurring' && x.price.recurring?.usage_type === 'metered')) {
    return 'setup';
  }

  if (items.some((x) => x.price.type === 'recurring')) {
    return 'subscription';
  }

  return 'payment';
}

// Calculate checkout amount with optional discount processing
export async function getCheckoutAmount(
  items: TLineItemExpanded[],
  currencyId: string,
  trialing = false,
  discountConfig?: {
    promotionCodeId?: string;
    couponId?: string;
    customerId?: string;
  }
) {
  let processedItems = items;
  let renew = new BN(0);

  if (items.find((x) => (x.upsell_price || x.price).custom_unit_amount) && items.length > 1) {
    throw new Error('Multiple items with custom unit amount are not supported');
  }

  // Apply discounts if discount configuration is provided
  if (discountConfig?.couponId && discountConfig?.customerId) {
    try {
      const currency = await PaymentCurrency.findByPk(currencyId);
      if (currency) {
        const discountResult = await applyDiscountsToLineItems({
          lineItems: items,
          promotionCodeId: discountConfig.promotionCodeId,
          couponId: discountConfig.couponId,
          customerId: discountConfig.customerId,
          currency,
          billingContext: {
            trialing,
          },
        });

        if (discountResult.discountSummary.appliedCoupon) {
          processedItems = discountResult.enhancedLineItems;
        }
      }
    } catch (error) {
      // If discount processing fails, continue with original items
      console.warn('Failed to apply discounts in getCheckoutAmount:', error.message);
    }
  }

  // Use async reduce to support quote_id fallback
  const subtotal = await processedItems
    .reduce(
      async (accPromise, x) => {
        const acc = await accPromise;
        const price = x.upsell_price || x.price;

        const isRecurring = price?.type === 'recurring';
        // trialing/metered recurring items: no upfront charge, skip adding to subtotal
        const skipAccAdd = isRecurring && (trialing || price?.recurring?.usage_type === 'metered');

        // Priority 1: Use custom_amount if available
        if (x.custom_amount) {
          if (isRecurring) renew = renew.add(new BN(x.custom_amount));
          if (skipAccAdd) return acc;
          return acc.add(new BN(x.custom_amount));
        }

        // Priority 2: If has quote_id but no custom_amount, fetch from Quote table
        if ((x as any).quote_id) {
          try {
            const quote = await PriceQuote.findByPk((x as any).quote_id);
            if (quote) {
              logger.info('Using quoted_amount from quote_id for checkout amount calculation', {
                priceId: x.price.id,
                quoteId: (x as any).quote_id,
                quotedAmount: quote.quoted_amount,
              });
              if (isRecurring) renew = renew.add(new BN(quote.quoted_amount));
              if (skipAccAdd) return acc;
              return acc.add(new BN(quote.quoted_amount));
            }

            // Quote not found - log warning and fall through to price calculation
            logger.warn('Quote not found for quote_id in line_item', {
              priceId: x.price.id,
              quoteId: (x as any).quote_id,
            });
          } catch (error) {
            logger.error('Failed to fetch quote for quote_id', {
              priceId: x.price.id,
              quoteId: (x as any).quote_id,
              error: (error as Error).message,
            });
          }
        }

        // Priority 3: Calculate from price (fixed pricing or quote query failed)
        const unitPrice = getPriceUintAmountByCurrency(price, currencyId);

        const itemPrice = new BN(unitPrice).mul(new BN(x.quantity));
        if (price.custom_unit_amount) {
          if (unitPrice) {
            if (isRecurring) renew = renew.add(itemPrice);
            if (skipAccAdd) return acc;
            return acc.add(itemPrice);
          }
        }

        if (isRecurring) {
          renew = renew.add(itemPrice);
          if (skipAccAdd) return acc;
        }

        return acc.add(itemPrice);
      },
      Promise.resolve(new BN(0))
    )
    .then((bn) => bn.toString());

  // Calculate total discount amount from processed line items
  let totalDiscountAmount = new BN(0);
  processedItems.forEach((item) => {
    if ((item as any).discount_amounts?.length > 0) {
      (item as any).discount_amounts.forEach((discountAmount: any) => {
        totalDiscountAmount = totalDiscountAmount.add(new BN(discountAmount.amount || '0'));
      });
    }
  });

  // Ensure discount doesn't exceed subtotal
  const adjustedDiscount = totalDiscountAmount.gt(new BN(subtotal)) ? new BN(subtotal) : totalDiscountAmount;

  const finalTotal = new BN(subtotal).sub(adjustedDiscount);
  const adjustedTotal = finalTotal.lt(new BN('0')) ? '0' : finalTotal.toString();

  return {
    subtotal: subtotal || '0',
    total: adjustedTotal,
    renew: renew.toString() || '0',
    discount: adjustedDiscount.toString(),
    shipping: '0',
    tax: '0',
    processedItems, // Return processed items with discount information
  };
}

export function getRecurringPeriod(recurring: PriceRecurring) {
  const { interval } = recurring || {};
  if (!interval) {
    return 0;
  }
  const count = +recurring.interval_count || 1;
  const dayInMs = 24 * 60 * 60 * 1000;

  switch (interval) {
    case 'hour':
      return 60 * 60 * 1000;
    case 'day':
      return count * dayInMs;
    case 'week':
      return count * 7 * dayInMs;
    case 'month':
      return count * 30 * dayInMs;
    case 'year':
      return count * 365 * dayInMs;
    default:
      return 0;
  }
}

export function expandLineItems(
  items: any[],
  products: Product[],
  prices: Price[],
  creditCurrencies?: TPaymentCurrency[]
) {
  items.forEach((item) => {
    item.price = prices.find((x) => x.id === item.price_id);
    if (item.price) {
      item.price.product = products.find((x) => x.id === item.price.product_id);
      if (item.price.product?.type === 'credit' && item.price.metadata?.credit_config) {
        item.price.credit = {
          amount: Number(item.price.metadata.credit_config.credit_amount || 0) * Number(item.quantity),
          currency_id: item.price.metadata.credit_config.currency_id,
          currency: creditCurrencies?.find((x) => x.id === item.price.metadata.credit_config.currency_id),
        };
      }
    }
  });

  return items;
}

export function filterCurrencies(method: TPaymentMethodExpanded, hasSelected: (currency: TPaymentCurrency) => boolean) {
  method.payment_currencies = method.payment_currencies.filter((x) => hasSelected(x));
  return method;
}

export function getSupportedPaymentMethods(
  methods: TPaymentMethodExpanded[],
  hasSelected: (currency: TPaymentCurrency) => boolean
) {
  const filtered = cloneDeep(methods).map((x) => filterCurrencies(x, hasSelected));
  return filtered.filter((x) => x.payment_currencies.length);
}

export function getSupportedPaymentCurrencies(items: TLineItemExpanded[]) {
  const currencies = items.reduce((acc: string[], x: any) => {
    const price = x.upsell_price || x.price;
    if (!price) {
      return acc;
    }
    return acc.concat(getPriceCurrencyOptions(price).map((c: any) => c.currency_id));
  }, []);
  return Array.from(new Set(currencies));
}

export function isLineItemCurrencyAligned(list: TLineItemExpanded[], index: number) {
  const prices = list.map((x) => x.upsell_price || x.price);

  const current = getPriceCurrencyOptions(prices[index] as any)
    .map((x) => x.currency_id)
    .sort();

  for (let i = 0; i < index; i++) {
    const previous = getPriceCurrencyOptions(prices[i] as any)
      .map((x) => x.currency_id)
      .sort();
    if (isEqual(current, previous) === false) {
      return false;
    }
  }

  return true;
}

export function isLineItemRecurringAligned(list: TLineItemExpanded[], index: number) {
  const prices = list.map((x) => x.upsell_price || x.price);

  if (prices[index]?.type !== 'recurring') {
    return true;
  }

  const recurring = prices.slice(0, index).find((x) => x?.type === 'recurring')?.recurring;
  if (!recurring) {
    return true;
  }

  return prices.slice(0, index + 1).every((x: any) => {
    if (x.type !== 'recurring') {
      return true;
    }

    // If the interval and interval_count are different, the recurring is not aligned
    if (
      String(recurring?.interval) !== String(x.recurring?.interval) ||
      Number(recurring?.interval_count) !== Number(x.recurring?.interval_count)
    ) {
      return false;
    }

    return true;
  });
}

export function isLineItemAligned(list: TLineItemExpanded[], index: number) {
  const currency = isLineItemCurrencyAligned(list, index);
  const recurring = isLineItemRecurringAligned(list, index);
  return {
    currency,
    recurring,
    aligned: currency && recurring,
  };
}

// FIXME: upsell validate https://stripe.com/docs/payments/checkout/upsells
export function canUpsell(from: TPrice, to: TPrice) {
  if (from.id === to.id) {
    return false;
  }
  if (from.product_id !== to.product_id) {
    return false;
  }
  if (to.active === false) {
    return false;
  }
  if (to.type !== 'recurring') {
    return false;
  }

  // longer periods
  const fromPeriod = getRecurringPeriod(from.recurring as PriceRecurring);
  const toPeriod = getRecurringPeriod(to.recurring as PriceRecurring);
  if (!fromPeriod || !toPeriod) {
    return false;
  }
  if (fromPeriod >= toPeriod) {
    return false;
  }

  // and lower prices
  const fromPrice = new BN(from.unit_amount).div(new BN(fromPeriod));
  const toPrice = new BN(to.unit_amount).div(new BN(toPeriod));
  if (fromPrice.lt(toPrice)) {
    return false;
  }

  return true;
}

export async function getFastCheckoutAmount({
  items,
  mode,
  currencyId,
  trialing = false,
  minimumCycle = 1,
  discountConfig,
}: {
  items: TLineItemExpanded[];
  mode: string;
  currencyId: string;
  trialing?: boolean;
  minimumCycle?: number;
  discountConfig?: {
    promotionCodeId?: string;
    couponId?: string;
    customerId?: string;
  };
}) {
  if (!minimumCycle || minimumCycle < 1) {
    // eslint-disable-next-line no-param-reassign
    minimumCycle = 1;
  }

  const { total, renew } = await getCheckoutAmount(items, currencyId, trialing, discountConfig);

  if (mode === 'payment' || mode === 'auto-recharge-auth') {
    return total;
  }

  if (mode === 'setup') {
    return new BN(renew).mul(new BN(minimumCycle)).toString();
  }

  if (mode === 'subscription') {
    return new BN(total).add(new BN(renew).mul(new BN(trialing ? minimumCycle : minimumCycle - 1))).toString();
  }

  return '0';
}

export function getBillingThreshold(config: Record<string, any> = {}) {
  if (config?.billing_threshold_amount) {
    const threshold = +(config.billing_threshold_amount as string);
    if (threshold > 0) {
      return threshold;
    }
  }

  const threshold = +(process.env.PAYMENT_BILLING_THRESHOLD as string);
  if (threshold > 0) {
    return threshold;
  }

  return 0;
}

export function getMinStakeAmount(config: Record<string, any> = {}) {
  if (config?.min_stake_amount) {
    const threshold = +(config.min_stake_amount as string);
    if (threshold > 0) {
      return threshold;
    }
  }

  const threshold = +(process.env.PAYMENT_MIN_STAKE_AMOUNT as string);
  if (threshold > 0) {
    return threshold;
  }

  return 0;
}

export async function canPayWithDelegation(
  beneficiaries: PaymentBeneficiary[],
  paymentMethod?: PaymentMethod
): Promise<boolean> {
  if (beneficiaries.length === 0) {
    return true;
  }

  // Get all valid wallet addresses (current + migrated)
  let allWalletAddresses = [wallet.address];
  if (paymentMethod?.type === 'arcblock') {
    const client = paymentMethod.getOcapClient();
    const migratedFrom = await getMigratedFromList(client);
    allWalletAddresses = [wallet.address, ...migratedFrom];
  }

  return beneficiaries.every((x) => allWalletAddresses.includes(x.address));
}

export function createPaymentBeneficiaries(total: string, beneficiaries: PaymentBeneficiary[]) {
  if (beneficiaries.length) {
    const shares = beneficiaries.reduce((acc, x) => acc + parseInt(x.share, 10), 0);
    const result: PaymentBeneficiary[] = [];
    beneficiaries.forEach((x, i) => {
      let share = new BN(total).div(new BN(shares)).mul(new BN(x.share)).toString();
      if (i === beneficiaries.length - 1) {
        share = result.reduce((acc, d) => acc.sub(new BN(d.share)), new BN(total)).toString();
      }

      result.push({ ...x, share });
    });

    return result;
  }

  return [];
}

export function createPaymentOutput(
  total: string,
  contract: string,
  beneficiaries: PaymentBeneficiary[]
): TransactionInput[] {
  if (beneficiaries.length) {
    return beneficiaries.map((x) => ({
      owner: x.address,
      assets: [],
      tokens: [
        {
          address: contract,
          value: x.share,
        },
      ],
    }));
  }

  return [
    {
      owner: wallet.address,
      assets: [],
      tokens: [
        {
          address: contract,
          value: total,
        },
      ],
    },
  ];
}

export function isDonationCheckoutSession(checkoutSession: CheckoutSession): boolean {
  return (
    checkoutSession.submit_type === 'donate' ||
    checkoutSession.metadata?.type === 'donation' ||
    !!checkoutSession.metadata?.is_donation
  );
}

/**
 * Format subscription product name based on line items
 */
export function formatSubscriptionProduct(items: TLineItemExpanded[], maxLength = 3) {
  const names = items.map((x) => x.price?.product?.name || '').filter(Boolean);
  return names.length > maxLength ? `${names.slice(0, maxLength).join(', ')} ...` : names.join(', ');
}

/**
 * Slippage configuration for subscription authorization calculation
 */
export type SlippageOptions = {
  /** Slippage percentage (e.g., 0.5 for 0.5%, 5 for 5%). Used when minAcceptableRate is not provided. */
  percent?: number;
  /** Minimum acceptable exchange rate (USD per token). When provided, used for precise authorization calculation. */
  minAcceptableRate?: string;
  /** Token decimal places. Required when minAcceptableRate is provided. */
  currencyDecimal?: number;
};

const DEFAULT_SLIPPAGE_PERCENT = 0.5;
const USD_DECIMALS = 8;

/**
 * Setup subscription creation parameters
 * @param items - Line items for the subscription
 * @param currencyId - Payment currency ID
 * @param trialInDays - Trial period in days (optional)
 * @param trialEnd - Trial end timestamp (optional)
 * @param slippageOptions - Slippage configuration for dynamic pricing authorization
 */
export function getSubscriptionCreateSetup(
  items: TLineItemExpanded[],
  currencyId: string,
  trialInDays = 0,
  trialEnd = 0,
  slippageOptions?: SlippageOptions | number
) {
  // Normalize slippage options (support legacy number parameter for backward compatibility)
  const options: SlippageOptions =
    typeof slippageOptions === 'number'
      ? { percent: slippageOptions }
      : slippageOptions || { percent: DEFAULT_SLIPPAGE_PERCENT };

  let setup = new BN(0);
  let hasDynamicPricing = false;
  let totalBaseAmountBN = new BN(0); // Track total USD base amount for dynamic pricing items

  // Debug log to verify items have correct upsell_price
  logger.debug('getSubscriptionCreateSetup: items check', {
    itemCount: items.length,
    items: items.map((x) => ({
      price_id: x.price?.id,
      upsell_price_id: x.upsell_price?.id,
      has_upsell: !!x.upsell_price,
      selected_price_id: getSubscriptionItemPrice(x)?.id,
      price_base_amount: x.price?.base_amount,
      upsell_base_amount: x.upsell_price?.base_amount,
    })),
  });

  items.forEach((x) => {
    const price = getSubscriptionItemPrice(x);

    // Priority: use custom_amount if available (for dynamic pricing with pre-calculated amount)
    // This allows callers to pass in the actual quoted amount from Quote system
    let amount: BN;
    if ((x as any).custom_amount) {
      amount = new BN((x as any).custom_amount);
    } else {
      const unit = getPriceUintAmountByCurrency(price, currencyId);
      amount = new BN(unit).mul(new BN(x.quantity));
    }

    // Check if this price uses dynamic pricing
    if (price.pricing_type === 'dynamic') {
      hasDynamicPricing = true;
      // Accumulate base amount for dynamic pricing items (stored in 10^8 scale)
      if (price.base_amount) {
        const itemBaseAmount = fromTokenToUnit(price.base_amount, USD_DECIMALS);
        const contribution = itemBaseAmount.mul(new BN(x.quantity));
        totalBaseAmountBN = totalBaseAmountBN.add(contribution);
        logger.debug('getSubscriptionCreateSetup: processing dynamic pricing item', {
          priceId: price.id,
          baseAmount: price.base_amount,
          quantity: x.quantity,
          contributionUSD: fromUnitToToken(contribution.toString(), USD_DECIMALS),
        });
      }
    }

    if (price.type === 'recurring') {
      if (price.recurring?.usage_type === 'licensed') {
        setup = setup.add(amount);
      }
    }
    if (price.type === 'one_time') {
      setup = setup.add(amount);
    }
  });

  // Apply slippage protection for subscriptions with dynamic pricing
  if (hasDynamicPricing && setup.gt(new BN(0))) {
    const { percent, minAcceptableRate, currencyDecimal } = options;

    // Priority: minAcceptableRate > percent
    // When minAcceptableRate is provided, calculate: authorization = base_amount / min_acceptable_rate
    // This is more accurate as it uses the exact rate threshold configured by user
    if (
      minAcceptableRate &&
      Number(minAcceptableRate) > 0 &&
      currencyDecimal !== undefined &&
      totalBaseAmountBN.gt(new BN(0))
    ) {
      // Calculate authorization using min_acceptable_rate
      // Formula: authorization_token = ceil(base_amount_usd / min_acceptable_rate)
      const minRateBN = fromTokenToUnit(minAcceptableRate, USD_DECIMALS);
      if (minRateBN.gt(new BN(0))) {
        const numerator = totalBaseAmountBN.mul(new BN(10).pow(new BN(currencyDecimal)));
        // Ceiling division: ceil(a/b) = (a + b - 1) / b
        setup = numerator.add(minRateBN).sub(new BN(1)).div(minRateBN);
        logger.info('Applied subscription authorization with min_acceptable_rate', {
          hasDynamicPricing,
          authorizationAmountRaw: setup.toString(),
          authorizationAmountDisplay: fromUnitToToken(setup.toString(), currencyDecimal),
          minAcceptableRate,
          totalBaseAmountUSD: fromUnitToToken(totalBaseAmountBN.toString(), USD_DECIMALS),
          currencyDecimal,
        });
      }
    } else {
      // Fallback: Apply slippage percent multiplier
      // Authorization amount = base * (1 + percent/100)
      const normalizedSlippage = Number.isFinite(percent) && percent! >= 0 ? percent! : DEFAULT_SLIPPAGE_PERCENT;
      // Convert slippage percent to multiplier: 0.5% -> 1005/1000, 5% -> 1050/1000
      const SLIPPAGE_MULTIPLIER = new BN(Math.round(1000 + normalizedSlippage * 10));
      const SLIPPAGE_DIVISOR = new BN(1000);
      setup = setup.mul(SLIPPAGE_MULTIPLIER).div(SLIPPAGE_DIVISOR);
      logger.info('Applied subscription authorization slippage protection', {
        hasDynamicPricing,
        authorizationAmount: setup.toString(),
        slippageRate: `${normalizedSlippage}%`,
      });
    }
  }

  const now = dayjs().unix();
  const item = items.find((x) => getSubscriptionItemPrice(x)?.type === 'recurring');
  const recurring = (item ? getSubscriptionItemPrice(item)?.recurring : undefined) as PriceRecurring;
  const cycle = getRecurringPeriod(recurring);

  let trialStartAt = 0;
  let trialEndAt = 0;
  if (+trialEnd && trialEnd > now) {
    trialStartAt = now;
    trialEndAt = trialEnd;
  } else if (trialInDays) {
    trialStartAt = now;
    trialEndAt = dayjs().add(trialInDays, 'day').unix();
  }

  const periodStart = trialStartAt || now;
  const periodEnd = trialEndAt || dayjs().add(cycle, 'millisecond').unix();

  return {
    recurring,
    cycle: {
      duration: cycle,
      anchor: periodEnd,
    },
    trial: {
      start: trialStartAt,
      end: trialEndAt,
    },
    period: {
      start: periodStart,
      end: periodEnd,
    },
    amount: {
      setup: setup.toString(),
    },
  };
}

/**
 * Creates subscription groupings based on checkout session configuration
 * @param {Object} params - Parameters for subscription creation
 * @returns {Object} Created subscription information
 */
export async function createGroupSubscriptions({
  checkoutSession,
  lineItems,
  customer,
  paymentSettings,
  setupIntent,
  trialConfig = {},
}: {
  checkoutSession: CheckoutSession;
  lineItems: TLineItemExpanded[];
  customer: Customer;
  paymentSettings: { method: PaymentMethod; currency: PaymentCurrency };
  setupIntent: SetupIntent | null;
  trialConfig?: { trialInDays?: number; trialEnd?: number };
}) {
  const enableGrouping = checkoutSession.enable_subscription_grouping;

  // Filter recurring items only
  const recurringItems = getRecurringLineItems(lineItems);
  if (recurringItems.length === 0) {
    logger.info('No recurring items found for subscription', {
      session: checkoutSession.id,
    });
    return { subscriptions: [], subscriptionGroups: {}, primarySubscription: null };
  }

  try {
    // Process based on grouping mode
    if (!enableGrouping) {
      // Single subscription mode
      const result = await createSingleSubscription({
        checkoutSession,
        lineItems: recurringItems,
        customer,
        paymentSettings,
        setupIntent,
        trialConfig,
      });
      return result;
    }
    // Multiple subscriptions mode
    const result = await createMultipleSubscriptions({
      checkoutSession,
      lineItems,
      customer,
      paymentSettings,
      setupIntent,
      trialConfig,
    });
    return result;
  } catch (error) {
    logger.error('Failed to create subscriptions', {
      error: error.message,
      session: checkoutSession.id,
      stack: error.stack,
    });
    throw error;
  }
}

/**
 * Creates a single subscription for all items
 */
async function createSingleSubscription(params: {
  checkoutSession: CheckoutSession;
  lineItems: TLineItemExpanded[];
  customer: Customer;
  paymentSettings: { method: PaymentMethod; currency: PaymentCurrency };
  setupIntent: SetupIntent | null;
  trialConfig?: { trialInDays?: number; trialEnd?: number };
}) {
  const { checkoutSession, lineItems, customer, paymentSettings, setupIntent, trialConfig } = params;
  const existingSubscriptionId = checkoutSession.subscription_id;

  const subscription = await createOrUpdateSubscription({
    existingSubscriptionId,
    checkoutSession,
    lineItems,
    customer,
    paymentSettings,
    setupIntent,
    trialConfig,
  });

  logger.info('Single subscription processed', {
    session: checkoutSession.id,
    subscription: subscription?.id,
    isNew: !existingSubscriptionId,
  });

  return {
    subscriptions: subscription ? [subscription] : [],
    subscriptionGroups: {},
    primarySubscription: subscription,
  };
}

/**
 * Creates multiple subscriptions grouped by price ID
 */
async function createMultipleSubscriptions(params: {
  checkoutSession: CheckoutSession;
  lineItems: TLineItemExpanded[];
  customer: Customer;
  paymentSettings: { method: PaymentMethod; currency: PaymentCurrency };
  setupIntent: SetupIntent | null;
  trialConfig?: { trialInDays?: number; trialEnd?: number };
}) {
  const { checkoutSession, lineItems, customer, paymentSettings, setupIntent, trialConfig } = params;

  // Group items by price ID
  const priceGroups = groupLineItemsByPrice(lineItems);
  const existingGroups = checkoutSession.subscription_groups || {};

  const subscriptions: any[] = [];
  const subscriptionGroups: Record<string, string> = {};
  let primarySubscription: any = null;

  const noStake = !!checkoutSession.subscription_data?.no_stake;
  const billingThreshold = getBillingThreshold(checkoutSession.subscription_data);
  const minStakeAmount = getMinStakeAmount(checkoutSession.subscription_data);

  // Create a task for each price group
  const tasks = Object.entries(priceGroups).map(([priceId, items], index) => async () => {
    try {
      const existingSubscriptionId = existingGroups[priceId];
      const isPrimary = index === 0;
      const subscriptionMetadata = {
        price_id: priceId,
        checkout_session_id: checkoutSession.id,
        ...(isPrimary ? { is_primary_subscription: true } : {}),
      };

      const subscriptionItems = items;

      // Set different stake configurations for different subscriptions
      let billingThresholds = {};
      if (isPrimary) {
        billingThresholds = {
          amount_gte: billingThreshold,
          stake_gte: minStakeAmount,
          reset_billing_cycle_anchor: false,
          no_stake: noStake,
        };
      } else {
        // if not primary subscription, set stake to 0
        billingThresholds = {
          amount_gte: 0,
          stake_gte: 0,
          reset_billing_cycle_anchor: false,
          no_stake: true,
          references_primary_stake: true,
        };
      }

      const subscription = await createOrUpdateSubscription({
        existingSubscriptionId,
        checkoutSession,
        lineItems: subscriptionItems,
        customer,
        paymentSettings,
        setupIntent,
        trialConfig,
        metadata: subscriptionMetadata,
        billing_thresholds: billingThresholds as SubscriptionBillingThresholds,
      });

      if (!subscription) {
        return { success: false, priceId, error: 'Failed to create subscription' };
      }

      return { success: true, priceId, subscription, isPrimary };
    } catch (error) {
      logger.error('Failed to create subscription for price group', {
        priceId,
        error: error.message,
        session: checkoutSession.id,
        stack: error.stack,
      });
      return { success: false, priceId, error: error.message };
    }
  });

  const results = await pAll(tasks, { concurrency: 5 });

  results.forEach((result) => {
    if (result.success && result.subscription) {
      subscriptionGroups[result.priceId] = result.subscription.id;
      subscriptions.push(result.subscription);
      if (result.isPrimary) {
        primarySubscription = result.subscription;
      }
    }
  });

  return {
    subscriptions,
    subscriptionGroups,
    primarySubscription,
  };
}

/**
 * Core logic for creating or updating a single subscription
 */
async function createOrUpdateSubscription(params: {
  existingSubscriptionId?: string | null;
  checkoutSession: CheckoutSession;
  lineItems: TLineItemExpanded[];
  customer: Customer;
  paymentSettings: { method: PaymentMethod; currency: PaymentCurrency };
  setupIntent: SetupIntent | null;
  trialConfig?: { trialInDays?: number; trialEnd?: number };
  metadata?: Record<string, any>;
  billing_thresholds?: SubscriptionBillingThresholds;
}): Promise<any> {
  const {
    existingSubscriptionId,
    checkoutSession,
    lineItems,
    customer,
    paymentSettings,
    setupIntent,
    trialConfig = {},
    metadata = {},
    billing_thresholds: billingThresholds,
  } = params;

  const { trialInDays = 0, trialEnd = 0 } = trialConfig;
  const { method: paymentMethod, currency: paymentCurrency } = paymentSettings;
  const normalizePercent = (value: any) => {
    const normalized = typeof value === 'string' ? Number(value) : value;
    if (!Number.isFinite(normalized) || normalized < 0) {
      return DEFAULT_SLIPPAGE_PERCENT;
    }
    return normalized;
  };
  const buildSlippageConfig = () => {
    const fromMeta = metadata?.slippage;
    if (fromMeta && typeof fromMeta === 'object') {
      const mode: 'percent' | 'rate' = fromMeta.mode === 'rate' ? 'rate' : 'percent';
      const percent = normalizePercent(fromMeta.percent);
      const minRate = typeof fromMeta.min_acceptable_rate === 'string' ? fromMeta.min_acceptable_rate : undefined;
      const baseCurrency = typeof fromMeta.base_currency === 'string' ? fromMeta.base_currency : undefined;
      const updatedAtMs = Number.isFinite(Number(fromMeta.updated_at_ms)) ? Number(fromMeta.updated_at_ms) : undefined;
      return {
        mode,
        percent,
        // Always include min_acceptable_rate if available (for both percent and rate modes)
        ...(minRate ? { min_acceptable_rate: minRate } : {}),
        ...(baseCurrency ? { base_currency: baseCurrency } : {}),
        ...(updatedAtMs ? { updated_at_ms: updatedAtMs } : {}),
      };
    }
    const fromSessionMeta = normalizeSlippageConfigFromMetadata(checkoutSession.metadata, DEFAULT_SLIPPAGE_PERCENT);
    if (fromSessionMeta) {
      return fromSessionMeta;
    }
    const percent = normalizePercent((checkoutSession as any).slippage_percent);
    return { mode: 'percent' as const, percent };
  };
  const slippageConfig = buildSlippageConfig();

  try {
    const itemsSubscriptionData = mergeSubscriptionDataFromLineItems(lineItems);
    let subscription = null;
    // Build slippage options with min_acceptable_rate for precise authorization calculation
    const slippageOptions: SlippageOptions = {
      percent: slippageConfig.percent,
      minAcceptableRate: slippageConfig.min_acceptable_rate,
      currencyDecimal: paymentCurrency.decimal,
    };
    const setup = getSubscriptionCreateSetup(lineItems, paymentCurrency.id, trialInDays, trialEnd, slippageOptions);

    if (existingSubscriptionId) {
      subscription = await Subscription.findByPk(existingSubscriptionId);
    }

    if (subscription) {
      // Validate subscription status
      if (subscription.status !== 'incomplete') {
        throw new Error(`Subscription ${subscription.id} status not incomplete: ${subscription.status}`);
      }

      const billingCycleAnchor =
        itemsSubscriptionData?.billing_cycle_anchor ||
        checkoutSession.subscription_data?.billing_cycle_anchor ||
        setup.cycle.anchor;

      // Update existing subscription
      subscription = await subscription.update({
        currency_id: paymentCurrency.id,
        customer_id: customer.id,
        default_payment_method_id: paymentMethod.id,
        current_period_start: setup.period.start,
        current_period_end: setup.period.end,
        billing_cycle_anchor: billingCycleAnchor,
        trial_end: setup.trial.end,
        trial_start: setup.trial.start,
        pending_invoice_item_interval: setup.recurring,
        pending_setup_intent: setupIntent?.id,
        slippage_config: slippageConfig,
      });

      logger.info('Subscription updated', {
        session: checkoutSession.id,
        subscription: subscription.id,
      });

      // Rebuild subscription items
      await SubscriptionItem.destroy({ where: { subscription_id: subscription.id } });
      await createSubscriptionItems(subscription.id, lineItems, checkoutSession, itemsSubscriptionData);

      // Update invoice customer if needed
      const invoice = await Invoice.findByPk(subscription.latest_invoice_id);
      if (invoice && invoice.customer_id !== customer.id) {
        await invoice.update({ customer_id: customer.id });
      }
    } else {
      // Create new subscription
      const recoveredFromId =
        itemsSubscriptionData.recovered_from || checkoutSession.subscription_data?.recovered_from || '';
      const recoveredFrom = recoveredFromId ? await Subscription.findByPk(recoveredFromId) : null;

      const safeMetadata = {
        ...(checkoutSession.subscription_data?.notification_settings
          ? {
              notification_settings: checkoutSession.subscription_data.notification_settings,
            }
          : {}),
        ...omit(checkoutSession.metadata || {}, ['days_until_due', 'days_until_cancel', 'page_info']),
        ...(itemsSubscriptionData.metadata || {}),
        ...metadata,
      };

      const subscriptionData = {
        ...checkoutSession.subscription_data,
        ...itemsSubscriptionData,
      };

      const subscriptionBillingThresholds = billingThresholds || {
        amount_gte: getBillingThreshold(subscriptionData),
        stake_gte: getMinStakeAmount(subscriptionData),
        reset_billing_cycle_anchor: false,
        no_stake: subscriptionData.no_stake,
      };

      subscription = await Subscription.create({
        livemode: !!checkoutSession.livemode,
        currency_id: paymentCurrency.id,
        customer_id: customer.id,
        status: 'incomplete',
        current_period_start: setup.period.start,
        current_period_end: setup.period.end,
        // FIXME: billing_cycle_anchor implementation is not aligned with stripe
        billing_cycle_anchor: subscriptionData.billing_cycle_anchor || setup.cycle.anchor,
        start_date: dayjs().unix(),
        trial_end: setup.trial.end,
        trial_start: setup.trial.start,
        trial_settings: {
          end_behavior: {
            missing_payment_method: 'create_invoice',
          },
        },
        billing_thresholds: subscriptionBillingThresholds,
        pending_invoice_item_interval: setup.recurring,
        pending_setup_intent: setupIntent?.id,
        default_payment_method_id: paymentMethod.id,
        cancel_at_period_end: false,
        collection_method: 'charge_automatically',
        slippage_config: slippageConfig,
        description: subscriptionData.description || formatSubscriptionProduct(lineItems),
        proration_behavior: subscriptionData.proration_behavior || 'none',
        payment_behavior: 'default_incomplete',
        days_until_due: subscriptionData.days_until_due,
        days_until_cancel: subscriptionData.days_until_cancel,
        recovered_from: recoveredFrom?.id,
        metadata: safeMetadata,
        service_actions: subscriptionData.service_actions || [],
      });

      logger.info('Subscription created', {
        session: checkoutSession.id,
        subscription: subscription.id,
      });

      // Create subscription items with item-specific metadata
      await createSubscriptionItems(subscription.id, lineItems, checkoutSession, itemsSubscriptionData);
    }

    return subscription;
  } catch (error) {
    logger.error('Failed in createOrUpdateSubscription', {
      existingSubscriptionId,
      error: error.message,
      stack: error.stack,
      session: checkoutSession.id,
    });
    throw error;
  }
}

/**
 * Creates subscription items for a subscription
 */
async function createSubscriptionItems(
  subscriptionId: string,
  lineItems: TLineItemExpanded[],
  checkoutSession: CheckoutSession,
  itemsSubscriptionData: Record<string, any>
) {
  try {
    const metadata = {
      ...(itemsSubscriptionData?.metadata || {}),
      ...(checkoutSession.metadata || {}),
    };

    const subItems = await Promise.all(
      lineItems.map((item) =>
        SubscriptionItem.create({
          livemode: !!checkoutSession.livemode,
          subscription_id: subscriptionId,
          price_id: item.upsell_price_id || item.price_id,
          quantity: item.quantity,
          metadata,
        })
      )
    );

    logger.info('Subscription items created', {
      session: checkoutSession.id,
      subscription: subscriptionId,
      items: subItems.map((x) => x.id),
    });

    return subItems;
  } catch (error) {
    logger.error('Failed to create subscription items', {
      subscriptionId,
      error: error.message,
      stack: error.stack,
      session: checkoutSession.id,
    });
    throw error;
  }
}

/**
 * Groups line items by price ID
 */
function groupLineItemsByPrice(lineItems: TLineItemExpanded[]): Record<string, TLineItemExpanded[]> {
  const groups: Record<string, TLineItemExpanded[]> = {};
  const recurringLineItems = getRecurringLineItems(lineItems);

  recurringLineItems.forEach((item) => {
    const priceId = item.upsell_price_id || item.price_id;
    if (!groups[priceId]) {
      groups[priceId] = [];
    }
    groups[priceId].push(item);
  });

  return groups;
}

export function getCheckoutSessionSubscriptionIds(checkoutSession: CheckoutSession) {
  const enableGrouping = checkoutSession.enable_subscription_grouping;
  if (!enableGrouping && checkoutSession.subscription_id) {
    return [checkoutSession.subscription_id];
  }
  return Object.values(checkoutSession.subscription_groups || {}).filter(Boolean);
}

export function getOneTimeLineItems(lineItems: TLineItemExpanded[]) {
  return lineItems.filter((item) => item.price?.type === 'one_time');
}

export function getRecurringLineItems(lineItems: TLineItemExpanded[]) {
  return lineItems.filter((item) => item.price?.type === 'recurring');
}

export function mergeSubscriptionDataFromLineItems(lineItems: TLineItemExpanded[]): Record<string, any> {
  if (lineItems.length === 0) {
    return {};
  }
  const mergedData: Record<string, any> = {};
  const mergedMetadata: Record<string, any> = {};
  lineItems.forEach((item: TLineItemExpanded) => {
    const subData = (item.subscription_data || {}) as any;
    if (subData.metadata) {
      Object.assign(mergedMetadata, subData.metadata);
    }
    Object.entries(subData).forEach(([key, value]) => {
      if (key !== 'metadata') {
        mergedData[key] = value;
      }
    });
  });

  if (Object.keys(mergedMetadata).length > 0) {
    mergedData.metadata = mergedMetadata;
  }

  return mergedData;
}

/**
 * Get line items related to a specific subscription
 * @param subscription The subscription object
 * @param lineItems All line items from checkout session
 * @param primarySubscription Optional primary subscription to check one-time items
 * @returns Line items associated with the subscription
 */
export async function getSubscriptionLineItems(
  subscription: Subscription,
  lineItems: TLineItemExpanded[],
  primarySubscription?: Subscription
): Promise<TLineItemExpanded[]> {
  const subscriptionItems = await SubscriptionItem.findAll({
    where: { subscription_id: subscription.id },
  });

  let subItems = lineItems.filter((x) =>
    subscriptionItems.some((y) => y.price_id === x.price_id || y.price_id === x.upsell_price_id)
  );

  // Add one-time items if this is the primary subscription
  if (primarySubscription && subscription.id === primarySubscription.id) {
    subItems = [...subItems, ...getOneTimeLineItems(lineItems)];
  }

  return subItems;
}

// Re-exported from credit-utils.ts to maintain backward compatibility
export { isCreditMetered, isCreditMeteredLineItems } from './credit-utils';

export function validateStripePaymentAmounts(amount: string, currency: PaymentCurrency) {
  const minAmountInUnits = fromTokenToUnit(0.5, currency.decimal);
  return new BN(amount).gte(new BN(minAmountInUnits)); // 0.5 USD
}

/**
 * Validates payment amounts meet minimum requirements
 * @param lineItems Line items to validate
 * @param currencyId Currency ID for validation
 * @param mode Checkout mode
 * @returns Validation result with error message if any
 */
export async function validatePaymentAmounts(
  lineItems: TLineItemExpanded[],
  currency: PaymentCurrency,
  checkoutSession: CheckoutSession
): Promise<{ valid: boolean; error?: string }> {
  const enableGrouping = checkoutSession.enable_subscription_grouping;
  const oneTimeItems = getOneTimeLineItems(lineItems);
  const recurringItems = getRecurringLineItems(lineItems);

  // Check for mixed pricing types (fixed + dynamic)
  const hasDynamicPricing = lineItems.some((item) => {
    const price = item.upsell_price || item.price;
    return (price as any)?.pricing_type === 'dynamic';
  });

  const hasFixedPricing = lineItems.some((item) => {
    const price = item.upsell_price || item.price;
    return !(price as any)?.pricing_type || (price as any)?.pricing_type === 'fixed';
  });

  if (hasDynamicPricing && hasFixedPricing) {
    return {
      valid: false,
      error: 'Cannot mix fixed pricing and dynamic pricing in the same checkout session',
    };
  }

  const minAmountInUnits = fromTokenToUnit(0.5, currency.decimal);

  // Case 1: All one-time items - validate total payment amount
  if (recurringItems.length === 0 && oneTimeItems.length > 0) {
    const { total } = await getCheckoutAmount(lineItems, currency.id);
    if (new BN(total).lt(new BN(minAmountInUnits))) {
      return {
        valid: false,
        error: 'Total payment amount must be greater or equal to 0.5 USD',
      };
    }
    return { valid: true };
  }

  // Case 2: Subscriptions - validate non-metered recurring items only.
  // Metered items are usage-based, their invoice amount depends on actual usage,
  // so checking unit_price against the minimum is meaningless.
  if (recurringItems.length > 0) {
    if (enableGrouping) {
      // Each subscription group is billed independently
      const priceGroups = groupLineItemsByPrice(lineItems);

      for (const [priceId, items] of Object.entries(priceGroups)) {
        // Skip metered groups — invoice amount depends on usage, not fixed price
        const price = items[0]?.upsell_price || items[0]?.price;
        // eslint-disable-next-line no-continue
        if (price?.recurring?.usage_type === 'metered') continue;

        let totalUnitPrice = new BN(0);
        items.forEach((item) => {
          const p = item.upsell_price || item.price;
          const unitPrice = getPriceUintAmountByCurrency(p, currency.id);
          totalUnitPrice = totalUnitPrice.add(new BN(unitPrice).mul(new BN(item.quantity)));
        });

        if (totalUnitPrice.lt(new BN(minAmountInUnits))) {
          const productName = items[0]?.price?.product?.name || `Product ${priceId}`;
          return {
            valid: false,
            error: `product "${productName}" unit price must be greater or equal to 0.5 USD`,
          };
        }
      }
    } else {
      // Non-grouping: all recurring items share one invoice
      const nonMeteredItems = recurringItems.filter((item) => {
        const price = item.upsell_price || item.price;
        return price?.recurring?.usage_type !== 'metered';
      });

      // If all recurring items are metered, skip validation
      if (nonMeteredItems.length > 0) {
        const { renew } = await getCheckoutAmount(nonMeteredItems, currency.id);
        if (new BN(renew).lt(new BN(minAmountInUnits))) {
          return {
            valid: false,
            error: 'Total subscription amount must be greater or equal to 0.5 USD',
          };
        }
      }
    }
  }

  return { valid: true };
}

/**
 * Process discount application for checkout session data
 * Validates coupon existence and basic validity, calculates discount amounts
 * Customer-specific validations are done later during checkout submission
 */
export async function processCheckoutSessionDiscounts(
  checkoutData: {
    currency_id: string;
    amount_total: string;
    line_items?: any[];
  },
  discounts: Array<{ coupon: string }>
): Promise<
  Array<{
    coupon: string;
    verification_method: string;
  }>
> {
  if (!discounts || !Array.isArray(discounts) || discounts.length === 0) {
    return [];
  }

  // Get currency for discount calculations
  const currency = await PaymentCurrency.findByPk(checkoutData.currency_id);
  if (!currency) {
    throw new Error('Currency not found');
  }

  const processedDiscounts: Array<{
    coupon: string;
    verification_method: string;
  }> = [];

  const expandedItems = await Price.expand(checkoutData.line_items || [], { product: true, upsell: true });

  // Process discounts concurrently for better performance
  const discountProcessingTasks = discounts.map((discountSpec, index) => async () => {
    if (!discountSpec.coupon) {
      throw new Error(`Coupon ID is required for discount at index ${index}`);
    }

    const coupon = await Coupon.findByPk(discountSpec.coupon);
    if (!coupon) {
      throw new Error(`Coupon '${discountSpec.coupon}' not found`);
    }

    // Validate coupon basic validity
    const couponValidation = validCoupon(coupon, expandedItems as TLineItemExpanded[]);
    if (!couponValidation.valid) {
      throw new Error(`Coupon '${discountSpec.coupon}' is not valid: ${couponValidation.reason}`);
    }
    return {
      coupon: coupon.id,
      verification_method: 'direct',
    };
  });

  const processedDiscountsArray = await pAll(discountProcessingTasks, { concurrency: 5 });
  processedDiscounts.push(...processedDiscountsArray);

  return processedDiscounts;
}

/**
 * Recalculate checkout session amounts with applied discounts
 */
export async function getCheckoutSessionAmounts(checkoutSession: CheckoutSession) {
  const { discounts } = checkoutSession;

  // Ensure we always have safe default values
  const safeSubtotal = checkoutSession.amount_subtotal || '0';
  const safeTotal = checkoutSession.amount_total || '0';
  const safeTotalDetails = checkoutSession.total_details || {
    amount_discount: '0',
    amount_shipping: '0',
    amount_tax: '0',
  };

  // If no discounts, return current amounts with safe defaults
  if (!discounts || discounts.length === 0) {
    return {
      amount_subtotal: safeSubtotal,
      amount_total: safeTotal,
      total_details: {
        amount_discount: safeTotalDetails.amount_discount || '0',
        amount_shipping: safeTotalDetails.amount_shipping || '0',
        amount_tax: safeTotalDetails.amount_tax || '0',
      },
    };
  }

  const originalTotal = safeTotal;
  let totalDiscountAmount = new BN('0');

  // Calculate discount for each coupon
  const discountPromises = discounts.map(async (discount) => {
    if (!discount.coupon) {
      return new BN('0');
    }

    const coupon = await Coupon.findByPk(discount.coupon);

    if (!coupon || !coupon.valid) {
      return new BN('0');
    }

    let discountAmount = new BN('0');
    const baseAmount = new BN(originalTotal);

    if (coupon.percent_off > 0) {
      // Percentage discount
      discountAmount = baseAmount.mul(new BN(coupon.percent_off)).div(new BN(100));
    } else if (coupon.amount_off && new BN(coupon.amount_off).gt(new BN('0'))) {
      // Fixed amount discount
      discountAmount = new BN(coupon.amount_off);
    }

    // Ensure discount doesn't exceed original amount
    if (discountAmount.gt(baseAmount)) {
      discountAmount = baseAmount;
    }

    return discountAmount;
  });

  const discountAmounts = await Promise.all(discountPromises);
  totalDiscountAmount = discountAmounts.reduce((sum: BN, amount: BN) => sum.add(amount), new BN('0'));

  // Ensure total discount doesn't exceed original amount
  if (totalDiscountAmount.gt(new BN(originalTotal))) {
    totalDiscountAmount = new BN(originalTotal);
  }

  const finalTotal = new BN(originalTotal).sub(totalDiscountAmount);
  // Ensure total doesn't go below 0
  const adjustedTotal = finalTotal.lt(new BN('0')) ? '0' : finalTotal.toString();

  return {
    amount_subtotal: safeSubtotal,
    amount_total: adjustedTotal,
    total_details: {
      amount_discount: totalDiscountAmount.toString(),
      amount_shipping: safeTotalDetails.amount_shipping || '0',
      amount_tax: safeTotalDetails.amount_tax || '0',
    },
    discounts,
  };
}

const QUOTE_LOCK_DURATION_SECONDS = 180;
function calculateTotalBaseAmount(baseAmount: string, quantity: number): string {
  const baseAmountBN = fromTokenToUnit(trimDecimals(baseAmount, USD_DECIMALS), USD_DECIMALS);
  const quantityBN = new BN(quantity);
  const totalBaseAmountBN = baseAmountBN.mul(quantityBN);
  return fromUnitToToken(totalBaseAmountBN.toString(), USD_DECIMALS);
}

/**
 * Enrich checkout session with dynamic pricing quotes
 * For each dynamic price, create or reuse a quote and attach quote info to line items
 *
 * @param options.skipGeneration - If true, only fetch existing quotes via quote_id, don't generate new ones
 */
export async function enrichCheckoutSessionWithQuotes(
  checkoutSession: CheckoutSession,
  lineItems: TLineItemExpanded[],
  currencyId: string,
  options: { skipGeneration?: boolean; forceRefresh?: boolean } = {}
): Promise<{
  lineItems: TLineItemExpanded[];
  quotes: Record<
    string,
    {
      quote_id: string;
      expires_at: number;
      quoted_amount: string;
      exchange_rate?: string;
      rate_provider_name?: string;
      rate_provider_id?: string;
    }
  >;
  rateUnavailable?: boolean;
  rateError?: string;
  regenerated?: boolean;
  refreshed?: boolean;
}> {
  const { skipGeneration = false, forceRefresh = false } = options;
  const quoteService = getQuoteService();

  // Filter out items with missing price (data integrity issue)
  const invalidItems = lineItems.filter((item) => !item.price);
  if (invalidItems.length > 0) {
    logger.error('Line items with missing price detected - possible data integrity issue', {
      sessionId: checkoutSession.id,
      invalidPriceIds: invalidItems.map((item) => item.price_id),
      totalItems: lineItems.length,
      invalidCount: invalidItems.length,
    });
  }
  // Only process items with valid price
  const validLineItems = lineItems.filter((item) => item.price);
  if (validLineItems.length === 0) {
    logger.warn('No valid line items with price found', { sessionId: checkoutSession.id });
    return { lineItems, quotes: {}, refreshed: false };
  }

  const quotes: Record<
    string,
    {
      quote_id: string;
      expires_at: number;
      quoted_amount: string;
      exchange_rate: string;
      rate_provider_name: string;
      rate_provider_id: string;
    }
  > = {};

  // First, check if line_items already have quote_id references
  // If so, fetch quote data from PriceQuote table and check if expired
  const lineItemsWithQuoteIds = validLineItems.filter((item) => (item as any).quote_id);
  const now = Math.floor(Date.now() / 1000);
  const quoteLockedAt = checkoutSession.metadata?.quote_locked_at;
  const lockStart =
    typeof quoteLockedAt === 'number'
      ? quoteLockedAt
      : typeof quoteLockedAt === 'string'
        ? Number(quoteLockedAt)
        : null;
  const lockActive = Number.isFinite(lockStart) && now - (lockStart as number) < QUOTE_LOCK_DURATION_SECONDS;
  const slippagePercentValue =
    (checkoutSession.metadata as any)?.slippage?.percent ?? (checkoutSession as any).slippage_percent;
  const slippagePercentValueNumber = Number(slippagePercentValue);
  const slippagePercent = Number.isFinite(slippagePercentValueNumber) ? slippagePercentValueNumber : 0.5;
  const quotesById = new Map<string, PriceQuote>();
  const refreshQuoteIds = new Set<string>();
  let hasExpiredQuotes = false;
  let hasStaleQuotes = false;
  let hasMissingQuotes = false;
  let regenerated = false;
  let createdNewQuotes = false;
  const applyQuoteToItem = (item: TLineItemExpanded, quote: PriceQuote) => {
    (item as any).custom_amount = quote.quoted_amount;
    (item as any).quoted_amount = quote.quoted_amount;
    (item as any).quote_id = quote.id;
    (item as any).exchange_rate = quote.exchange_rate;
    (item as any).rate_provider_name = quote.rate_provider_name;
    (item as any).rate_provider_id = quote.rate_provider_id;
    (item as any).rate_timestamp_ms = quote.rate_timestamp_ms;
    (item as any).expires_at = quote.expires_at;
    // Store the currency ID this quote was created for - essential for frontend validation
    (item as any).quote_currency_id = quote.target_currency_id;

    const quoteKey = `${item.price?.id || item.price_id}_${currencyId}`;
    quotes[quoteKey] = {
      quote_id: quote.id,
      expires_at: quote.expires_at,
      quoted_amount: quote.quoted_amount,
      exchange_rate: quote.exchange_rate,
      rate_provider_name: quote.rate_provider_name,
      rate_provider_id: quote.rate_provider_id,
    };
  };

  if (lineItemsWithQuoteIds.length > 0) {
    const quoteIds = Array.from(new Set(lineItemsWithQuoteIds.map((item) => (item as any).quote_id).filter(Boolean)));
    const existingQuotes = await PriceQuote.findAll({
      where: { id: quoteIds },
    });

    existingQuotes.forEach((quote) => {
      quotesById.set(quote.id, quote);
    });
    if (existingQuotes.length !== quoteIds.length) {
      hasMissingQuotes = true;
      logger.warn('Some quote references are missing from database', {
        sessionId: checkoutSession.id,
        totalQuoteIds: quoteIds.length,
        foundQuotes: existingQuotes.length,
      });
    }

    lineItemsWithQuoteIds.forEach((item) => {
      const quoteId = (item as any).quote_id;
      const quote = quotesById.get(quoteId);
      if (!quote) {
        if (!skipGeneration) {
          refreshQuoteIds.add(quoteId);
        }
        return;
      }

      const isPaid = quote.status === 'paid';
      const isUsed = quote.status === 'used';
      if (isPaid || (isUsed && (lockActive || skipGeneration))) {
        applyQuoteToItem(item, quote);
        return;
      }
      if (isUsed && !lockActive && !skipGeneration) {
        hasExpiredQuotes = true;
        refreshQuoteIds.add(quoteId);
        logger.info('Detected used quote with expired lock, will refresh', {
          sessionId: checkoutSession.id,
          quoteId,
          status: quote.status,
        });
        return;
      }

      if (forceRefresh && !skipGeneration) {
        if (!lockActive) {
          refreshQuoteIds.add(quoteId);
          logger.info('Force refresh quote for checkout session', {
            sessionId: checkoutSession.id,
            quoteId,
          });
        } else {
          logger.info('Skip force refresh due to active quote lock', {
            sessionId: checkoutSession.id,
            quoteId,
          });
        }
        return;
      }

      const price = (item.upsell_price || item.price) as any;
      if (
        !skipGeneration &&
        !lockActive &&
        quote.status !== 'paid' &&
        price?.pricing_type === 'dynamic' &&
        price?.base_amount
      ) {
        const expectedBaseAmount = calculateTotalBaseAmount(price.base_amount, Number(item.quantity || 0));
        const expectedTrimmed = trimDecimals(expectedBaseAmount, USD_DECIMALS);
        const quoteTrimmed = trimDecimals(quote.base_amount || '0', USD_DECIMALS);
        if (quoteTrimmed !== expectedTrimmed) {
          hasStaleQuotes = true;
          refreshQuoteIds.add(quoteId);
          logger.info('Detected quote base_amount mismatch, will refresh quote', {
            sessionId: checkoutSession.id,
            quoteId,
            priceId: item.price_id || item.price?.id,
            expectedBaseAmount: expectedTrimmed,
            quoteBaseAmount: quoteTrimmed,
            quantity: item.quantity,
          });
          return;
        }
      }

      const isExpired = quote.status !== 'paid' && (quote.expires_at < now || quote.status === 'expired');
      if (!skipGeneration && !lockActive && isExpired) {
        hasExpiredQuotes = true;
        refreshQuoteIds.add(quoteId);
        logger.info('Detected expired quote', {
          sessionId: checkoutSession.id,
          expiredQuoteId: quote.id,
          expiresAt: new Date(quote.expires_at * 1000).toISOString(),
          now: new Date(now * 1000).toISOString(),
        });
        return;
      }

      const isInactive = quote.status !== 'active' && quote.status !== 'paid';
      if (!skipGeneration && !lockActive && isInactive) {
        hasExpiredQuotes = true;
        refreshQuoteIds.add(quoteId);
        logger.info('Detected inactive quote, will refresh', {
          sessionId: checkoutSession.id,
          quoteId: quote.id,
          status: quote.status,
        });
        return;
      }

      // Use existing quotes (either not expired, or skipGeneration)
      applyQuoteToItem(item, quote);
    });

    logger.info('Processed existing quotes from database', {
      sessionId: checkoutSession.id,
      totalQuoteIds: lineItemsWithQuoteIds.length,
      foundQuotes: quotesById.size,
      hasExpiredQuotes,
      hasStaleQuotes,
      hasMissingQuotes,
      skipGeneration,
      refreshQuotes: refreshQuoteIds.size,
    });

    if (skipGeneration || refreshQuoteIds.size === 0) {
      return { lineItems, quotes, refreshed: false };
    }
  }

  // Find all dynamic pricing items that need new quotes or refresh
  const dynamicItems = validLineItems.filter((item) => {
    const price = getSubscriptionItemPrice(item);
    return (
      price?.pricing_type === 'dynamic' && ((item as any).quote_id ? refreshQuoteIds.has((item as any).quote_id) : true)
    );
  });

  // If no dynamic items need new quotes, return early
  if (dynamicItems.length === 0) {
    return { lineItems, quotes, refreshed: false };
  }

  // If skipGeneration is enabled, don't generate new quotes for items without quote_id
  // This is used for submitted/completed checkout sessions where we only want to use existing quotes
  if (skipGeneration) {
    logger.info('Skipping quote generation for dynamic items (skipGeneration enabled)', {
      sessionId: checkoutSession.id,
      dynamicItemsWithoutQuotes: dynamicItems.length,
    });
    return { lineItems, quotes, refreshed: false };
  }

  // Fetch exchange rate once for all dynamic items (they all use the same target currency)
  const currency = (await PaymentCurrency.findByPk(currencyId, {
    include: [{ model: PaymentMethod, as: 'payment_method' }],
  })) as PaymentCurrency & { payment_method: PaymentMethod };
  if (!currency) {
    throw new Error(`Currency ${currencyId} not found`);
  }

  if (currency.payment_method?.type === 'stripe') {
    return { lineItems, quotes, refreshed: false };
  }

  const exchangeRateService = getExchangeRateService();

  // Try to get exchange rate, but don't block checkout if unavailable
  let rateResult;
  try {
    // For ArcBlock payment method, always use ABT for exchange rate
    // For other methods, use the currency's symbol
    const rateSymbol = getExchangeRateSymbol(currency.symbol, currency.payment_method?.type as ChainType);
    rateResult = await exchangeRateService.getRate(rateSymbol);
  } catch (error: any) {
    logger.warn('Exchange rate unavailable for checkout session', {
      checkoutSessionId: checkoutSession.id,
      currencyId,
      currencySymbol: currency.symbol,
      error: error.message,
    });

    // Return without quotes, frontend will show warning
    return {
      lineItems,
      quotes: {},
      rateUnavailable: true,
      rateError: error.message || 'Exchange rate service unavailable',
      refreshed: false,
    };
  }

  logger.info('Fetched exchange rate for batch quote creation', {
    currencyId,
    currencySymbol: currency.symbol,
    rate: rateResult.rate,
    provider: rateResult.provider_name,
    dynamicItemsCount: dynamicItems.length,
  });

  // Create or refresh quotes for dynamic items using the same exchange rate
  const quoteResults = await Promise.all(
    dynamicItems.map(async (item) => {
      // Use upsell_price if available, otherwise use price
      const price = getSubscriptionItemPrice(item);
      const { quantity } = item;

      try {
        const quoteId = (item as any).quote_id;
        let quoteResponse: QuoteResponse;

        const existingQuote = quoteId ? quotesById.get(quoteId) : null;
        const shouldCreateNewQuote = !existingQuote || (existingQuote.status === 'used' && !lockActive);
        if (!shouldCreateNewQuote && quoteId && existingQuote) {
          quoteResponse = await quoteService.refreshQuoteWithRate({
            quote_id: quoteId,
            price_id: price?.id,
            session_id: checkoutSession.id,
            target_currency_id: currencyId,
            quantity,
            rateResult,
            slippage_percent: slippagePercent,
          });
        } else {
          quoteResponse = await quoteService.createQuoteWithRate({
            price_id: price?.id,
            session_id: checkoutSession.id,
            target_currency_id: currencyId,
            quantity,
            rateResult,
            slippage_percent: slippagePercent,
          });
          createdNewQuotes = true;
        }

        return {
          item,
          quoteResponse,
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        logger.error('Failed to create quote for dynamic price', {
          priceId: price.id,
          checkoutSessionId: checkoutSession.id,
          currencyId,
          errorMessage,
          errorName: (error as any)?.name,
        });
        throw error;
      }
    })
  );

  // Build quote map for quick lookup
  const quoteByPriceId = new Map<string, (typeof quoteResults)[0]>();
  quoteResults.forEach((result) => {
    const priceId = result.item.price?.id || result.item.price_id;
    if (priceId) {
      quoteByPriceId.set(priceId, result);
    }
  });

  // Extract quote info helper
  const extractQuoteInfo = (quoteResponse: QuoteResponse) => ({
    quote_id: quoteResponse.quote.id,
    expires_at: quoteResponse.expires_at,
    quoted_amount: quoteResponse.computed_unit_amount,
    exchange_rate: quoteResponse.quote.exchange_rate,
    rate_provider_name: quoteResponse.quote.rate_provider_name,
    rate_provider_id: quoteResponse.quote.rate_provider_id,
    rate_timestamp_ms: quoteResponse.quote.rate_timestamp_ms,
  });

  // Build quotes object
  quoteResults.forEach((result) => {
    const priceId = result.item.price?.id || result.item.price_id;
    if (priceId) {
      const quoteKey = `${priceId}_${currencyId}`;
      quotes[quoteKey] = extractQuoteInfo(result.quoteResponse);
    }
  });

  regenerated = createdNewQuotes;
  const refreshed = refreshQuoteIds.size > 0 || createdNewQuotes;

  // Enrich line items with quote information
  const enrichedLineItems = lineItems.map((item) => {
    // Skip items with missing price
    if (!item.price) {
      return item;
    }
    const quoteInfo = quoteByPriceId.get(item.price.id);
    if (!quoteInfo) {
      return item;
    }

    const quoteData = extractQuoteInfo(quoteInfo.quoteResponse);
    return {
      ...item,
      ...quoteData,
      custom_amount: quoteInfo.quoteResponse.computed_unit_amount,
    };
  });

  return {
    lineItems: enrichedLineItems,
    quotes,
    regenerated,
    refreshed,
  };
}
