import type Stripe from 'stripe';
import { Op } from 'sequelize';

import logger from '../../../libs/logger';
import {
  AutoRechargeConfig,
  CheckoutSession,
  Customer,
  Invoice,
  Lock,
  PaymentCurrency,
  PaymentMethod,
  SetupIntent,
  Subscription,
  TEventExpanded,
} from '../../../store/models';
import { updateGroupSubscriptionsPaymentMethod, updateAutoRechargeConfigPaymentMethod } from '../resource';
import { getCheckoutSessionSubscriptionIds } from '../../../libs/session';

async function handleSubscriptionOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const subscription = await Subscription.findOne({
    where: { 'payment_details.stripe.setup_intent_id': stripeIntentId },
  });

  if (!subscription) {
    logger.warn('local subscription not found for setup intent', { id: event.id, type: event.type, stripeIntentId });
    return;
  }

  if (event.type === 'setup_intent.succeeded') {
    if (subscription.status === 'incomplete') {
      await subscription.start();
      logger.info('subscription become active on stripe intent succeeded', subscription.id);
    }

    const checkoutSession = await CheckoutSession.findBySubscriptionId(subscription.id);
    if (checkoutSession && checkoutSession.status === 'open') {
      const subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
      if (subscriptionIds.length <= 1) {
        const updates: Partial<CheckoutSession> = {
          status: 'complete',
          payment_status: 'no_payment_required',
        };
        if (checkoutSession.subscription_id) {
          updates.success_subscription_count = 1;
        }
        await checkoutSession.update(updates);
        logger.info('checkout session become complete on stripe intent succeeded', checkoutSession.id);
      }
    }

    return;
  }

  if (event.type === 'setup_intent.canceled') {
    // FIXME:
  }

  if (event.type === 'setup_intent.payment_failed') {
    // FIXME:
  }
}

async function handleSetupIntentOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const setupIntent = await SetupIntent.findOne({
    where: { 'setup_details.stripe.setup_intent_id': stripeIntentId },
  });

  if (!setupIntent) {
    logger.warn('local subscription not found for setup intent', { id: event.id, type: event.type, stripeIntentId });
    return;
  }

  if (event.type === 'setup_intent.succeeded') {
    setupIntent.update({
      status: 'succeeded',
      last_setup_error: null,
      payment_method_types: ['stripe'],
    });

    if (
      setupIntent.metadata?.subscription_id &&
      setupIntent.metadata?.from_currency &&
      setupIntent.metadata?.to_currency
    ) {
      const subscription = await Subscription.findByPk(setupIntent.metadata.subscription_id);
      if (subscription) {
        await subscription.update({
          currency_id: setupIntent.currency_id,
          default_payment_method_id: setupIntent.payment_method_id,
          payment_settings: {
            payment_method_types: ['stripe'],
            payment_method_options: {},
          },
        });
        // lock the subscription to prevent concurrent change plan
        await Lock.acquire(`${subscription.id}-change-plan`, subscription.current_period_end);
        logger.info('subscription payment changed to stripe on stripe setup intent succeeded', {
          subscriptionId: subscription.id,
          setupIntentId: setupIntent.id,
          stripeIntentId,
        });
      }
    }
  }
}

async function handleCheckoutSessionOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const checkoutSession = await CheckoutSession.findOne({
    where: { 'payment_details.stripe.setup_intent_id': stripeIntentId },
  });
  if (!checkoutSession) {
    logger.warn('local checkout session not found for setup intent', {
      id: event.id,
      type: event.type,
      stripeIntentId,
    });
    return;
  }

  logger.info('received setup intent event', { id: event.id, data: event.data, stripeIntentId, checkoutSession });

  if (event.type === 'setup_intent.succeeded') {
    const stripePaymentMethod = event.data.object.payment_method;
    if (stripePaymentMethod && checkoutSession.subscription_groups) {
      await updateGroupSubscriptionsPaymentMethod({
        stripePaymentMethodId: stripePaymentMethod,
        subscriptionIds: Object.values(checkoutSession.subscription_groups),
      });
    }
  }
}

