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

export default {
  action: 'auto-recharge-auth',
  authPrincipal: false,
  persistentDynamicClaims: true,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureAutoRechargeAuthorization(extraParams.autoRechargeConfigId);
      return getAuthPrincipalClaim(paymentMethod, 'auto-recharge-auth');
    },
  },
  onConnect: async (args: CallbackArgs) => {
    const { userDid, userPk, extraParams } = args;
    const { autoRechargeConfigId } = extraParams;
    const { autoRechargeConfig, paymentMethod, paymentCurrency } =
      await ensureAutoRechargeAuthorization(autoRechargeConfigId);
    const claimsList: any[] = [];
    const expandedItems = await Price.expand(
      [{ price_id: autoRechargeConfig.price_id, quantity: autoRechargeConfig.quantity }],
      {
        product: true,
      }
    );
    if (paymentMethod.type === 'arcblock') {
      claimsList.push({
        signature: await getDelegationTxClaim({
          mode: 'auto-recharge-auth',
          userDid,
          userPk,
          nonce: autoRechargeConfig.id,
          data: getTxMetadata({
            autoRechargeConfigId,
          }),
          paymentCurrency,
          paymentMethod,
          trialing: false,
          billingThreshold: 0,
          items: expandedItems,
          requiredStake: false,
          slippageConfig: autoRechargeConfig.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: 'auto-recharge-auth',
          userDid,
          userPk,
          nonce: autoRechargeConfig.id,
          data: getTxMetadata({
            autoRechargeConfigId,
          }),
          paymentCurrency,
          paymentMethod,
          trialing: false,
          billingThreshold: 0,
          items: expandedItems,
          requiredStake: false,
          slippageConfig: autoRechargeConfig.slippage_config || undefined,
        }),
      });

      return claimsList;
    }

    throw new Error(`subscription: Payment method ${paymentMethod.type} not supported`);
  },
  onAuth: async (args: CallbackArgs) => {
    const { request, userDid, userPk, claims, extraParams, updateSession, step } = args;
    const { autoRechargeConfigId } = extraParams;
    const { autoRechargeConfig, paymentMethod, paymentCurrency } =
      await ensureAutoRechargeAuthorization(autoRechargeConfigId);
    const result = request?.context?.store?.result || [];
    result.push({
      step,
      claim: claims?.[0],
      stepRequest: {
        headers: request?.headers,
      },
    });
    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: {
        ...(autoRechargeConfig.payment_settings?.payment_method_options || {}),
        [paymentMethod.type]: { payer: userDid },
      },
    };
    const prepareTxExecution = async () => {
      await autoRechargeConfig.update({ payment_settings: paymentSettings });
    };

    const afterTxExecution = async (paymentDetails: Record<string, any>) => {
      await autoRechargeConfig.update({
        payment_details: { ...autoRechargeConfig.payment_details, [paymentMethod.type]: paymentDetails },
        enabled: true,
      });
      logger.info('enable auto recharge success', {
        autoRechargeConfigId,
      });
    };

    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,
        '',
        paymentCurrency?.contract,
        autoRechargeConfig.id
      );

      await afterTxExecution(paymentDetails);

      return { hash: paymentDetails.tx_hash };
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      await prepareTxExecution();

      broadcastAutoRechargeEvmTransaction(autoRechargeConfigId, 'pending', claimsList);

      const paymentDetails = await executeEvmTransaction('approve', userDid, claimsList, paymentMethod);
      waitForEvmTxConfirm(
        paymentMethod.getEvmClient(),
        Number(paymentDetails.block_height),
        paymentMethod.confirmation.block
      )
        .then(async () => {
          await afterTxExecution(paymentDetails);
          broadcastAutoRechargeEvmTransaction(autoRechargeConfigId, 'confirmed', claimsList);
        })
        .catch(console.error);

      return { hash: paymentDetails.tx_hash };
    }

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