import { Router } from 'express';
import Joi from 'joi';
import { BN, fromTokenToUnit } from '@ocap/util';

import { literal, OrderItem, fn, col, Op } from 'sequelize';
import pick from 'lodash/pick';
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
import logger from '../libs/logger';
import { authenticate } from '../libs/security';
import {
  AutoRechargeConfig,
  ChainType,
  CreditGrant,
  Customer,
  Invoice,
  MeterEvent,
  PaymentCurrency,
  PaymentMethod,
  Price,
  Product,
  Subscription,
} from '../store/models';
import { createCreditGrant, getCreditGrantStats } from '../libs/credit-grant';
import { expireGrant } from '../queues/credit-grant';
import { getMeterPriceIdsFromSubscription } from '../libs/subscription';
import { blocklet } from '../libs/auth';
import { formatMetadata } from '../libs/util';
import { getPriceUintAmountByCurrency } from '../libs/price';
import { checkTokenBalance } from '../libs/payment';
import { getExchangeRateService } from '../libs/exchange-rate/service';
import { getExchangeRateSymbol, hasTokenAddress } from '../libs/exchange-rate/token-address-mapping';
import { isRateBelowMinAcceptableRate } from '../libs/slippage';
import { trimDecimals } from '../libs/math-utils';
import { systemMaxPendingAmount } from '../libs/env';

const router = Router();
const auth = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'] });
const authMine = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'], mine: true });
const authPortal = authenticate<CreditGrant>({
  component: true,
  roles: ['owner', 'admin'],
  record: {
    // @ts-ignore
    model: CreditGrant,
    field: 'customer_id',
  },
});
const creditGrantSchema = Joi.object({
  amount: Joi.number().required(),
  currency_id: Joi.string().max(15).optional(),
  customer_id: Joi.string().max(45).required(),
  name: Joi.string().max(255).optional(),
  category: Joi.string().valid('paid', 'promotional').required(),
  priority: Joi.number().integer().min(0).max(100).default(50),
  effective_at: Joi.number().integer().optional(),
  expires_at: Joi.number().integer().optional(),
  metadata: MetadataSchema,
  applicability_config: Joi.object({
    scope: Joi.object({
      price_type: Joi.string().valid('metered').optional(),
      prices: Joi.array().items(Joi.string()).optional(),
    }).optional(),
  }).optional(),
});

const listSchema = createListParamSchema<{
  customer_id?: string;
  subscription_id?: string;
  currency_id?: string;
  status?: string;
  livemode?: boolean;
  q?: string;
}>({
  customer_id: Joi.string().optional(),
  subscription_id: Joi.string().optional(),
  currency_id: Joi.string().optional(),
  status: Joi.string().optional(),
  livemode: Joi.boolean().optional(),
  q: Joi.string().optional(),
});

async function expandScopePrices(creditGrant: CreditGrant) {
  const scope = creditGrant.applicability_config?.scope;
  if (scope && scope.prices) {
    const expandedItems = await Price.expand(
      scope.prices.map((x) => ({ id: x, price_id: x, quantity: 1 })),
      { product: true }
    );
    return expandedItems;
  }
  return [];
}