async function handleAutoRechargeOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const autoRechargeConfig = await AutoRechargeConfig.findOne({
    where: { 'payment_details.stripe.setup_intent_id': stripeIntentId },
  });
  if (!autoRechargeConfig) {
    logger.warn('local auto recharge config not found for setup intent', {
      id: event.id,
      type: event.type,
      stripeIntentId,
    });
    return;
  }

  if (event.type === 'setup_intent.succeeded') {
    const stripePaymentMethod = event.data.object.payment_method;
    if (stripePaymentMethod) {
      await updateAutoRechargeConfigPaymentMethod({
        stripePaymentMethodId: stripePaymentMethod,
        autoRechargeConfig,
      });
    }
  }
}

async function handleUpdateStripePaymentMethodOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const { metadata } = event.data.object;

  if (metadata?.action !== 'update_payment_method' || !metadata?.subscription_id) {
    return;
  }

  if (event.type !== 'setup_intent.succeeded') {
    return;
  }

  const subscription = await Subscription.findByPk(metadata.subscription_id);
  if (!subscription) {
    logger.warn('subscription not found for update payment method', {
      id: event.id,
      stripeIntentId,
      subscriptionId: metadata.subscription_id,
    });
    return;
  }

  const paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
  if (!paymentMethod || paymentMethod.type !== 'stripe') {
    logger.warn('stripe payment method not found for subscription', {
      id: event.id,
      stripeIntentId,
      subscriptionId: subscription.id,
    });
    return;
  }

  const stripeSubscriptionId = subscription.payment_details?.stripe?.subscription_id;
  if (!stripeSubscriptionId) {
    logger.warn('stripe subscription id not found', {
      id: event.id,
      stripeIntentId,
      subscriptionId: subscription.id,
    });
    return;
  }

  const client = paymentMethod.getStripeClient();
  const stripePaymentMethodId = event.data.object.payment_method as string;

  await client.subscriptions.update(stripeSubscriptionId, {
    default_payment_method: stripePaymentMethodId,
  });

  logger.info('stripe payment method updated via webhook', {
    subscriptionId: subscription.id,
    stripeIntentId,
    paymentMethod: stripePaymentMethodId,
  });
}

