/* eslint-disable no-await-in-loop */
import { Op } from 'sequelize';

import { mintNftForCheckoutSession } from '../integrations/arcblock/nft';
import { ensurePassportIssued } from '../integrations/blocklet/passport';
import { ensureInvoiceForCheckout } from '../routes/connect/shared';
import dayjs from '../libs/dayjs';
import { events } from '../libs/event';
import logger from '../libs/logger';
import createQueue from '../libs/queue';
import {
  CheckoutSession,
  Customer,
  Invoice,
  InvoiceItem,
  PaymentIntent,
  PaymentMethod,
  Price,
  SetupIntent,
  Subscription,
  SubscriptionItem,
} from '../store/models';
import { getCheckoutSessionSubscriptionIds } from '../libs/session';
import { rollbackDiscountUsageForCheckoutSession } from '../libs/discount/discount';
import { destroyExistingInvoice } from '../libs/invoice';

type CheckoutSessionJob = {
  id: string;
  action: 'expire';
};

export const checkoutSessionQueue = createQueue<CheckoutSessionJob>({
  name: 'checkoutSession',
  onJob: handleCheckoutSessionJob,
  options: {
    concurrency: 10,
    maxRetries: 3,
    enableScheduledJob: true,
  },
});

export async function handleCheckoutSessionJob(job: CheckoutSessionJob): Promise<void> {
  const checkoutSession = await CheckoutSession.findByPk(job.id);
  if (!checkoutSession) {
    logger.warn('CheckoutSession not found', { id: job.id });
    return;
  }
  if (job.action === 'expire') {
    if (checkoutSession.status !== 'open') {
      logger.info('Skip expire CheckoutSession since status is not open', {
        checkoutSession: checkoutSession.id,
        status: checkoutSession.status,
      });
      return;
    }
    if (checkoutSession.payment_status === 'paid') {
      logger.info('Skip expire CheckoutSession since payment status is paid', {
        checkoutSession: checkoutSession.id,
        paymentIntent: checkoutSession.payment_intent_id,
      });
      return;
    }

    const now = dayjs().unix();
    if (checkoutSession.expires_at > now) {
      checkoutSessionQueue.push({
        id: checkoutSession.id,
        job: { id: checkoutSession.id, action: 'expire' },
        runAt: checkoutSession.expires_at,
      });
      return;
    }

    await checkoutSession.update({ status: 'expired' });
    logger.info('CheckoutSession expired', { checkoutSession: checkoutSession.id });
  }
}

// eslint-disable-next-line require-await
export async function startCheckoutSessionQueue() {
  // Auto populate subscription queue
  const now = dayjs().unix();
  const checkoutSessions = await CheckoutSession.findAll({
    where: {
      status: 'open',
      expires_at: { [Op.lte]: now },
    },
  });

  checkoutSessions.forEach(async (checkoutSession) => {
    const exist = await checkoutSessionQueue.get(checkoutSession.id);
    if (!exist) {
      checkoutSessionQueue.push({
        id: checkoutSession.id,
        job: { id: checkoutSession.id, action: 'expire' },
        runAt: checkoutSession.expires_at,
      });
    }
  });
}

checkoutSessionQueue.on('failed', ({ id, job, error }) => {
  logger.error('CheckoutSession job failed', { id, job, error });
});

events.on('checkout.session.completed', (checkoutSession: CheckoutSession) => {
  ensurePassportIssued(checkoutSession).catch((err) => {
    logger.error('ensurePassportIssued failed', { error: err, checkoutSession: checkoutSession.id });
  });
  mintNftForCheckoutSession(checkoutSession.id).catch((err) => {
    logger.error('mintNftForCheckoutSession failed', { error: err, checkoutSession: checkoutSession.id });
  });

  // lock prices used
  Price.update(
    { locked: true },
    { where: { id: checkoutSession.line_items.map((x) => x.upsell_price_id || x.price_id) } }
  ).catch((err) => {
    logger.error('lock price on checkout session complete failed', {
      error: err,
      checkoutSession: checkoutSession.id,
    });
  });
});

events.on('checkout.session.created', (checkoutSession: CheckoutSession) => {
  if (checkoutSession.expires_at) {
    checkoutSessionQueue.push({
      id: checkoutSession.id,
      job: { id: checkoutSession.id, action: 'expire' },
      runAt: checkoutSession.expires_at,
    });
  }
});

