import nconf from 'nconf';
import moment from 'moment';

import logger from '../../logger';
import { model as User } from '../../../models/user'; // eslint-disable-line import/no-cycle
import { getStripeApi } from './api';
import {
  BadRequest,
  NotFound,
} from '../../errors';
import payments from '../payments'; // eslint-disable-line import/no-cycle
import { // eslint-disable-line import/no-cycle
  model as Group,
  basicFields as basicGroupFields,
} from '../../../models/group';
import shared from '../../../../common';
import { applyGemPayment, applySku } from './oneTimePayments'; // eslint-disable-line import/no-cycle
import { applySubscription, handlePaymentMethodChange } from './subscriptions'; // eslint-disable-line import/no-cycle

const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');

const BASE_URL = nconf.get('BASE_URL');

export async function handleWebhooks (options, stripeInc) {
  const { body, headers } = options;

  // @TODO: We need to mock this, but curently we don't have correct
  // Dependency Injection. And the Stripe Api doesn't seem to be a singleton?
  let stripeApi = getStripeApi();
  if (stripeInc) stripeApi = stripeInc;

  let event;

  try {
    // Verify the event by fetching it from Stripe
    event = stripeApi.webhooks.constructEvent(body, headers['stripe-signature'], endpointSecret);
  } catch (err) {
    logger.error(new Error('Error verifying Stripe webhook'), { err });
    throw new BadRequest(`Webhook Error: ${err.message}`);
  }

  try {
    switch (event.type) {
      case 'payment_intent.created':
      case 'payment_intent.succeeded':
      case 'payment_intent.payment_failed':
      case 'setup_intent.created':
      case 'setup_intent.succeeded':
      case 'charge.succeeded':
      case 'charge.failed':
      case 'payment_method.attached':
      case 'customer.created':
      case 'customer.updated':
      case 'customer.deleted':
      case 'customer.subscription.created':
      case 'customer.subscription.updated':
      case 'invoiceitem.created':
      case 'invoice.created':
      case 'invoice.updated':
      case 'invoice.finalized':
      case 'invoice.paid':
      case 'invoice.upcoming':
      case 'invoice.payment_succeeded': {
        // Events sent even if not active in the Stripe dashboard when a payment is being made
        // This is to avoid error logs from the webhook handler not being implemented
        break;
      }
      case 'checkout.session.completed': {
        const session = event.data.object;
        const { metadata } = session;

        if (metadata.server_url !== BASE_URL) {
          break;
        }

        if (metadata.type === 'edit-card-group' || metadata.type === 'edit-card-user') {
          await handlePaymentMethodChange(session);
        } else if (metadata.type === 'subscription') {
          await applySubscription(session);
        } else if (metadata.type === 'sku') {
          await applySku(session);
        } else {
          await applyGemPayment(session);
        }

        break;
      }
      case 'customer.subscription.deleted': {
        // event.request !== null means that the user itself cancelled the subscrioption,
        // the cancellation on our side has been already handled
        if (event.request && event.request.id !== null) break;

        const subscription = event.data.object;
        const customerId = subscription.customer;
        const isGroupSub = shared.content.subscriptionBlocks[subscription.plan.id].target === 'group';

        let user;
        let groupId;

        if (isGroupSub) {
          const groupFields = basicGroupFields.concat(' purchased');
          const group = await Group.findOne({
            'purchased.plan.customerId': customerId,
            'purchased.plan.paymentMethod': this.constants.PAYMENT_METHOD,
          }).select(groupFields).exec();

          if (!group) throw new NotFound(shared.i18n.t('groupNotFound'));
          groupId = group._id;

          user = await User.findById(group.leader).exec();
        } else {
          user = await User.findOne({
            'purchased.plan.customerId': customerId,
            'purchased.plan.paymentMethod': this.constants.PAYMENT_METHOD,
          }).exec();
        }

        if (!user) throw new NotFound(shared.i18n.t('userNotFound'));

        await stripeApi.customers.del(customerId);

        await payments.cancelSubscription({
          user,
          groupId,
          paymentMethod: this.constants.PAYMENT_METHOD,
          // Give three extra days to allow the user to resubscribe without losing benefits
          nextBill: moment().add({ days: 3 }).toDate(),
        });

        break;
      }
      default: {
        throw new BadRequest(`Missing handler for Stripe webhook ${event.type}`);
      }
    }
  } catch (err) {
    logger.error(new Error('Error handling Stripe webhook'), { event, err });
    throw err;
  }
}