async function handleBatchOverduePaymentOnSetupSucceeded(event: TEventExpanded, stripeIntentId: string) {
  const { metadata } = event.data.object;

  if (metadata?.action !== 'pay_overdue_batch' || !metadata?.currency_id) {
    return;
  }

  if (event.type !== 'setup_intent.succeeded') {
    return;
  }

  const paymentCurrency = await PaymentCurrency.findByPk(metadata.currency_id);
  if (!paymentCurrency) {
    logger.warn('payment currency not found for batch overdue payment', {
      id: event.id,
      stripeIntentId,
      currencyId: metadata.currency_id,
    });
    return;
  }

  const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
  if (!paymentMethod || paymentMethod.type !== 'stripe') {
    logger.warn('stripe payment method not found or invalid type', {
      id: event.id,
      stripeIntentId,
      currencyId: metadata.currency_id,
      paymentMethodType: paymentMethod?.type,
    });
    return;
  }

  let invoices: Invoice[];

  if (metadata.invoices) {
    let invoiceIds: string[];
    try {
      invoiceIds = JSON.parse(metadata.invoices);
    } catch (err) {
      logger.error('failed to parse invoices from setup intent metadata', {
        id: event.id,
        stripeIntentId,
        metadata,
      });
      return;
    }

    invoices = await Invoice.findAll({
      where: {
        id: { [Op.in]: invoiceIds },
        currency_id: metadata.currency_id,
      },
    });

    const subscriptionIds = [...new Set(invoices.map((inv) => inv.subscription_id).filter(Boolean) as string[])];

    if (subscriptionIds.length > 0) {
      const additionalInvoices = await Invoice.findAll({
        where: {
          subscription_id: { [Op.in]: subscriptionIds },
          currency_id: metadata.currency_id,
          status: { [Op.in]: ['open', 'uncollectible'] },
          id: { [Op.notIn]: invoiceIds },
        },
      });

      invoices = [...invoices, ...additionalInvoices];

      logger.info('found additional overdue invoices for subscriptions', {
        stripeIntentId,
        subscriptionIds,
        originalCount: invoiceIds.length,
        additionalCount: additionalInvoices.length,
        totalCount: invoices.length,
      });
    }
  } else if (metadata.subscription_id) {
    invoices = await Invoice.findAll({
      where: {
        subscription_id: metadata.subscription_id,
        currency_id: metadata.currency_id,
        status: { [Op.in]: ['open', 'uncollectible'] },
      },
    });
  } else if (metadata.customer_id) {
    const customer = await Customer.findByPkOrDid(metadata.customer_id);
    if (!customer) {
      logger.warn('customer not found for batch overdue payment', {
        id: event.id,
        stripeIntentId,
        customerId: metadata.customer_id,
      });
      return;
    }

    invoices = await Invoice.findAll({
      where: {
        customer_id: customer.id,
        currency_id: metadata.currency_id,
        status: { [Op.in]: ['open', 'uncollectible'] },
      },
    });
  } else {
    logger.error('invalid metadata for batch overdue payment, must provide invoices, subscription_id or customer_id', {
      id: event.id,
      stripeIntentId,
      metadata,
    });
    return;
  }

  if (invoices.length === 0) {
    logger.warn('no invoices found for batch overdue payment', {
      id: event.id,
      stripeIntentId,
      metadata,
    });
    return;
  }

  const validInvoices = invoices.filter((inv) => inv.metadata?.stripe_id);
  if (validInvoices.length === 0) {
    logger.warn('no valid stripe invoices found for batch overdue payment', {
      id: event.id,
      stripeIntentId,
      totalInvoices: invoices.length,
    });
    return;
  }

  const client = paymentMethod.getStripeClient();
  const stripePaymentMethodId = event.data.object.payment_method as string;

  const payResults = await Promise.all(
    validInvoices.map(async (invoice) => {
      try {
        await client.invoices.pay(invoice.metadata!.stripe_id, {
          payment_method: stripePaymentMethodId,
        });

        logger.info('stripe invoice payment initiated via webhook', {
          invoiceId: invoice.id,
          stripeInvoiceId: invoice.metadata!.stripe_id,
          stripeIntentId,
        });

        return { invoiceId: invoice.id, success: true };
      } catch (err) {
        logger.error('failed to pay stripe invoice via webhook', {
          invoiceId: invoice.id,
          stripeInvoiceId: invoice.metadata!.stripe_id,
          stripeIntentId,
          error: err,
        });
        return { invoiceId: invoice.id, success: false, error: err.message };
      }
    })
  );

  logger.info('batch overdue payment completed via webhook', {
    stripeIntentId,
    currencyId: metadata.currency_id,
    totalInvoices: invoices.length,
    validInvoices: validInvoices.length,
    succeeded: payResults.filter((r) => r.success).length,
    failed: payResults.filter((r) => !r.success).length,
  });
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function handleSetupIntentEvent(event: TEventExpanded, _: Stripe) {
  const stripeIntentId = event.data.object.id;
  logger.info('received setup intent event', { id: event.id, type: event.type, stripeIntentId });

  await handleSubscriptionOnSetupSucceeded(event, stripeIntentId);
  await handleSetupIntentOnSetupSucceeded(event, stripeIntentId);
  await handleCheckoutSessionOnSetupSucceeded(event, stripeIntentId);
  await handleAutoRechargeOnSetupSucceeded(event, stripeIntentId);
  await handleUpdateStripePaymentMethodOnSetupSucceeded(event, stripeIntentId);
  await handleBatchOverduePaymentOnSetupSucceeded(event, stripeIntentId);
}