events.on('checkout.session.expired', async (checkoutSession: CheckoutSession) => {
  logger.info('Start cleanup on checkoutSession expired', { checkoutSession: checkoutSession.id });

  // Do some cleanup
  if (checkoutSession.invoice_id) {
    await InvoiceItem.destroy({ where: { invoice_id: checkoutSession.invoice_id } });
    await Invoice.destroy({ where: { id: checkoutSession.invoice_id } });
    logger.info('Invoice and InvoiceItem for checkout session deleted on expire', {
      checkoutSession: checkoutSession.id,
      invoice: checkoutSession.invoice_id,
    });
  } else {
    // Do some reverse lookup if invoice is not related to checkout session
    const invoice = await Invoice.findOne({ where: { checkout_session_id: checkoutSession.id } });
    if (invoice) {
      await destroyExistingInvoice(invoice);
      logger.info('Invoice and InvoiceItem for checkout session deleted on expire', {
        checkoutSession: checkoutSession.id,
        invoice: invoice.id,
      });
    }
  }

  if (checkoutSession.setup_intent_id) {
    await SetupIntent.destroy({ where: { id: checkoutSession.setup_intent_id } });
    logger.info('SetupIntent for checkout session deleted on expire', {
      checkoutSession: checkoutSession.id,
      setupIntent: checkoutSession.setup_intent_id,
    });
  }

  if (checkoutSession.payment_intent_id && checkoutSession.payment_status !== 'paid') {
    const paymentIntent = await PaymentIntent.findByPk(checkoutSession.payment_intent_id);
    const stripePaymentId = paymentIntent?.payment_details?.stripe?.payment_intent_id;
    if (paymentIntent && stripePaymentId) {
      const method = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
      if (method?.type === 'stripe') {
        const client = method.getStripeClient();
        try {
          await client.paymentIntents.cancel(stripePaymentId, { cancellation_reason: 'abandoned' });
          logger.info('Stripe PaymentIntent for checkout session canceled on expire', {
            checkoutSession: checkoutSession.id,
            paymentIntent: checkoutSession.payment_intent_id,
            stripePayment: stripePaymentId,
          });
        } catch (err) {
          logger.error('Stripe PaymentIntent for checkout session cancel failed on expire', {
            checkoutSession: checkoutSession.id,
            paymentIntent: checkoutSession.payment_intent_id,
            stripePayment: stripePaymentId,
            error: err,
          });
        }
      }
    }

    await PaymentIntent.destroy({ where: { id: checkoutSession.payment_intent_id } });
    logger.info('PaymentIntent for checkout session deleted on expire', {
      checkoutSession: checkoutSession.id,
      paymentIntent: checkoutSession.payment_intent_id,
    });
  }

  const subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
  if (subscriptionIds.length > 0) {
    const subscriptions = await Subscription.findAll({ where: { id: { [Op.in]: subscriptionIds } } });
    await Promise.all(
      subscriptions.map(async (subscription) => {
        const stripeSubscriptionId = subscription?.payment_details?.stripe?.subscription_id;
        if (subscription && stripeSubscriptionId) {
          const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
          if (method?.type === 'stripe') {
            const client = method.getStripeClient();
            try {
              await client.subscriptions.cancel(stripeSubscriptionId, {
                prorate: false,
                invoice_now: false,
                cancellation_details: {
                  comment: 'checkout_session_expired',
                  feedback: 'unused',
                },
              });
              logger.info('Stripe Subscription for checkout session canceled on expire', {
                checkoutSession: checkoutSession.id,
                subscription: checkoutSession.subscription_id,
                stripeSubscription: stripeSubscriptionId,
              });
            } catch (err) {
              logger.error('Stripe Subscription for checkout session cancel failed on expire', {
                checkoutSession: checkoutSession.id,
                subscription: checkoutSession.subscription_id,
                stripeSubscription: stripeSubscriptionId,
                error: err,
              });
            }
          }
        }

        await SubscriptionItem.destroy({ where: { subscription_id: subscription.id } });
        await Subscription.destroy({ where: { id: subscription.id } });
        logger.info('Subscription and SubscriptionItem for checkout session deleted on expire', {
          checkoutSession: checkoutSession.id,
          subscription: subscription.id,
        });
      })
    );
  }

  await rollbackDiscountUsageForCheckoutSession(checkoutSession.id);

  // update price lock status
  for (const item of checkoutSession.line_items) {
    const price = await Price.findByPk(item.price_id);
    if (price?.locked) {
      const used = await price.isUsed(false);
      logger.info('Price used status recheck on expire', {
        checkoutSession: checkoutSession.id,
        priceId: item.price_id,
        used,
      });
      if (!used) {
        await price.update({ locked: false });
        logger.info('Price for checkout session unlocked on expire', {
          checkoutSession: checkoutSession.id,
          priceId: item.price_id,
        });
      }
    }
  }

  logger.info('Done cleanup on checkoutSession expired', { checkoutSession: checkoutSession.id });
});

events.on(
  'checkout.session.pending_invoice',
  async ({ checkoutSessionId, paymentIntentId }: { checkoutSessionId: string; paymentIntentId: string }) => {
    const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
    if (!checkoutSession) {
      logger.warn('CheckoutSession not found for pending invoice', { checkoutSessionId });
      return;
    }
    if (checkoutSession.invoice_id) {
      logger.info('Invoice already exists for checkout session', {
        checkoutSessionId,
        invoiceId: checkoutSession.invoice_id,
      });
      return;
    }
    if (checkoutSession.mode !== 'payment') {
      logger.info('Skipping invoice creation for non-payment mode', {
        checkoutSessionId,
        mode: checkoutSession.mode,
      });
      return;
    }
    if (!checkoutSession.invoice_creation?.enabled) {
      logger.info('Invoice creation not enabled for checkout session', { checkoutSessionId });
      return;
    }

    const paymentIntent = await PaymentIntent.findByPk(paymentIntentId);
    if (!paymentIntent) {
      return;
    }

    const customer = await Customer.findByPk(checkoutSession.customer_id);
    if (!customer) {
      return;
    }

    await ensureInvoiceForCheckout({
      checkoutSession,
      paymentIntent,
      customer,
      props: {
        status: 'paid',
        amount_due: '0',
        amount_paid: checkoutSession.amount_total,
        amount_remaining: '0',
      },
    });
  }
);