router.get('/', authMine, async (req, res) => {
  try {
    const { page, pageSize, ...query } = await listSchema.validateAsync(req.query, { stripUnknown: true });
    const where = getWhereFromKvQuery(query.q);
    if (query.customer_id) {
      const customer = await Customer.findByPkOrDid(query.customer_id);
      if (!customer) {
        throw new Error(`Customer ${query.customer_id} not found`);
      }
      where.customer_id = customer.id;
    }
    if (query.subscription_id) {
      const invoices = await Invoice.findAll({
        where: {
          subscription_id: query.subscription_id,
          ...(where.customer_id ? { customer_id: where.customer_id } : {}),
        },
        attributes: ['id'],
      });
      const invoiceIds = invoices.map((invoice) => invoice.id);
      const subscriptionFilters = [{ 'metadata.subscription_id': query.subscription_id }];
      if (invoiceIds.length > 0) {
        subscriptionFilters.push({ 'metadata.invoice_id': { [Op.in]: invoiceIds } } as any);
      }
      where[Op.and] = [...(where[Op.and] || []), { [Op.or]: subscriptionFilters }];
    }
    if (query.currency_id) {
      where.currency_id = query.currency_id;
    }
    if (query.status) {
      where.status = typeof query.status === 'string' ? query.status.split(',') : query.status;
    }
    if (typeof query.livemode === 'boolean') {
      where.livemode = query.livemode;
    }

    const order: OrderItem[] = getOrder(req.query);
    // 默认granted 、pending、depleted 排序
    order.unshift([literal("CASE status WHEN 'granted' THEN 1 WHEN 'pending' THEN 2 ELSE 3 END"), 'ASC']);

    const { rows: list, count } = await CreditGrant.findAndCountAll({
      where,
      order,
      offset: (page - 1) * pageSize,
      limit: pageSize,
      include: [
        { model: Customer, as: 'customer' },
        { model: PaymentCurrency, as: 'paymentCurrency' },
      ],
    });

    res.json({ count, list, paging: { page, pageSize } });
  } catch (err) {
    logger.error('Error listing credit grants', err);
    res.status(400).json({ error: err.message });
  }
});

router.get('/summary', authMine, async (req, res) => {
  try {
    const customerId = req.query.customer_id;
    if (!customerId) {
      return res.status(400).json({ error: 'customer_id is required' });
    }

    const customer = await Customer.findByPkOrDid(customerId as string);
    if (!customer) {
      return res.status(404).json({ error: `Customer ${customerId} not found` });
    }

    const { subscription_id: subscriptionId } = req.query;
    if (subscriptionId && typeof subscriptionId !== 'string') {
      return res.status(400).json({ error: 'subscription_id must be a string' });
    }

    let priceIds: string[] = [];
    if (subscriptionId) {
      const subscription = await Subscription.findByPk(subscriptionId);
      if (!subscription) {
        return res.status(404).json({ error: 'Subscription not found' });
      }
      priceIds = await getMeterPriceIdsFromSubscription(subscription);
    }

    const result = await CreditGrant.getEffectiveCreditSummary({
      customerId: customer.id,
      priceIds,
    });

    return res.json(result);
  } catch (err: any) {
    logger.error('get credit balance failed', { error: err.message, customerId: req.params.customer_id });
    return res.status(400).json({ error: err.message });
  }
});

const holdersSchema = Joi.object({
  currency_id: Joi.string().required(),
  page: Joi.number().integer().min(1).default(1),
  pageSize: Joi.number().integer().min(0).optional(), // 0 or undefined = return all
  livemode: Joi.boolean().optional(),
});

// Get all holders (customers with balance) for a specific credit currency
router.get('/holders', auth, async (req, res) => {
  try {
    const { error, value } = holdersSchema.validate(req.query, { stripUnknown: true });
    if (error) {
      return res.status(400).json({ error: error.message });
    }

    const { currency_id: currencyId, page, pageSize, livemode } = value;

    const currency = await PaymentCurrency.findByPk(currencyId);
    if (!currency) {
      return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
    }

    if (currency.type !== 'credit') {
      return res.status(400).json({ error: 'Currency must be of type credit' });
    }

    // Build where clause for credit grants
    const grantWhere: any = {
      currency_id: currencyId,
      status: { [Op.in]: ['granted', 'pending'] }, // Only active grants
    };
    if (typeof livemode === 'boolean') {
      grantWhere.livemode = livemode;
    }

    // Use database aggregation - only customer_id, no JOIN needed
    const aggregatedData = (await CreditGrant.findAll({
      where: grantWhere,
      attributes: [
        'customer_id',
        [fn('COUNT', col('id')), 'grantCount'],
        [fn('SUM', literal('CAST(remaining_amount AS DECIMAL(40,0))')), 'totalBalance'],
      ],
      group: ['customer_id'], // Only group by customer_id - much faster!
      order: [[literal('totalBalance'), 'DESC']],
      raw: true,
    })) as any[];

    const totalCount = aggregatedData.length;

    // Paginate (pageSize = 0 or undefined means return all)
    const shouldPaginate = pageSize && pageSize > 0;
    const paginatedData = shouldPaginate
      ? aggregatedData.slice((page - 1) * pageSize, page * pageSize)
      : aggregatedData;

    const holders = paginatedData.map((item) => ({
      customer_id: item.customer_id,
      balance: (item.totalBalance || '0').toString(),
      grantCount: parseInt(item.grantCount || '0', 10),
    }));

    return res.json({
      holders,
      currency: {
        id: currency.id,
        name: currency.name,
        symbol: currency.symbol,
        decimal: currency.decimal,
      },
      paging: {
        page: shouldPaginate ? page : 1,
        pageSize: shouldPaginate ? pageSize : totalCount,
        total: totalCount,
        totalPages: shouldPaginate ? Math.ceil(totalCount / pageSize) : 1,
      },
    });
  } catch (err: any) {
    logger.error('Error getting credit holders', { error: err.message });
    return res.status(400).json({ error: err.message });
  }
});

