import pick from 'lodash/pick';
import type Stripe from 'stripe';

import dayjs from '../../../libs/dayjs';
import logger from '../../../libs/logger';
import {
  finalizeStripeSubscriptionUpdate,
  getDaysUntilCancel,
  getDaysUntilDue,
  getDueUnit,
  shouldCancelSubscription,
} from '../../../libs/subscription';
import { CheckoutSession, PaymentMethod, Subscription, TEventExpanded } from '../../../store/models';
import { getCheckoutSessionSubscriptionIds } from '../../../libs/session';
import { createEvent } from '../../../libs/audit';

export async function handleStripeSubscriptionSucceed(subscription: Subscription, status: string) {
  if (!subscription.payment_details?.stripe?.subscription_id) {
    return;
  }

  // do not move to active if actual setup is not done
  const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
  const client = method!.getStripeClient();
  const result: any = await client.subscriptions.retrieve(subscription.payment_details.stripe.subscription_id, {
    expand: ['latest_invoice.payment_intent', 'pending_setup_intent'],
  });
  const checkoutSession = await CheckoutSession.findBySubscriptionId(subscription.id);
  let subscriptionIds: string[] = [subscription.id];
  if (checkoutSession) {
    subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
  }
  const isMultipleSubscriptions = subscriptionIds.length > 1 && checkoutSession?.enable_subscription_grouping;
  // if not multiple subscriptions, check if setup is done
  if (!isMultipleSubscriptions && result.pending_setup_intent && result.pending_setup_intent.status !== 'succeeded') {
    logger.warn('subscription can not active because stripe setup not done', {
      id: subscription.id,
      status: result.pending_setup_intent.status,
    });
    return;
  }
  const paymentIntent = result.latest_invoice?.payment_intent;
  const paymentIntentStatus = typeof paymentIntent === 'string' ? paymentIntent : paymentIntent?.status;
  if (result.latest_invoice?.payment_intent && paymentIntentStatus !== 'succeeded') {
    logger.warn('subscription can not active because stripe payment not done', {
      id: subscription.id,
      status: result.latest_invoice.payment_intent.status,
    });
    return;
  }

  await subscription.update({ status });
  if (subscription.trial_end && subscription.trial_end > Date.now() / 1000 && subscription.status === 'trialing') {
    createEvent('Subscription', 'customer.subscription.trial_start', subscription).catch(console.error);
  } else if (subscription.status === 'active') {
    createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
  }
  logger.info('subscription become active on stripe event', { id: subscription.id, status: subscription.status });

  if (checkoutSession && checkoutSession.status === 'open') {
    await checkoutSession.increment('success_subscription_count', { by: 1 });
    await checkoutSession.reload();
    if (
      checkoutSession.success_subscription_count &&
      checkoutSession.success_subscription_count >= subscriptionIds.length
    ) {
      await checkoutSession.update({
        status: 'complete',
        payment_status: 'paid',
        payment_details: paymentIntent?.payment_details || null,
      });
      logger.info('checkout session become complete on stripe event', {
        id: checkoutSession.id,
        subscriptionId: subscription.id,
      });
    }
  }
}

// https://stripe.com/docs/billing/subscriptions/webhooks#events
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function handleSubscriptionEvent(event: TEventExpanded, _: Stripe) {
  const localSubscriptionId = event.data.object.metadata?.id;
  if (!localSubscriptionId) {
    logger.warn('local subscription id not found in strip event', { id: event.id, type: event.type });
    return;
  }
  const subscription = await Subscription.findByPk(localSubscriptionId);
  if (!subscription) {
    logger.warn('local subscription not found', { id: event.id, type: event.type, localSubscriptionId });
    return;
  }

  logger.info('received subscription event', { id: event.id, type: event.type, localSubscriptionId });

  if (event.type === 'customer.subscription.updated') {
    if (
      event.data.previous_attributes?.status === 'incomplete' &&
      ['active', 'trialing'].includes(event.data.object.status)
    ) {
      await handleStripeSubscriptionSucceed(subscription, event.data.object.status);
      return;
    }

    const fields = ['cancel_at', 'cancel_at_period_end', 'canceled_at', 'current_period_start', 'current_period_end'];
    if (subscription.payment_settings?.payment_method_types?.includes('stripe')) {
      fields.push('pause_collection');
    }
    if (subscription.status === 'trialing' && event.data.object.status === 'active') {
      fields.push('status');
      fields.push('trial_end');
    }

    const isPaused = event.data.object.pause_collection && event.data.object.pause_collection.behavior === 'void';
    const currentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
    if (isPaused && currentMethod?.type !== 'stripe') {
      logger.info('subscription paused on stripe event and payment method is not stripe, skip update', {
        id: subscription.id,
      });
      return;
    }
    await finalizeStripeSubscriptionUpdate({
      subscription,
      updates: pick(event.data.object, fields),
      items: (event.data.object.items.data || []).map((x: any) => ({
        id: x.metadata?.id,
        price_id: x.price.metadata?.id,
        stripe_id: x.id,
        stripe_price_id: x.price.id,
        stripe_subscription_id: x.subscription,
        metadata: x.metadata,
        quantity: x.quantity,
        billing_thresholds: x.billing_thresholds,
        // discounts: x.discounts,
        // tax_rates: x.tax_rates,
      })),
    });

    logger.info('subscription updated on stripe event', {
      id: subscription.id,
      updates: pick(event.data.object, fields),
    });

    return;
  }

  if (event.type === 'customer.subscription.deleted') {
    if (['incomplete', 'incomplete_expired'].includes(subscription.status)) {
      logger.warn('subscription not ended on stripe event', { id: subscription.id });
    } else {
      await subscription.update({ status: 'canceled', ended_at: event.data.object.ended_at });
      logger.info('subscription ended on stripe event', { id: subscription.id });
    }
  }

  if (event.type === 'customer.subscription.paused') {
    if (['incomplete', 'incomplete_expired'].includes(subscription.status)) {
      logger.warn('subscription not paused on stripe event', { id: subscription.id });
    } else {
      await subscription.update({ status: 'paused', pause_collection: event.data.object.pause_collection });
      logger.info('subscription paused on stripe event', { id: subscription.id });
    }
  }
}

