import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
import type { CallbackArgs } from '../../libs/auth';
import { isDelegationSufficientForPayment } from '../../libs/payment';
import { getFastCheckoutAmount, getSubscriptionCreateSetup, type SlippageOptions } from '../../libs/session';
import { checkRemainingStake, onSubscriptionUpdateConnected } from '../../libs/subscription';
import { getTxMetadata } from '../../libs/util';
import { invoiceQueue } from '../../queues/invoice';
import { addSubscriptionJob, subscriptionQueue } from '../../queues/subscription';
import type { TLineItemExpanded } from '../../store/models';
import { Invoice } from '../../store/models/invoice';
import {
  ensureSubscription,
  executeOcapTransactions,
  getAuthPrincipalClaim,
  getDelegationTxClaim,
  getStakeTxClaim,
} from './shared';
import { ensureStakeInvoice } from '../../libs/invoice';
import { EVM_CHAIN_TYPES } from '../../libs/constants';
import logger from '../../libs/logger';

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

    const claimsList: any[] = [];
    // @ts-ignore
    const items = subscription!.items as TLineItemExpanded[];
    const trialing = false;
    const billingThreshold = Number(subscription!.billing_thresholds?.amount_gte || 0);

    // Calculate required amount considering slippage_config for dynamic pricing
    const slippageConfig = subscription?.slippage_config;
    let requiredAmount: string;
    if (slippageConfig?.min_acceptable_rate) {
      // Use slippage_config for precise calculation
      const slippageOptions: SlippageOptions = {
        percent: slippageConfig.percent ?? 0.5,
        minAcceptableRate: slippageConfig.min_acceptable_rate,
        currencyDecimal: paymentCurrency.decimal,
      };
      const setup = getSubscriptionCreateSetup(items, paymentCurrency.id, 0, 0, slippageOptions);
      requiredAmount = setup.amount.setup;
    } else {
      requiredAmount = await getFastCheckoutAmount({
        items,
        mode: 'subscription',
        currencyId: paymentCurrency.id,
        trialing: false,
      });
    }

    if (paymentMethod.type === 'arcblock') {
      const requiredStake = !subscription!.billing_thresholds?.no_stake;

      // 1. First check if stake is needed and if there's a valid existing stake
      let needsNewStake = false;
      let existingStakeAddress: string | undefined;
      if (requiredStake) {
        existingStakeAddress = subscription?.payment_details?.arcblock?.staking?.address;
        if (existingStakeAddress) {
          const stakeCheck = await checkRemainingStake(paymentMethod, paymentCurrency, existingStakeAddress, '1');
          needsNewStake = !stakeCheck.enough;
          logger.info('Change plan: checking existing stake', {
            subscriptionId: subscription!.id,
            existingStakeAddress,
            hasValidExistingStake: stakeCheck.enough,
            needsNewStake,
            stakeCheck,
          });
        } else {
          needsNewStake = true;
        }
      }

      // 2. Check if delegation is sufficient
      const delegation = await isDelegationSufficientForPayment({
        paymentMethod,
        paymentCurrency,
        userDid,
        amount: requiredAmount,
      });

      logger.info('Change plan: delegation check', {
        subscriptionId: subscription!.id,
        delegationSufficient: delegation.sufficient,
        needsNewStake,
        requiredStake,
      });

      // 3. Add delegation claim if needed
      // Need delegation if: delegation is not sufficient, OR we don't require stake (legacy behavior)
      if (delegation.sufficient === false || !requiredStake) {
        claimsList.push({
          signature: await getDelegationTxClaim({
            mode: 'subscription',
            userDid,
            userPk,
            nonce: `change-plan-${subscription!.id}`,
            data: getTxMetadata({ subscriptionId: subscription!.id }),
            paymentCurrency,
            paymentMethod,
            trialing,
            billingThreshold,
            items,
            slippageConfig: subscription?.slippage_config || undefined,
            // Reuse DelegateState fetched above — avoids a duplicate chain RPC on CF cold isolates
            delegateState: delegation.state,
          }),
        });
      }

      // 4. Add stake claim only if we need a new stake
      if (needsNewStake) {
        claimsList.push({
          prepareTx: await getStakeTxClaim({
            userDid,
            userPk,
            paymentCurrency,
            paymentMethod,
            items,
            subscription: subscription!,
          }),
        });
      } else if (requiredStake && existingStakeAddress) {
        logger.info('Change plan: skipping stake request, reusing existing stake', {
          subscriptionId: subscription!.id,
          existingStakeAddress,
        });
      }

      // 5. If no claims needed, this is an error - API should have prevented entering connect flow
      if (claimsList.length === 0) {
        logger.warn('Change plan: no claims needed but entered connect flow', {
          subscriptionId: subscription!.id,
          delegationSufficient: delegation.sufficient,
          needsNewStake,
          requiredStake,
        });
        throw new Error('No authorization needed for this subscription update. Please try again.');
      }

      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-plan-${subscription!.id}`,
          data: getTxMetadata({ subscriptionId: subscription!.id }),
          paymentCurrency,
          paymentMethod,
          trialing,
          billingThreshold,
          items,
          slippageConfig: subscription?.slippage_config || undefined,
        }),
      });

      return claimsList;
    }

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

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

    // For plan change, use the pending_update invoice if exists, not the latest_invoice_id
    // The plan change invoice is created in subscriptions.ts and stored in pending_update.updates.latest_invoice_id
    const pendingInvoiceId = subscription?.pending_update?.updates?.latest_invoice_id;
    let invoice = latestInvoice;
    if (pendingInvoiceId && pendingInvoiceId !== latestInvoice?.id) {
      const pendingInvoice = await Invoice.findByPk(pendingInvoiceId);
      if (pendingInvoice) {
        invoice = pendingInvoice;
        logger.info('Change plan: using pending_update invoice instead of latest_invoice', {
          subscriptionId,
          pendingInvoiceId,
          latestInvoiceId: latestInvoice?.id,
        });
      } else {
        logger.error('Change plan: pending invoice not found in database', {
          subscriptionId,
          pendingInvoiceId,
          latestInvoiceId: latestInvoice?.id,
        });
      }
    }

    // Validate invoice exists - change-plan should always have an invoice to process
    if (!invoice) {
      logger.error('Change plan: no invoice found for subscription update', {
        subscriptionId,
        pendingInvoiceId,
        latestInvoiceId: latestInvoice?.id,
        pendingUpdate: subscription?.pending_update,
      });
      throw new Error('Invoice not found for subscription plan change. Please try again.');
    }

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

    const requiredStake = !subscription!.billing_thresholds?.no_stake;

    // Check if we have a valid existing stake (which means no new staking claim was requested)
    let hasValidExistingStake = false;
    if (requiredStake && paymentMethod.type === 'arcblock') {
      const existingStakeAddress = subscription?.payment_details?.arcblock?.staking?.address;
      if (existingStakeAddress) {
        const stakeCheck = await checkRemainingStake(paymentMethod, paymentCurrency, existingStakeAddress, '1');
        hasValidExistingStake = stakeCheck.enough;
      }
    }

    // 判断是否为最后一步
    // Final step if: we have a staking claim, OR stake is not required, OR we're reusing existing stake
    const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
    const isFinalStep =
      (paymentMethod.type === 'arcblock' && (staking || !requiredStake || hasValidExistingStake)) ||
      paymentMethod.type !== 'arcblock';

    if (!isFinalStep) {
      await updateSession({
        result,
      });
      return { result };
    }

    // 清理 session
    try {
      await updateSession({
        result: [],
      });
    } catch (error) {
      logger.error('updateSession', {
        error,
      });
    }
    const claimsList = result.map((x: any) => x.claim);

    const prepareTxExecution = async () => {
      await subscription?.update({
        payment_settings: {
          payment_method_types: [paymentMethod.type],
          payment_method_options: {
            [paymentMethod.type]: { payer: userDid },
          },
        },
      });
    };

    const afterTxExecution = async (paymentDetails: any) => {
      await subscription?.update({ payment_details: { [paymentMethod.type]: paymentDetails } });

      if (invoice) {
        // Update invoice status and payment_settings before processing
        const invoiceUpdates: any = {
          payment_settings: {
            payment_method_types: [paymentMethod.type],
            payment_method_options: {
              [paymentMethod.type]: { payer: userDid },
            },
          },
        };
        if (invoice.status === 'uncollectible' || invoice.status === 'draft') {
          invoiceUpdates.status = 'open';
        }
        await invoice.update(invoiceUpdates);
        logger.info('Change plan: invoice updated before queue processing', {
          invoiceId: invoice.id,
          status: invoiceUpdates.status || invoice.status,
          paymentMethodType: paymentMethod.type,
        });

        await invoiceQueue.pushAndWait({
          id: invoice.id,
          job: { invoiceId: invoice.id, retryOnError: false, waitForPayment: true },
        });

        // Reload invoice to check processing result
        await invoice.reload();
        logger.info('Change plan: invoice queue processing completed', {
          invoiceId: invoice.id,
          status: invoice.status,
          paymentIntentId: invoice.payment_intent_id,
          paid: invoice.paid,
        });

        if (!invoice.payment_intent_id && invoice.amount_remaining !== '0') {
          logger.error('Change plan: invoice processed but no paymentIntent created', {
            invoiceId: invoice.id,
            status: invoice.status,
            amountRemaining: invoice.amount_remaining,
          });
        }
      }
      if (subscription) {
        await onSubscriptionUpdateConnected(subscriptionId);
        await subscriptionQueue.delete(subscription.id);
        await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
      }
    };

    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,
        subscription?.id,
        paymentCurrency?.contract
      );
      await ensureStakeInvoice(
        {
          total: stakingAmount,
          description: 'Stake for subscription plan change',
          currency_id: paymentCurrency.id,
          metadata: {
            payment_details: {
              arcblock: {
                tx_hash: paymentDetails?.staking?.tx_hash,
                payer: paymentDetails?.payer,
                address: paymentDetails?.staking?.address,
              },
            },
          },
        },
        subscription!,
        paymentMethod,
        customer!
      );
      await afterTxExecution(paymentDetails);

      return { hash: paymentDetails.tx_hash };
    }

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

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