import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import { getTxMetadata } from '../../libs/util';
import { Lock, type TLineItemExpanded } from '../../store/models';
import {
  ensureChangePaymentContext,
  executeOcapTransactions,
  getAuthPrincipalClaim,
  getDelegationTxClaim,
  getStakeTxClaim,
  updateStripeSubscriptionAfterChangePayment,
} from './shared';
import { ensureStakeInvoice } from '../../libs/invoice';
import { EVM_CHAIN_TYPES } from '../../libs/constants';
import logger from '../../libs/logger';
import { getFastCheckoutAmount, getSubscriptionCreateSetup, SlippageOptions } from '../../libs/session';
import { isDelegationSufficientForPayment } from '../../libs/payment';
import { getQuoteService } from '../../libs/quote-service';

export default {
  action: 'change-payment',
  authPrincipal: false,
  persistentDynamicClaims: true,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureChangePaymentContext(extraParams.subscriptionId);
      return getAuthPrincipalClaim(paymentMethod, 'continue');
    },
  },
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
    const { subscriptionId } = extraParams;
    const { subscription, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);

    const claimsList: any[] = [];
    // @ts-ignore
    const items = subscription!.items as TLineItemExpanded[];
    const trialing = true;
    const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);

    // Calculate required amount considering slippage_config for dynamic pricing
    const slippageConfig = subscription?.slippage_config;
    let requiredAmount: string;
    if (slippageConfig?.min_acceptable_rate) {
      // Use slippage_config for precise calculation
      const slippageOptions: SlippageOptions = {
        percent: slippageConfig.percent ?? 0.5,
        minAcceptableRate: slippageConfig.min_acceptable_rate,
        currencyDecimal: paymentCurrency.decimal,
      };
      const setup = getSubscriptionCreateSetup(items, paymentCurrency.id, 0, 0, slippageOptions);
      requiredAmount = setup.amount.setup;
    } else {
      requiredAmount = await getFastCheckoutAmount({
        items,
        mode: 'subscription',
        currencyId: paymentCurrency.id,
        trialing: false,
      });
    }

    if (paymentMethod.type === 'arcblock') {
      const delegation = await isDelegationSufficientForPayment({
        paymentMethod,
        paymentCurrency,
        userDid,
        amount: requiredAmount,
      });
      const needDelegation = delegation.sufficient === false;
      const requiredStake = !subscription.billing_thresholds?.no_stake;
      if (needDelegation || !requiredStake) {
        claimsList.push({
          signature: await getDelegationTxClaim({
            mode: 'setup',
            userDid,
            userPk,
            nonce: `change-method-${subscription.id}`,
            data: getTxMetadata({ subscriptionId: subscription.id }),
            paymentCurrency,
            paymentMethod,
            trialing,
            billingThreshold,
            items,
            requiredStake,
            slippageConfig: subscription?.slippage_config || undefined,
            // Reuse DelegateState fetched above — avoids a duplicate chain RPC on CF cold isolates
            delegateState: delegation.state,
          }),
        });
      }

      if (requiredStake) {
        claimsList.push({
          prepareTx: await getStakeTxClaim({
            userDid,
            userPk,
            paymentCurrency,
            paymentMethod,
            items,
            subscription,
          }),
        });
      }
      return claimsList;
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      if (!paymentCurrency.contract) {
        throw new Error(`Payment currency ${paymentMethod.type}:${paymentCurrency.id} does not support subscription`);
      }

      claimsList.push({
        signature: await getDelegationTxClaim({
          mode: 'subscription',
          userDid,
          userPk,
          nonce: `change-method-${subscription!.id}`,
          data: getTxMetadata({ subscriptionId: subscription!.id }),
          paymentCurrency,
          paymentMethod,
          trialing,
          billingThreshold,
          items,
          slippageConfig: subscription?.slippage_config || undefined,
        }),
      });

      return claimsList;
    }

    throw new Error(`ChangePayment: Payment method ${paymentMethod.type} not supported`);
  },

  onAuth: async ({ request, userDid, userPk, claims, extraParams, updateSession, step }: CallbackArgs) => {
    const { subscriptionId } = extraParams;
    const { setupIntent, subscription, paymentMethod, paymentCurrency, customer } =
      await ensureChangePaymentContext(subscriptionId);

    const requiredStake = !subscription.billing_thresholds?.no_stake;

    const result = request?.context?.store?.result || [];
    result.push({
      step,
      claim: claims?.[0],
      stepRequest: {
        headers: request?.headers,
      },
    });

    // 判断是否为最后一步
    const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
    const isFinalStep =
      (paymentMethod.type === 'arcblock' && (staking || !requiredStake)) || paymentMethod.type !== 'arcblock';

    if (!isFinalStep) {
      await updateSession({
        result,
      });
      return { result };
    }

    // 清理 session
    try {
      await updateSession({
        result: [],
      });
    } catch (error) {
      logger.error('updateSession', {
        error,
      });
    }
    const claimsList = result.map((x: any) => x.claim);

    const prepareTxExecution = async () => {
      await subscription?.update({
        payment_settings: {
          payment_method_types: [paymentMethod.type],
          payment_method_options: {
            [paymentMethod.type]: { payer: userDid },
          },
        },
      });
    };

    const afterTxExecution = async (paymentDetails: any) => {
      await setupIntent.update({
        status: 'succeeded',
        last_setup_error: null,
        payment_method_types: [paymentMethod.type],
        payment_method_options: {
          [paymentMethod.type]: { payer: userDid },
        },
        setup_details: {
          [paymentMethod.type]: paymentDetails,
        },
      });

      await subscription?.update({
        currency_id: paymentCurrency.id,
        default_payment_method_id: paymentMethod.id,
        payment_details: {
          ...subscription.payment_details,
          [paymentMethod.type]: paymentDetails,
        },
      });

      // Create quotes for dynamic pricing items after payment method change
      // @ts-ignore
      const items = subscription!.items as TLineItemExpanded[];
      const dynamicItems = items.filter(
        (item: TLineItemExpanded) => (item.upsell_price || item.price)?.pricing_type === 'dynamic'
      );
      if (dynamicItems.length > 0) {
        const quoteService = getQuoteService();
        for (const item of dynamicItems) {
          try {
            const price = item.upsell_price || item.price;
            // eslint-disable-next-line no-await-in-loop
            await quoteService.getOrCreateQuote({
              price_id: price.id,
              target_currency_id: paymentCurrency.id,
              quantity: item.quantity || 1,
              idempotency_key_salt: `change-payment-${subscription!.id}-${Date.now()}`,
            });
            logger.info('Created quote for dynamic pricing item after payment change', {
              subscriptionId: subscription!.id,
              priceId: price.id,
              currencyId: paymentCurrency.id,
            });
          } catch (error) {
            logger.error('Failed to create quote for dynamic pricing item after payment change', {
              subscriptionId: subscription!.id,
              priceId: (item.upsell_price || item.price)?.id,
              currencyId: paymentCurrency.id,
              error,
            });
            // Continue with other items even if one fails
          }
        }
      }

      await Lock.acquire(`${subscription.id}-change-plan`, subscription.current_period_end);
      // update stripe subscription
      await updateStripeSubscriptionAfterChangePayment(setupIntent, subscription);
    };

    if (paymentMethod.type === 'arcblock') {
      await prepareTxExecution();
      const requestArray = result
        .map((item: { stepRequest?: Request }) => item.stepRequest)
        .filter(Boolean) as Request[];
      const requestSource = requestArray.length > 0 ? requestArray : request;

      const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
        userDid,
        userPk,
        claimsList,
        paymentMethod,
        requestSource,
        subscription?.id,
        paymentCurrency.contract
      );
      if (stakingAmount && stakingAmount !== '0') {
        await ensureStakeInvoice(
          {
            total: stakingAmount,
            description: 'Stake for subscription payment change',
            currency_id: paymentCurrency.id,
            metadata: {
              payment_details: {
                arcblock: {
                  tx_hash: paymentDetails?.staking?.tx_hash,
                  payer: paymentDetails?.payer,
                  address: paymentDetails?.staking?.address,
                },
              },
            },
          },
          subscription!,
          paymentMethod,
          customer!
        );
      }

      await afterTxExecution(paymentDetails);
      return { hash: paymentDetails.tx_hash };
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      await prepareTxExecution();
      const paymentDetails = await executeEvmTransaction('approve', userDid, claimsList, paymentMethod);
      waitForEvmTxConfirm(paymentMethod.getEvmClient(), +paymentDetails.block_height, paymentMethod.confirmation.block)
        .then(async () => {
          await afterTxExecution(paymentDetails);
        })
        .catch(console.error);
      return { hash: paymentDetails.tx_hash };
    }

    throw new Error(`ChangePayment: Payment method ${paymentMethod.type} not supported`);
  },
};