const checkAutoRechargeSchema = Joi.object({
  customer_id: Joi.string().required(),
  currency_id: Joi.string().required(),
  pending_amount: Joi.string().optional(),
  max_recharge_times: Joi.number().integer().min(1).max(10).optional().default(3),
  max_pending_amount: Joi.string()
    .pattern(/^\d+(\.\d+)?$/)
    .optional(),
});

router.get('/verify-availability', authMine, async (req, res) => {
  try {
    const { error, value } = checkAutoRechargeSchema.validate(req.query, { stripUnknown: true });
    if (error) {
      return res.status(400).json({ error: error.message });
    }

    const { customer_id: customerId, currency_id: currencyId } = value;
    let pendingAmount = value.pending_amount;

    const customer = await Customer.findByPkOrDid(customerId);
    if (!customer) {
      return res.status(404).json({ error: `Customer ${customerId} not found` });
    }

    const currency = await PaymentCurrency.findByPk(currencyId);
    if (!currency) {
      return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
    }

    const config = (await AutoRechargeConfig.findOne({
      where: {
        customer_id: customer.id,
        currency_id: currencyId,
        enabled: true,
      },
      include: [
        { model: PaymentCurrency, as: 'rechargeCurrency', required: false },
        { model: Price, as: 'price', include: [{ model: Product, as: 'product' }] },
        { model: PaymentMethod, as: 'paymentMethod', required: false },
      ],
    })) as
      | (AutoRechargeConfig & {
          rechargeCurrency?: PaymentCurrency;
          price?: Price & { product?: Product };
          paymentMethod?: PaymentMethod;
        })
      | null;

    if (!config) {
      return res.json({
        can_continue: false,
        has_auto_recharge: false,
        reason: 'auto_recharge_config_not_found',
      });
    }

    // 1. Check config completeness
    if (!config.rechargeCurrency) {
      return res.json({
        can_continue: false,
        reason: 'recharge_currency_not_found',
      });
    }

    if (!config.price) {
      return res.json({
        can_continue: false,
        reason: 'price_not_found',
      });
    }

    if (!config.paymentMethod) {
      return res.json({
        can_continue: false,
        reason: 'payment_method_not_found',
      });
    }

    // 2. Check if stripe (balance check not supported)
    if (config.paymentMethod.type === 'stripe') {
      return res.json({
        can_continue: false,
        reason: 'balance_check_not_supported',
      });
    }

    // 3. Check price amount
    const priceAmount = await getPriceUintAmountByCurrency(config.price, config.rechargeCurrency.id);
    if (!priceAmount) {
      return res.json({
        can_continue: false,
        reason: 'invalid_price_amount',
      });
    }

    // totalAmount: payment amount required for one auto recharge (priceAmount * quantity)
    const totalAmount = new BN(priceAmount).mul(new BN(config.quantity ?? 1));

    // 4. Get pending amount if not provided
    if (!pendingAmount) {
      const [pendingSummary] = await MeterEvent.getPendingAmounts({
        customerId: customer.id,
        currencyId,
        livemode: req.livemode,
      });
      pendingAmount = pendingSummary?.[currencyId] || '0';
    }

    const pendingAmountBN = new BN(pendingAmount);

    // 5. Check system-level maximum pending amount limit (highest priority, cannot be bypassed)
    // systemMaxPendingAmount is configured via PAYMENT_KIT_MAX_PENDING_AMOUNT (in token format)
    if (systemMaxPendingAmount > 0 && pendingAmountBN.gt(new BN(0))) {
      const systemMaxPendingAmountBN = fromTokenToUnit(
        trimDecimals(String(systemMaxPendingAmount), currency.decimal),
        currency.decimal
      );
      if (pendingAmountBN.gt(systemMaxPendingAmountBN)) {
        return res.json({
          can_continue: false,
          reason: 'system_pending_limit_exceeded',
          pending_amount: pendingAmount,
          system_max_pending_amount: systemMaxPendingAmountBN.toString(),
          detail: 'Pending amount exceeds system maximum limit configured in Payment Kit',
        });
      }
    }

    // 6. Check caller-specified max pending amount limit (if provided)
    // Note: max_pending_amount is in token format (e.g., "100"), need to convert to unit format for comparison
    if (value.max_pending_amount && pendingAmountBN.gt(new BN(0))) {
      const maxPendingAmountBN = fromTokenToUnit(
        trimDecimals(value.max_pending_amount, currency.decimal),
        currency.decimal
      );
      if (pendingAmountBN.gt(maxPendingAmountBN)) {
        return res.json({
          can_continue: false,
          reason: 'pending_amount_exceeds_limit',
          pending_amount: pendingAmount,
          max_pending_amount: maxPendingAmountBN.toString(),
          detail: 'Current pending amount exceeds the maximum allowed limit',
        });
      }
    }

    // 7. Get payer
    const payer =
      config.payment_settings?.payment_method_options?.[
        config.paymentMethod.type as keyof typeof config.payment_settings.payment_method_options
      ]?.payer || customer.did;

    if (!payer) {
      return res.json({
        can_continue: false,
        reason: 'payer_not_found',
      });
    }

    let balanceResult: { sufficient: boolean; token?: { balance: string } } | null = null;
    // 8. If user has pending amount, check if they can pay it off
    if (pendingAmountBN.gt(new BN(0))) {
      // Get credit amount per recharge from price metadata
      const creditConfig = config.price.metadata?.credit_config;
      if (!creditConfig || !creditConfig.credit_amount) {
        return res.json({
          can_continue: false,
          reason: 'credit_config_not_found',
        });
      }

      // Convert credit_amount from token to unit, then multiply by quantity
      // creditAmountPerRecharge: credits amount obtained from one auto recharge
      const creditAmountPerUnit = fromTokenToUnit(creditConfig.credit_amount, currency.decimal);
      const creditAmountPerRecharge = new BN(creditAmountPerUnit).mul(new BN(config.quantity ?? 1));

      // Calculate how many recharges are needed to pay off the pending amount
      // Formula: ceil(pendingAmount / creditAmountPerRecharge)
      const requiredRechargeTimesBN = pendingAmountBN
        .add(creditAmountPerRecharge)
        .sub(new BN(1))
        .div(creditAmountPerRecharge);

      // Check if required recharge times exceeds limit
      const maxRechargeTimes = value.max_recharge_times;
      if (requiredRechargeTimesBN.gt(new BN(maxRechargeTimes))) {
        return res.json({
          can_continue: false,
          reason: 'too_many_recharges_required',
          pending_amount: pendingAmount,
          required_recharge_times: requiredRechargeTimesBN.toString(),
          max_allowed_times: maxRechargeTimes,
        });
      }

      // Calculate required payment amount to pay off pending
      // requiredPaymentAmount = totalAmount (one recharge cost) * requiredRechargeTimes
      const requiredPaymentAmount = totalAmount.mul(requiredRechargeTimesBN);

      // Check daily limit
      const today = new Date().toISOString().split('T')[0];
      const isNewDay = config.last_recharge_date !== today;

      if (!isNewDay && config.daily_stats && config.daily_limits) {
        const { max_attempts: maxAttemptsRaw, max_amount: maxAmountRaw } = config.daily_limits;
        const { attempt_count: attemptCount, total_amount: totalAmountStats } = config.daily_stats;

        // Check attempt limit
        const maxAttempts = Number(maxAttemptsRaw);
        if (maxAttempts > 0) {
          // Safe to convert to number since requiredRechargeTimesBN is already checked to be <= 3
          const requiredRechargeTimes = requiredRechargeTimesBN.toNumber();
          const remainingAttempts = maxAttempts - Number(attemptCount);
          if (requiredRechargeTimes > remainingAttempts) {
            return res.json({
              can_continue: false,
              reason: 'daily_limit_reached',
              detail: 'attempt_limit_exceeded',
            });
          }
        }

        // Check amount limit
        const maxAmount = new BN(maxAmountRaw || '0');
        if (maxAmount.gt(new BN(0))) {
          const remainingAmount = maxAmount.sub(new BN(totalAmountStats || '0'));
          if (requiredPaymentAmount.gt(remainingAmount)) {
            return res.json({
              can_continue: false,
              reason: 'daily_limit_reached',
              detail: 'amount_limit_exceeded',
            });
          }
        }
      }

      // Check payment account balance: balance must be sufficient to pay off pending AND cover at least one more recharge
      // This ensures user can pay off pending amount and still have balance for continued usage
      // minimumRequiredBalance = requiredPaymentAmount (to pay off pending) + totalAmount (for one more recharge)
      const minimumRequiredBalance = requiredPaymentAmount.add(totalAmount);
      balanceResult = await checkTokenBalance({
        paymentMethod: config.paymentMethod,
        paymentCurrency: config.rechargeCurrency,
        userDid: payer,
        amount: minimumRequiredBalance.toString(),
        skipUserCheck: true,
      });

      if (!balanceResult.sufficient) {
        return res.json({
          can_continue: false,
          reason: 'insufficient_balance',
          payment_account_balance: balanceResult.token?.balance || '0',
          pending_amount: pendingAmount,
          required_amount: minimumRequiredBalance.toString(),
          detail: 'balance_must_cover_pending_plus_one_recharge',
        });
      }
    } else {
      // No pending amount: check if balance can cover at least one recharge
      balanceResult = await checkTokenBalance({
        paymentMethod: config.paymentMethod,
        paymentCurrency: config.rechargeCurrency,
        userDid: payer,
        amount: totalAmount.toString(),
        skipUserCheck: true,
      });

      if (!balanceResult.sufficient) {
        return res.json({
          can_continue: false,
          reason: 'insufficient_balance',
          payment_account_balance: balanceResult.token?.balance || '0',
          pending_amount: pendingAmount,
          required_amount: totalAmount.toString(),
        });
      }
    }

    // 7. Check dynamic pricing constraints
    const isDynamicPricing = config.price.pricing_type === 'dynamic';
    let exchangeRateInfo: { rate: string; timestamp_ms: number; degraded?: boolean } | null = null;
    let slippageCheck: { passed: boolean; reason?: string } | null = null;
    let canCoverPending = true;

    if (isDynamicPricing) {
      // 7a. Check if exchange rate is available
      const methodType = config.paymentMethod.type as ChainType;
      if (!hasTokenAddress(config.rechargeCurrency.symbol, methodType)) {
        return res.json({
          can_continue: false,
          reason: 'exchange_rate_not_supported',
          is_dynamic_pricing: true,
        });
      }

      // 7b. Get current exchange rate
      try {
        const exchangeRateService = getExchangeRateService();
        const rateSymbol = getExchangeRateSymbol(config.rechargeCurrency.symbol, methodType);
        const rateResult = await exchangeRateService.getRate(rateSymbol);
        exchangeRateInfo = {
          rate: rateResult.rate,
          timestamp_ms: rateResult.timestamp_ms,
          degraded: rateResult.degraded,
        };
      } catch (err: any) {
        logger.warn('Failed to fetch exchange rate for auto-recharge check', {
          error: err.message,
          symbol: config.rechargeCurrency.symbol,
        });
        return res.json({
          can_continue: false,
          reason: 'exchange_rate_fetch_failed',
          is_dynamic_pricing: true,
          error: err.message,
        });
      }

      // 7c. Check slippage if config exists
      if (config.slippage_config?.min_acceptable_rate && exchangeRateInfo) {
        const rateBelowMin = isRateBelowMinAcceptableRate(
          exchangeRateInfo.rate,
          config.slippage_config.min_acceptable_rate
        );
        slippageCheck = {
          passed: !rateBelowMin,
          reason: rateBelowMin ? 'rate_below_min_acceptable' : undefined,
        };

        if (rateBelowMin) {
          return res.json({
            can_continue: false,
            reason: 'slippage_exceeded',
            is_dynamic_pricing: true,
            exchange_rate: exchangeRateInfo,
            slippage_check: slippageCheck,
            slippage_config: {
              min_acceptable_rate: config.slippage_config.min_acceptable_rate,
              percent: config.slippage_config.percent,
            },
          });
        }
      }

      // 7d. For dynamic pricing, check if single recharge can cover pending amount
      if (pendingAmountBN.gt(new BN(0))) {
        // Calculate credit amount from single recharge
        const creditConfig = config.price.metadata?.credit_config as { credit_amount?: string | number } | undefined;
        const creditAmount = creditConfig?.credit_amount
          ? new BN(String(creditConfig.credit_amount)).mul(new BN(config.quantity ?? 1))
          : new BN(0);

        // Convert pending amount to credit currency for comparison
        canCoverPending = creditAmount.gte(pendingAmountBN);

        if (!canCoverPending) {
          return res.json({
            can_continue: false,
            reason: 'cannot_cover_pending_single_recharge',
            is_dynamic_pricing: true,
            exchange_rate: exchangeRateInfo,
            slippage_check: slippageCheck,
            pending_amount: pendingAmount,
            single_recharge_credit: creditAmount.toString(),
          });
        }
      }
    }

    return res.json({
      can_continue: true,
      payment_account_sufficient: true,
      payment_account_balance: balanceResult?.token?.balance || '0',
      pending_amount: pendingAmount,
      is_dynamic_pricing: isDynamicPricing,
      ...(exchangeRateInfo && { exchange_rate: exchangeRateInfo }),
      ...(slippageCheck && { slippage_check: slippageCheck }),
      can_cover_pending: canCoverPending,
    });
  } catch (err: any) {
    logger.error('check auto recharge failed', {
      error: err.message,
      customerId: req.query.customer_id,
      currencyId: req.query.currency_id,
    });
    return res.status(400).json({ error: err.message });
  }
});

