import type { Transaction } from '@ocap/client';
import { fromAddress } from '@ocap/wallet';
import { fromTokenToUnit, toBase58 } from '@ocap/util';
import { encodeTransferItx } from '../../integrations/ethereum/token';
import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import { getGasPayerExtra } from '../../libs/payment';
import { getTxMetadata } from '../../libs/util';
import { ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
import logger from '../../libs/logger';
import { ensureRechargeInvoice, retryUncollectibleInvoices } from '../../libs/invoice';
import { EVMChainType } from '../../store/models';
import { EVM_CHAIN_TYPES } from '../../libs/constants';

export default {
  action: 'recharge',
  authPrincipal: false,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureSubscriptionRecharge(extraParams.subscriptionId);
      return getAuthPrincipalClaim(paymentMethod, 'recharge');
    },
  },
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
    const { subscriptionId } = extraParams;
    let { amount } = extraParams;
    const { paymentMethod, paymentCurrency, receiverAddress } = await ensureSubscriptionRecharge(subscriptionId);
    amount = fromTokenToUnit(amount, paymentCurrency.decimal).toString();
    if (paymentMethod.type === 'arcblock') {
      const tokens = [{ address: paymentCurrency.contract as string, value: amount }];
      // @ts-ignore
      const itx: TransferV3Tx = {
        outputs: [{ owner: receiverAddress, tokens, assets: [] }],
        data: getTxMetadata({
          rechargeCustomerId: receiverAddress,
          subscriptionId,
          currencyId: paymentCurrency.id,
          amount,
          action: 'recharge',
        }),
      };

      const claims: { [key: string]: object } = {
        prepareTx: {
          type: 'TransferV3Tx',
          description: `Recharge account for ${receiverAddress}`,
          partialTx: { from: userDid, pk: userPk, itx },
          requirement: { tokens },
          chainInfo: {
            host: paymentMethod.settings?.arcblock?.api_host as string,
            id: paymentMethod.settings?.arcblock?.chain_id as string,
          },
        },
      };

      return claims;
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      return {
        signature: {
          type: 'eth:transaction',
          data: toBase58(
            Buffer.from(
              JSON.stringify({
                network: paymentMethod.settings[paymentMethod.type as EVMChainType]?.chain_id,
                tx: encodeTransferItx(receiverAddress, amount, paymentCurrency.contract as string),
              }),
              'utf-8'
            )
          ),
        },
      };
    }

    throw new Error(`Payment method ${paymentMethod.type} not supported`);
  },
  onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
    const { subscriptionId } = extraParams;
    const { paymentMethod, paymentCurrency, receiverAddress, subscription, customer } =
      await ensureSubscriptionRecharge(subscriptionId);
    let { amount } = extraParams;
    amount = fromTokenToUnit(amount, paymentCurrency.decimal).toString();

    const afterTxExecution = async (paymentDetails: any) => {
      await ensureRechargeInvoice(
        {
          total: amount,
          description: 'Add funds for subscription',
          currency_id: paymentCurrency.id,
          metadata: {
            payment_details: {
              [paymentMethod.type]: paymentDetails,
              receiverAddress,
            },
          },
        },
        subscription!,
        paymentMethod,
        customer!
      );
      try {
        retryUncollectibleInvoices({
          currencyId: paymentCurrency.id,
          subscriptionId,
        });
      } catch (err) {
        logger.error('Failed to retry uncollectible invoices', {
          error: err,
          currencyId: paymentCurrency.id,
          subscriptionId,
        });
      }
    };
    if (paymentMethod.type === 'arcblock') {
      try {
        const client = paymentMethod.getOcapClient();
        const claim = claims.find((x) => x.type === 'prepareTx');

        const tx: Partial<Transaction> = client.decodeTx(claim.finalTx);
        if (claim.delegator && claim.from) {
          tx.delegator = claim.delegator;
          tx.from = claim.from;
        }

        // @ts-ignore
        const { buffer } = await client.encodeTransferV3Tx({ tx });
        const txHash = await client.sendTransferV3Tx(
          // @ts-ignore
          { tx, wallet: fromAddress(userDid) },
          await getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
        );

        logger.info('Recharge successful', {
          receiverAddress,
          amount,
          subscriptionId,
          paymentMethod: paymentMethod.type,
        });

        await afterTxExecution({
          tx_hash: txHash,
          payer: userDid,
          type: 'transfer',
        });
        return { hash: txHash };
      } catch (err) {
        console.error(err);
        logger.error('Failed to finalize recharge on arcblock', { receiverAddress, error: err });
        throw err;
      }
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      try {
        const paymentDetails = await executeEvmTransaction('transfer', userDid, claims, paymentMethod);
        waitForEvmTxConfirm(
          paymentMethod.getEvmClient(),
          Number(paymentDetails.block_height),
          paymentMethod.confirmation.block
        )
          .then(async () => {
            logger.info('Recharge successful', {
              receiverAddress,
              amount,
              subscriptionId,
              paymentMethod: paymentMethod.type,
            });
            await afterTxExecution(paymentDetails);
          })
          .catch(console.error);

        return { hash: paymentDetails.tx_hash };
      } catch (err) {
        console.error(err);
        logger.error(`Failed to finalize recharge on ${paymentMethod.type}`, {
          receiverAddress,
          error: err,
        });
        throw err;
      }
    }

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