/* eslint-disable no-await-in-loop */
import type { Transaction } from '@ocap/client';
import { fromAddress } from '@ocap/wallet';

import { toBase58 } from '@ocap/util';
import { encodeTransferItx } from '../../integrations/ethereum/token';
import { waitForEvmTxConfirm, waitForEvmTxReceipt } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import { ethWallet, wallet } from '../../libs/auth';
import { getGasPayerExtra } from '../../libs/payment';
import { getTxMetadata } from '../../libs/util';
import { invoiceQueue } from '../../queues/invoice';
import { handlePaymentSucceed, paymentQueue } from '../../queues/payment';
import { EVMChainType, PaymentIntent } from '../../store/models';
import { ensureSubscriptionForCollectBatch, getAuthPrincipalClaim } from './shared';
import { EVM_CHAIN_TYPES } from '../../libs/constants';

// Used to collect uncollectible invoices for a subscription and currency
export default {
  action: 'collect-batch',
  authPrincipal: false,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureSubscriptionForCollectBatch(
        extraParams.subscriptionId,
        extraParams.currencyId,
        extraParams.customerId
      );
      return getAuthPrincipalClaim(paymentMethod, 'pay');
    },
  },
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
    const { subscriptionId, currencyId, customerId } = extraParams;
    const { amount, invoices, paymentCurrency, paymentMethod, subscription } = await ensureSubscriptionForCollectBatch(
      subscriptionId,
      currencyId,
      customerId
    );

    if (paymentMethod.type === 'arcblock') {
      const tokens = [{ address: paymentCurrency.contract as string, value: amount }];
      // @ts-ignore
      const itx: TransferV3Tx = {
        outputs: [{ owner: wallet.address, tokens, assets: [] }],
        data: getTxMetadata({
          subscriptionId,
          currencyId,
          invoices,
        }),
      };

      const claims: { [key: string]: object } = {
        prepareTx: {
          type: 'TransferV3Tx',
          description: `Pay all past due invoices ${subscriptionId ? `for subscription ${subscription?.description}` : ''}`,
          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(ethWallet.address, amount!, paymentCurrency.contract),
              }),
              'utf-8'
            )
          ),
        },
      };
    }

    throw new Error(`Payment method ${paymentMethod.type} not supported`);
  },
  onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
    const { subscriptionId, currencyId, customerId } = extraParams;
    const { invoices, paymentMethod } = await ensureSubscriptionForCollectBatch(subscriptionId, currencyId, customerId);

    const afterTxExecution = async (paymentDetails: any) => {
      const paymentIntents = await PaymentIntent.findAll({
        where: {
          invoice_id: invoices,
        },
      });
      for (const paymentIntent of paymentIntents) {
        await paymentIntent.update({
          status: 'succeeded',
          amount_received: paymentIntent.amount,
          capture_method: 'manual',
          last_payment_error: null,
          payment_details: {
            [paymentMethod.type]: paymentDetails,
          },
        });

        await handlePaymentSucceed(paymentIntent);

        // cleanup the queue
        let exist = await paymentQueue.get(paymentIntent.id);
        if (exist) {
          await paymentQueue.delete(paymentIntent.id);
        }
        if (paymentIntent.invoice_id) {
          exist = await invoiceQueue.get(paymentIntent.invoice_id);
          if (exist) {
            await invoiceQueue.delete(paymentIntent.invoice_id);
          }
        }
      }
    };

    if (paymentMethod.type === 'arcblock') {
      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))
      );
      await afterTxExecution({ tx_hash: txHash, payer: userDid, type: 'transfer' });

      return { hash: txHash };
    }
    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      const client = paymentMethod.getEvmClient();
      const claim = claims.find((x) => x.type === 'signature');

      const receipt = await waitForEvmTxReceipt(client, claim.hash);
      if (!receipt.status) {
        throw new Error(`EVM Transaction failed: ${claim.hash}`);
      }

      waitForEvmTxConfirm(client, Number(receipt.blockNumber), paymentMethod.confirmation.block)
        .then(async () => {
          await afterTxExecution({
            tx_hash: claim.hash,
            block_height: receipt.blockNumber.toString(),
            gas_used: receipt.gasUsed.toString(),
            gas_price: receipt.gasPrice.toString(),
            payer: userDid,
            type: 'transfer',
          });
        })
        .catch(console.error);
      return { hash: claim.hash };
    }
    throw new Error(`Payment method ${paymentMethod.type} not supported`);
  },
};
