import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import { getTxMetadata } from '../../libs/util';
import { type TLineItemExpanded } from '../../store/models';
import {
  ensurePayerChangeContext,
  executeOcapTransactions,
  getAuthPrincipalClaim,
  getDelegationTxClaim,
} from './shared';
import { EVM_CHAIN_TYPES } from '../../libs/constants';

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

    if (userDid === payerAddress) {
      throw new Error('The current payer is the same as the new payer, please use another account to change payer');
    }
    const claimsList: any[] = [];
    // @ts-ignore
    const items = subscription!.items as TLineItemExpanded[];
    const trialing = true;
    const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);

    if (paymentMethod.type === 'arcblock') {
      claimsList.push({
        signature: await getDelegationTxClaim({
          mode: 'delegation',
          userDid,
          userPk,
          nonce: subscription.id,
          data: getTxMetadata({ subscriptionId: subscription.id }),
          paymentCurrency,
          paymentMethod,
          trialing,
          billingThreshold,
          items,
          requiredStake: false,
          slippageConfig: subscription?.slippage_config || undefined,
        }),
      });
      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-payer-${subscription!.id}`,
          data: getTxMetadata({ subscriptionId: subscription!.id }),
          paymentCurrency,
          paymentMethod,
          trialing,
          billingThreshold,
          items,
          slippageConfig: subscription?.slippage_config || undefined,
        }),
      });

      return claimsList;
    }

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

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

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

    const afterTxExecution = async (paymentDetails: any) => {
      await subscription?.update({
        payment_settings: {
          payment_method_types: [paymentMethod.type],
          payment_method_options: {
            [paymentMethod.type]: { payer: userDid },
          },
        },
        payment_details: {
          ...subscription.payment_details,
          [paymentMethod.type]: {
            ...(subscription.payment_details?.[paymentMethod.type as keyof typeof subscription.payment_details] || {}),
            type: 'delegate',
            payer: userDid,
            tx_hash: paymentDetails.tx_hash,
            delegation_address: paymentDetails?.delegation_address,
          },
        },
      });
    };

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

      const paymentDetails = await executeOcapTransactions(
        userDid,
        userPk,
        claimsList,
        paymentMethod,
        requestSource,
        subscription?.id,
        paymentCurrency.contract
      );

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

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      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(`ChangePayer: Payment method ${paymentMethod.type} not supported`);
  },
};