// Schema for stats endpoint
const statsSchema = Joi.object({
  // Credit granted by did
  // The did that grants the credit is not necessarily the component that send the request.
  granted_by: Joi.string().optional(),
  category: Joi.string().valid('paid', 'promotional').optional(),
  currency_id: Joi.string().required(),
  start_date: Joi.number().integer().required(),
  end_date: Joi.number().integer().required(),
  timezone_offset: Joi.number()
    .integer()
    .min(-12 * 60)
    .max(14 * 60)
    .optional(),
});

// Get credit grant statistics with flexible filtering
router.get('/stats', auth, async (req, res) => {
  try {
    const { error, value } = statsSchema.validate(req.query, { stripUnknown: true });
    if (error) {
      return res.status(400).json({ error: error.message });
    }

    const {
      granted_by: grantedBy,
      category,
      currency_id: currencyId,
      start_date: startDate,
      end_date: endDate,
      timezone_offset: timezoneOffset,
    } = value;

    if (startDate > endDate) {
      return res.status(400).json({ error: 'start_date must be less than or equal to end_date' });
    }

    const result = await getCreditGrantStats({
      grantedBy,
      category,
      currencyId,
      startDate,
      endDate,
      timezoneOffset,
    });

    return res.json(result);
  } catch (err: any) {
    logger.error('Error getting credit grant stats', { error: err.message, query: req.query });
    return res.status(400).json({ error: err.message });
  }
});