export async function syncStripeSubscriptionAfterRecovery(subscription: Subscription, paymentIntentId: string) {
  if (!subscription.payment_details?.stripe?.subscription_id) {
    return;
  }

  try {
    const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
    if (method && method.type === 'stripe') {
      const client = await method.getStripeClient();
      await client.subscriptions.update(subscription.payment_details.stripe.subscription_id, {
        cancel_at_period_end: false,
        cancel_at: null,
      });

      logger.info('Removed cancellation settings in Stripe after payment success', {
        subscription: subscription.id,
        stripeSubscription: subscription.payment_details.stripe.subscription_id,
        paymentIntent: paymentIntentId,
      });
    }
  } catch (err) {
    logger.error('Failed to update Stripe subscription after payment success', {
      error: err,
      subscription: subscription.id,
      paymentIntent: paymentIntentId,
    });
  }
}

export async function handleSubscriptionOnPaymentFailure(
  subscription: Subscription,
  eventType: string,
  client: Stripe
) {
  if (!subscription || subscription.isImmutable()) {
    logger.warn('Subscription is immutable or not found', { subscription: subscription.id });
    return;
  }

  const paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
  if (paymentMethod && paymentMethod.type !== 'stripe') {
    logger.info('Subscription payment method is not stripe', {
      subscription: subscription.id,
      paymentMethod: paymentMethod?.type,
    });
    return;
  }

  logger.info('start handle stripe subscription on payment failure', {
    subscription: subscription.id,
    eventType,
  });

  const now = dayjs().unix();
  const { interval } = subscription.pending_invoice_item_interval;
  const dueUnit = getDueUnit(interval);
  const cancelUpdates: { [key: string]: any } = {};
  const daysUntilCancel = getDaysUntilCancel(subscription);
  const cancelSubscription = shouldCancelSubscription(subscription);

  if (daysUntilCancel > 0) {
    cancelUpdates.cancel_at = subscription.current_period_start + daysUntilCancel * dueUnit;
  } else {
    cancelUpdates.cancel_at_period_end = true;
  }

  if (cancelSubscription) {
    await subscription.update({
      status: 'canceled',
      canceled_at: now,
      cancelation_details: {
        comment: 'exceed_current_period',
        feedback: 'other',
        reason: 'payment_failed',
      },
    });
    logger.warn(`[${eventType}] Subscription moved to canceled after payment failed`, {
      subscription: subscription.id,
    });
  } else {
    // check grace period
    const daysUntilDue = getDaysUntilDue(subscription);
    if (typeof daysUntilDue === 'number') {
      const gracePeriodStart = subscription.current_period_start;
      const graceDuration = daysUntilDue ? daysUntilDue * dueUnit : 0;

      if (gracePeriodStart + graceDuration <= now) {
        // past grace period, set to past_due
        await subscription.update({
          status: 'past_due',
          ...cancelUpdates,
          cancelation_details: {
            comment: 'past_due',
            feedback: 'other',
            reason: 'payment_failed',
          },
        });
        logger.warn(`[${eventType}] Subscription moved to past_due after payment failed`, {
          subscription: subscription.id,
        });
      }
    }
  }

  // sync to stripe
  if (subscription.payment_details?.stripe?.subscription_id && client) {
    try {
      if (paymentMethod && paymentMethod.type === 'stripe') {
        if (cancelSubscription) {
          await client.subscriptions.cancel(subscription.payment_details.stripe.subscription_id, {
            cancellation_details: {
              comment: 'exceed_current_period',
              feedback: 'other',
            },
          });
          logger.info('subscription in Stripe has canceled after payment failed', {
            subscription: subscription.id,
            stripeSubscription: subscription.payment_details.stripe.subscription_id,
            eventType,
          });
          return;
        }
        const stripeUpdates: any = {
          cancellation_details: {
            comment: 'past_due',
            feedback: 'other',
          },
        };

        if (cancelUpdates.cancel_at) {
          stripeUpdates.cancel_at = cancelUpdates.cancel_at;
        } else if (cancelUpdates.cancel_at_period_end) {
          stripeUpdates.cancel_at_period_end = true;
        }

        await client.subscriptions.update(subscription.payment_details.stripe.subscription_id, stripeUpdates);
        logger.info(`[${eventType}] Updated subscription in Stripe after payment failed`, {
          subscription: subscription.id,
          stripeSubscription: subscription.payment_details.stripe.subscription_id,
          updates: stripeUpdates,
        });
      }
    } catch (err) {
      logger.error(`[${eventType}] Failed to update subscription in Stripe`, {
        error: err,
        subscription: subscription.id,
      });
    }
  }
}
