import type { CallbackArgs } from '../../libs/auth';
import { isSubscriptionOverdraftProtectionEnabled } from '../../libs/subscription';
import { Lock, TLineItemExpanded } from '../../store/models';
import {
  ensureSubscriptionForOverdraftProtection,
  executeOcapTransactions,
  getAuthPrincipalClaim,
  getOverdraftProtectionStakeTxClaim,
} from './shared';
import { ensureStakeInvoice } from '../../libs/invoice';

export default {
  action: 'overdraft-protection',
  authPrincipal: false,
  claims: {
    authPrincipal: async ({ extraParams }: CallbackArgs) => {
      const { paymentMethod } = await ensureSubscriptionForOverdraftProtection(
        extraParams.subscriptionId,
        extraParams.amount
      );
      return getAuthPrincipalClaim(paymentMethod, 'continue');
    },
  },
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
    const { subscriptionId } = extraParams;
    const { subscription, paymentMethod, paymentCurrency, stakeAmount } =
      await ensureSubscriptionForOverdraftProtection(subscriptionId, extraParams.amount);
    const { remaining } = await isSubscriptionOverdraftProtectionEnabled(subscription);

    const payerAddress = subscription.overdraft_protection?.payment_details?.arcblock?.payer;
    if (userDid !== payerAddress && remaining !== '0') {
      // if previous account has remaining overdraft protection, we don't allow to use new account to pay for it
      throw new Error(
        `You are not the payer for this subscription. Expected payer: ${payerAddress}, but found: ${userDid}.`
      );
    }

    // @ts-ignore
    const items = subscription!.items as TLineItemExpanded[];
    const claims: { [type: string]: [string, object] } = {};

    if (paymentMethod.type === 'arcblock') {
      // we always need to stake for the subscription
      claims.staking = [
        'prepareTx',
        await getOverdraftProtectionStakeTxClaim({
          userDid,
          userPk,
          paymentCurrency,
          paymentMethod,
          stakeAmount,
          subscription,
          items,
        }),
      ];

      return claims;
    }

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

  onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
    const { subscriptionId, amount } = extraParams;
    const { subscription, paymentCurrency, paymentMethod, customer } = await ensureSubscriptionForOverdraftProtection(
      subscriptionId,
      amount
    );

    const afterTxExecution = async (paymentDetails: any) => {
      await subscription?.update({
        overdraft_protection: {
          payment_details: {
            ...(subscription.overdraft_protection?.payment_details || {}),
            [paymentMethod.type]: paymentDetails,
          },
          enabled: subscription.overdraft_protection?.enabled || false,
          payment_method_id: paymentMethod.id,
        },
      });

      // release the exhausted lock, so that the notification can be sent again if overdraft protection exhausted
      await Lock.release(`${subscription.id}-${paymentCurrency.id}-overdraft-protection-exhausted`);
    };

    if (paymentMethod.type === 'arcblock') {
      const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
        userDid,
        userPk,
        claims,
        paymentMethod,
        request,
        subscription?.id,
        paymentCurrency.contract,
        `overdraft-protection-${subscription.id}`
      );
      await ensureStakeInvoice(
        {
          total: stakingAmount,
          description: 'Stake for subscription overdraft protection',
          currency_id: paymentCurrency.id,
          billing_reason: 'stake_overdraft_protection',
          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 };
    }

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