router.get('/:id', authPortal, async (req, res) => {
  const creditGrant = (await CreditGrant.findByPk(req.params.id, {
    include: [
      { model: Customer, as: 'customer' },
      { model: PaymentCurrency, as: 'paymentCurrency' },
    ],
  })) as CreditGrant & { paymentCurrency?: PaymentCurrency };
  if (!creditGrant) {
    return res.status(404).json({ error: `Credit grant ${req.params.id} not found` });
  }

  let paymentMethod = null;
  if (creditGrant.paymentCurrency) {
    paymentMethod = await PaymentMethod.findByPk(creditGrant.paymentCurrency.payment_method_id);
  }
  const expandedPrices = await expandScopePrices(creditGrant);
  return res.json({
    ...creditGrant.toJSON(),
    items: expandedPrices,
    paymentMethod,
  });
});

router.post('/', auth, async (req, res) => {
  try {
    const { error } = creditGrantSchema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: `Credit grant create request invalid: ${error.message}` });
    }

    // 获取币种信息用于金额转换
    const currencyId = req.body.currency_id;
    if (!currencyId) {
      return res.status(400).json({ error: 'currency_id is required' });
    }

    const paymentCurrency = await PaymentCurrency.findByPk(currencyId);
    if (!paymentCurrency) {
      return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
    }

    let customer = await Customer.findByPkOrDid(req.body.customer_id);
    if (!customer) {
      if (req.body.customer_id.startsWith('cus_')) {
        return res.status(404).json({ error: `Customer ${req.body.customer_id} not found` });
      }
      const did = req.body.customer_id;
      const { user: userInfo } = await blocklet.getUser(did);
      if (!userInfo) {
        return res.status(404).json({ error: `User ${did} not found` });
      }
      customer = await Customer.create({
        livemode: true,
        did: userInfo?.did || did,
        name: userInfo?.fullName || did,
        email: userInfo?.email || '',
        phone: userInfo?.phone || '',
        address: Customer.formatAddressFromUser(userInfo),
        description: userInfo.remark || '',
        metadata: {},
        balance: '0',
        next_invoice_sequence: 1,
        delinquent: false,
        invoice_prefix: Customer.getInvoicePrefix(),
      });
      logger.info('Customer created on credit grant', {
        customerId: customer.id,
        customer: customer.toJSON(),
      });
    }

    const unitAmount = fromTokenToUnit(req.body.amount, paymentCurrency.decimal).toString();
    let applicabilityConfig = req.body.applicability_config;
    if (!req.body.applicability_config || !req.body.applicability_config.scope?.prices) {
      applicabilityConfig = {
        scope: {
          price_type: 'metered',
        },
      };
    }

    const creditGrant = await createCreditGrant({
      amount: unitAmount,
      currency_id: currencyId,
      customer_id: customer.id,
      name: req.body.name,
      category: req.body.category,
      priority: req.body.priority,
      effective_at: req.body.effective_at,
      expires_at: req.body.expires_at,
      applicability_config: applicabilityConfig,
      metadata: req.body.metadata,
      livemode: req.livemode,
      created_via: req.user?.via || 'api',
      created_by: req.user?.did,
    });

    return res.json({
      ...creditGrant.toJSON(),
      customer,
      paymentCurrency,
    });
  } catch (err: any) {
    logger.error('create credit grant failed', { error: err, request: req.body });
    return res.status(400).json({ error: err.message });
  }
});

