import { broadcastEvmTransaction, executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import dayjs from '../../libs/dayjs';
import logger from '../../libs/logger';
import { isDelegationSufficientForPayment } from '../../libs/payment';
import { getSubscriptionTrialSetup } from '../../libs/subscription';
import { getFastCheckoutAmount, getSubscriptionCreateSetup, type SlippageOptions } from '../../libs/session';
import { getTxMetadata } from '../../libs/util';
import { normalizeSlippageConfigFromMetadata } from '../../libs/slippage';
import { invoiceQueue } from '../../queues/invoice';
import { addSubscriptionJob } from '../../queues/subscription';
import { Price } from '../../store/models';
import {
  ensureSetupIntent,
  executeOcapTransactions,
  getAuthPrincipalClaim,
  getDelegationTxClaim,
  getStakeTxClaim,
  returnStakeForCanceledSubscription,
} from './shared';
import { ensureStakeInvoice } from '../../libs/invoice';
import { EVM_CHAIN_TYPES } from '../../libs/constants';

export default {
  action: 'setup',
  authPrincipal: false,
  persistentDynamicClaims: true,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureSetupIntent(extraParams.checkoutSessionId, true);
      return getAuthPrincipalClaim(paymentMethod, 'subscribe');
    },
  },
  onConnect: async (args: CallbackArgs) => {
    const { userDid, userPk, extraParams } = args;
    const { checkoutSessionId } = extraParams;
    const { paymentMethod, paymentCurrency, checkoutSession, subscription } = await ensureSetupIntent(
      checkoutSessionId,
      true
    );
    if (!subscription) {
      throw new Error('Subscription for checkoutSession not found');
    }

    const claimsList: any[] = [];
    const now = dayjs().unix();
    // Expand line_items to include full price and upsell_price objects
    // This is critical for getSubscriptionCreateSetup to correctly use upsell_price when calculating authorization amount
    const items = await Price.expand(checkoutSession.line_items);
    const { trialEnd, trialInDays } = getSubscriptionTrialSetup(
      checkoutSession.subscription_data as any,
      paymentCurrency.id
    );

    const trialing = trialInDays > 0 || trialEnd > now;
    const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
    const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);

    // Calculate required amount considering slippage_config for dynamic pricing
    // Priority: subscription.slippage_config > checkoutSession.metadata.slippage
    const slippageConfig =
      subscription?.slippage_config || normalizeSlippageConfigFromMetadata(checkoutSession.metadata);
    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, trialInDays, trialEnd, slippageOptions);
      requiredAmount = setup.amount.setup;
    } else {
      requiredAmount = await getFastCheckoutAmount({
        items,
        mode: checkoutSession.mode,
        currencyId: paymentCurrency.id,
        trialing,
      });
    }

    if (paymentMethod.type === 'arcblock') {
      const delegation = await isDelegationSufficientForPayment({
        paymentMethod,
        paymentCurrency,
        userDid,
        amount: requiredAmount,
      });
      // if we can complete purchase without any wallet interaction
      if (delegation.sufficient === false) {
        claimsList.push({
          signature: await getDelegationTxClaim({
            mode: checkoutSession.mode,
            userDid,
            userPk,
            nonce: checkoutSession.id,
            data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
            paymentCurrency,
            paymentMethod,
            trialing,
            billingThreshold: Math.max(minStakeAmount, billingThreshold),
            items,
            slippageConfig: subscription?.slippage_config || undefined,
            // Reuse DelegateState fetched above — avoids a duplicate chain RPC on CF cold isolates
            delegateState: delegation.state,
          }),
        });
      }

      // we always need to stake for the subscription
      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 setup`);
      }

      claimsList.push({
        signature: await getDelegationTxClaim({
          mode: checkoutSession.mode,
          userDid,
          userPk,
          nonce: checkoutSession.id,
          data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
          paymentCurrency,
          paymentMethod,
          trialing,
          billingThreshold,
          items,
          slippageConfig: subscription?.slippage_config || undefined,
        }),
      });

      return claimsList;
    }

    throw new Error(`Payment method ${paymentMethod.type} not supported`);
  },
  onAuth: async (args: CallbackArgs) => {
    const { request, userDid, userPk, claims, extraParams, updateSession, step } = args;
    const { checkoutSessionId } = extraParams;
    const { setupIntent, checkoutSession, paymentMethod, subscription, invoice, paymentCurrency, customer } =
      await ensureSetupIntent(checkoutSessionId);

    if (!subscription) {
      throw new Error('Subscription for checkoutSession not found');
    }

    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) || 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 paymentSettings = {
      payment_method_types: [paymentMethod.type],
      payment_method_options: {
        [paymentMethod.type]: { payer: userDid },
      },
    };

    const prepareTxExecution = async () => {
      await setupIntent.update({ status: 'processing' });
      await subscription.update({ payment_settings: paymentSettings });
      if (invoice) {
        await invoice.update({ payment_settings: paymentSettings });
      }
    };

    const afterTxExecution = async (paymentDetails: any) => {
      await setupIntent.update({
        status: 'succeeded',
        last_setup_error: null,
        setup_details: { [paymentMethod.type]: paymentDetails },
        ...paymentSettings,
      });
      await subscription.update({
        status: subscription.trial_end ? 'trialing' : 'active',
        payment_details: { [paymentMethod.type]: paymentDetails },
      });

      await checkoutSession.update({ status: 'complete', payment_status: 'paid' });
      if (invoice) {
        await invoiceQueue.pushAndWait({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
      }
      await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
      logger.info('CheckoutSession updated on setup done', {
        checkoutSession: checkoutSession.id,
        setupIntent: setupIntent.id,
        paymentDetails,
      });
    };

    if (paymentMethod.type === 'arcblock') {
      try {
        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
        );

        await ensureStakeInvoice(
          {
            total: stakingAmount,
            description: 'Stake for subscription',
            checkout_session_id: checkoutSessionId,
            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);

        if (subscription.recovered_from) {
          returnStakeForCanceledSubscription(subscription.recovered_from);
        }

        return { hash: paymentDetails.tx_hash };
      } catch (err) {
        logger.error('Failed to finalize setup', { setupIntent: setupIntent.id, error: err });
        await setupIntent.update({ status: 'requires_capture' });
        throw err;
      }
    }

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

      return { hash: paymentDetails.tx_hash };
    }

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