import type { Transaction, TransferV3Tx } from '@ocap/client';
import { toBase58 } from '@ocap/util';
import { fromAddress } from '@ocap/wallet';

import { encodeTransferItx } from '../../integrations/ethereum/token';
import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import { CallbackArgs, ethWallet } from '../../libs/auth';
import logger from '../../libs/logger';
import { getGasPayerExtra } from '../../libs/payment';
import { createPaymentOutput } from '../../libs/session';
import { getTxMetadata } from '../../libs/util';
import { handlePaymentSucceed } from '../../queues/payment';
import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim } from './shared';
import { EVMChainType } from '../../store/models';
import { EVM_CHAIN_TYPES } from '../../libs/constants';
import { validateQuoteForPayment, handleExpiredQuotePayment } from '../../libs/quote-validation';

export default {
  action: 'payment',
  authPrincipal: false,

  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensurePaymentIntent(extraParams.checkoutSessionId, '', true);
      return getAuthPrincipalClaim(paymentMethod, 'pay');
    },
  },

  onConnect: async (args: CallbackArgs) => {
    const { userDid, userPk, extraParams } = args;
    const { checkoutSessionId, connectedDid, sessionUserDid } = extraParams;
    const { paymentIntent, paymentCurrency, paymentMethod } = await ensurePaymentIntent(
      checkoutSessionId,
      connectedDid || sessionUserDid || userDid
    );
    if (!paymentIntent) {
      throw new Error('Payment intent not found');
    }

    if (paymentMethod.type === 'arcblock') {
      const tokens = [{ address: paymentCurrency.contract as string, value: paymentIntent.amount }];
      // @ts-ignore
      const itx: TransferV3Tx = {
        outputs: createPaymentOutput(
          paymentIntent.amount,
          paymentCurrency.contract as string,
          paymentIntent.beneficiaries || []
        ),
        data: getTxMetadata({
          paymentIntentId: paymentIntent.id,
          checkoutSessionId,
        }),
      };

      return {
        prepareTx: {
          type: 'TransferV3Tx',
          description: 'Approve the transfer to complete payment',
          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,
          },
        },
      };
    }

    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, paymentIntent.amount, paymentCurrency.contract),
              }),
              'utf-8'
            )
          ),
        },
      };
    }

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

  onAuth: async (args: CallbackArgs) => {
    const { request, userDid, claims, extraParams } = args;
    const { checkoutSessionId, connectedDid, sessionUserDid } = extraParams;
    const { checkoutSession, customer, paymentIntent, paymentMethod } = await ensurePaymentIntent(
      checkoutSessionId,
      connectedDid || sessionUserDid || userDid
    );
    if (!paymentIntent) {
      throw new Error('Payment intent not found');
    }

    await ensureInvoiceForCheckout({ checkoutSession, customer, paymentIntent });

    const preQuoteValidation = await validateQuoteForPayment({
      checkoutSessionId: checkoutSession?.id,
      invoiceId: paymentIntent.invoice_id,
      amount: paymentIntent.amount,
    });

    if (!preQuoteValidation.valid) {
      const errorCode =
        preQuoteValidation.action === 'require_manual_review' ? 'quote_expired' : 'quote_validation_failed';
      logger.error('Payment blocked due to quote validation before submit', {
        paymentIntentId: paymentIntent.id,
        quoteId: preQuoteValidation.quoteId,
        reason: preQuoteValidation.reason,
      });
      await paymentIntent.update({
        status: 'requires_action',
        last_payment_error: {
          type: 'validation_error',
          code: errorCode,
          message: preQuoteValidation.reason || 'Quote validation failed',
        },
      });
      throw new Error(`Payment validation failed: ${preQuoteValidation.reason}`);
    }

    if (paymentMethod.type === 'arcblock') {
      try {
        await paymentIntent.update({ status: 'processing' });
        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))
        );

        const quoteValidation = await validateQuoteForPayment({
          checkoutSessionId: checkoutSession?.id,
          invoiceId: paymentIntent.invoice_id,
          amount: paymentIntent.amount,
        });

        if (!quoteValidation.valid) {
          if (quoteValidation.action === 'reject') {
            // Reject the payment - mark as failed
            logger.error('Payment rejected due to quote validation', {
              paymentIntentId: paymentIntent.id,
              quoteId: quoteValidation.quoteId,
              reason: quoteValidation.reason,
              txHash,
            });
            await paymentIntent.update({
              status: 'requires_action',
              last_payment_error: {
                type: 'validation_error',
                code: 'quote_validation_failed',
                message: quoteValidation.reason || 'Quote validation failed',
              },
            });
            throw new Error(`Payment validation failed: ${quoteValidation.reason}`);
          }

          if (quoteValidation.action === 'require_manual_review') {
            // Hold for manual review
            logger.error('Payment requires manual review due to expired quote', {
              paymentIntentId: paymentIntent.id,
              quoteId: quoteValidation.quoteId,
              reason: quoteValidation.reason,
              txHash,
            });

            await handleExpiredQuotePayment({
              quoteId: quoteValidation.quoteId!,
              checkoutSessionId: checkoutSession?.id,
              invoiceId: paymentIntent.invoice_id,
              paymentIntentId: paymentIntent.id,
              amount: paymentIntent.amount,
              currencyId: paymentIntent.currency_id,
              txHash,
            });

            await paymentIntent.update({
              status: 'requires_action',
              last_payment_error: {
                type: 'validation_error',
                code: 'quote_expired',
                message: quoteValidation.reason || 'Quote expired - manual review required',
              },
              metadata: {
                ...paymentIntent.metadata,
                quote_validation: {
                  status: 'expired',
                  quoteId: quoteValidation.quoteId,
                  reason: quoteValidation.reason,
                  held_for_review: true,
                },
              },
            });
            throw new Error(`Payment held for manual review: ${quoteValidation.reason}`);
          }
        }

        await paymentIntent.update({
          status: 'succeeded',
          amount_received: paymentIntent.amount,
          last_payment_error: null,
          payment_details: {
            [paymentMethod.type]: {
              tx_hash: txHash,
              payer: userDid,
              type: 'transfer',
            },
          },
        });

        await handlePaymentSucceed(paymentIntent);

        return { hash: txHash };
      } catch (err) {
        console.error(err);
        logger.error('Failed to finalize paymentIntent on arcblock', { paymentIntent: paymentIntent.id, error: err });
        await paymentIntent.update({ status: 'requires_capture' });
        throw err;
      }
    }

    if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
      try {
        await paymentIntent.update({ status: 'processing' });

        const paymentDetails = await executeEvmTransaction('transfer', userDid, claims, paymentMethod);
        waitForEvmTxConfirm(
          paymentMethod.getEvmClient(),
          Number(paymentDetails.block_height),
          paymentMethod.confirmation.block
        )
          .then(async () => {
            const quoteValidation = await validateQuoteForPayment({
              checkoutSessionId: checkoutSession?.id,
              invoiceId: paymentIntent.invoice_id,
            });

            if (!quoteValidation.valid) {
              if (quoteValidation.action === 'reject') {
                // Reject the payment - mark as failed
                logger.error('Payment rejected due to quote validation', {
                  paymentIntentId: paymentIntent.id,
                  quoteId: quoteValidation.quoteId,
                  reason: quoteValidation.reason,
                  txHash: paymentDetails.tx_hash,
                });
                await paymentIntent.update({
                  status: 'requires_action',
                  last_payment_error: {
                    type: 'validation_error',
                    code: 'quote_validation_failed',
                    message: quoteValidation.reason || 'Quote validation failed',
                  },
                });
                return;
              }

              if (quoteValidation.action === 'require_manual_review') {
                // Hold for manual review
                logger.error('Payment requires manual review due to expired quote', {
                  paymentIntentId: paymentIntent.id,
                  quoteId: quoteValidation.quoteId,
                  reason: quoteValidation.reason,
                  txHash: paymentDetails.tx_hash,
                });

                await handleExpiredQuotePayment({
                  quoteId: quoteValidation.quoteId!,
                  checkoutSessionId: checkoutSession?.id,
                  invoiceId: paymentIntent.invoice_id,
                  paymentIntentId: paymentIntent.id,
                  amount: paymentIntent.amount,
                  currencyId: paymentIntent.currency_id,
                  txHash: paymentDetails.tx_hash,
                });

                await paymentIntent.update({
                  status: 'requires_action',
                  last_payment_error: {
                    type: 'validation_error',
                    code: 'quote_expired',
                    message: quoteValidation.reason || 'Quote expired - manual review required',
                  },
                  metadata: {
                    ...paymentIntent.metadata,
                    quote_validation: {
                      status: 'expired',
                      quoteId: quoteValidation.quoteId,
                      reason: quoteValidation.reason,
                      held_for_review: true,
                    },
                  },
                });
                return;
              }
            }

            await paymentIntent.update({
              status: 'succeeded',
              amount_received: paymentIntent.amount,
              last_payment_error: null,
              payment_details: { [paymentMethod.type]: paymentDetails },
            });
            await handlePaymentSucceed(paymentIntent);
          })
          .catch(console.error);

        return { hash: paymentDetails.tx_hash };
      } catch (err) {
        console.error(err);
        logger.error(`Failed to finalize paymentIntent on ${paymentMethod.type}`, {
          paymentIntent: paymentIntent.id,
          error: err,
        });
        await paymentIntent.update({ status: 'requires_capture' });
        throw err;
      }
    }

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