const updateSchema = Joi.object({
  metadata: MetadataSchema,
  expired: Joi.boolean().optional(),
});

router.put('/:id', auth, async (req, res) => {
  const creditGrant = await CreditGrant.findByPk(req.params.id);
  if (!creditGrant) {
    return res.status(404).json({ error: `Credit grant ${req.params.id} not found` });
  }

  const { error, value } = updateSchema.validate(pick(req.body, ['metadata', 'expired']), { stripUnknown: true });
  if (error) {
    return res.status(400).json({ error: `Credit grant update request invalid: ${error.message}` });
  }

  // Handle metadata update
  if (value.metadata !== undefined) {
    await creditGrant.update({ metadata: formatMetadata(value.metadata) });
  }

  // Handle expire operation first (before metadata update)
  if (value.expired === true) {
    // Only pending or granted grants can be expired
    if (!['pending', 'granted'].includes(creditGrant.status)) {
      return res.status(400).json({
        error: `Cannot expire credit grant with status '${creditGrant.status}'. Only 'pending' or 'granted' grants can be expired.`,
      });
    }

    await expireGrant(creditGrant);

    logger.info('Credit grant manually expired', {
      creditGrantId: req.params.id,
      previousStatus: creditGrant.status,
      requestedBy: req.user?.did,
    });
  }

  return res.json({ success: true });
});

export default router;
