import Users from 'meteor/vulcan:users';
import { Utils, addGraphQLMutation, addGraphQLResolvers, Connectors } from 'meteor/vulcan:core';

/**
 * @summary Verify that the un/subscription can be performed
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user
 * @returns {Object} collectionName, fields: object, item, hasSubscribedItem: boolean
 */
const prepareSubscription = (action, collection, itemId, user) => {

  // get item's collection name
  const collectionName = collection._name.slice(0,1).toUpperCase() + collection._name.slice(1);

  // get item data
  const item = collection.findOne(itemId);

  // there no user logged in or no item, abort process
  if (!user || !item) {
    return false;
  }

  // edge case: Users collection
  if (collectionName === 'Users') {
    // someone can't subscribe to themself, abort process
    if (item._id === user._id) {
      return false;
    }
  } else {
    // the item's owner is the subscriber, abort process
    if (item.userId && item.userId === user._id) {
      return false;
    }
  }

  // assign the right fields depending on the collection
  const fields = {
    subscribers: 'subscribers',
    subscriberCount: 'subscriberCount',
  };

  // return true if the item has the subscriber's id in its fields
  const hasSubscribedItem = !!_.deep(item, fields.subscribers) && _.deep(item, fields.subscribers) && _.deep(item, fields.subscribers).indexOf(user._id) !== -1;

  // assign the right update operator and count depending on the action type
  const updateQuery = action === 'subscribe' ? {
    findOperator: '$ne', // where 'IT' isn't...
    updateOperator: '$addToSet', // ...add 'IT' to the array...
    updateCount: 1, // ...and log the addition +1
  } : {
    findOperator: '$eq', // where 'IT' is...
    updateOperator: '$pull', // ...remove 'IT' from the array...
    updateCount: -1, // ...and log the subtraction -1
  };

  // return the utility object to pursue
  return {
    collectionName,
    fields,
    item,
    hasSubscribedItem,
    ...updateQuery,
  };
};

/**
 * @summary Perform the un/subscription after verification: update the collection item & the user
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user: current user (xxx: legacy, to replace with this.userId)
 * @returns {Boolean}
 */
const performSubscriptionAction = (action, collection, itemId, user) => {

  // subscription preparation to verify if can pursue and give shorthand variables
  const subscription = prepareSubscription(action, collection, itemId, user);

  // Abort process if the situation matches one of these cases:
  // - subscription preparation failed (ex: no user, no item, subscriber is author's item, ... see all cases above)
  // - the action is subscribe but the user has already subscribed to this item
  // - the action is unsubscribe but the user hasn't subscribed to this item
  if (!subscription || (action === 'subscribe' && subscription.hasSubscribedItem) || (action === 'unsubscribe' && !subscription.hasSubscribedItem)) {
    throw Error(Utils.encodeIntlError({id: 'app.mutation_not_allowed', value: 'Already subscribed'}));
  }

  // shorthand for useful variables
  const { collectionName, fields, item, findOperator, updateOperator, updateCount } = subscription;

  // Perform the action, eg. operate on the item's collection
  const result = Connectors.update(collection, {
    _id: itemId,
    // if it's a subscription, find  where there are not the user (ie. findOperator = $ne), else it will be $in
    [fields.subscribers]: { [findOperator]: user._id }
  }, {
    // if it's a subscription, add a subscriber (ie. updateOperator = $addToSet), else it will be $pull
    [updateOperator]: { [fields.subscribers]: user._id },
    // if it's a subscription, the count is incremented of 1, else decremented of 1
    $inc: { [fields.subscriberCount]: updateCount },
  });

  // log the operation on the subscriber if it has succeeded
  if (result > 0) {
    // id of the item subject of the action
    let loggedItem = {
      itemId: item._id,
    };

    // in case of subscription, log also the date
    if (action === 'subscribe') {
      loggedItem = {
        ...loggedItem,
        subscribedAt: new Date()
      };
    }

    // update the user's list of subscribed items
    Users.update({
      _id: user._id
    }, {
      [updateOperator]: { [`subscribedItems.${collectionName}`]: loggedItem }
    });

    const updatedUser = Users.findOne({_id: user._id}, {fields: {_id:1, subscribedItems: 1}});
    
    return updatedUser;
  } else {
    throw Error(Utils.encodeIntlError({id: 'app.something_bad_happened'}));
  }
};

/**
 * @summary Generate mutations 'collection.subscribe' & 'collection.unsubscribe' automatically
 * @params {Array[Collections]} collections
 */
 const subscribeMutationsGenerator = (collection) => {

   // generic mutation function calling the performSubscriptionAction
   const genericMutationFunction = (collectionName, action) => {
     // return the method code
     return function(root, { documentId }, context) {
       
       // extract the current user & the relevant collection from the graphql server context
       const { currentUser, [Utils.capitalize(collectionName)]: collection } = context;
       
       // permission check
       if (!Users.canDo(context.currentUser, `${collectionName}.${action}`)) {
         throw new Error(Utils.encodeIntlError({id: 'app.noPermission'}));
       }
       
       // do the actual subscription action
       return performSubscriptionAction(action, collection, documentId, currentUser);
     };
   };

   const collectionName = collection._name;
   
   // add mutations to the schema
   addGraphQLMutation(`${collectionName}Subscribe(documentId: String): User`),
   addGraphQLMutation(`${collectionName}Unsubscribe(documentId: String): User`);
   
   // create an object of the shape expected by mutations resolvers
   addGraphQLResolvers({
     Mutation: {
       [`${collectionName}Subscribe`]: genericMutationFunction(collectionName, 'subscribe'),
       [`${collectionName}Unsubscribe`]: genericMutationFunction(collectionName, 'unsubscribe'),
     },
   });
   
   
 };

// Finally. Add the mutations to the Meteor namespace 🖖

// vulcan:users is a dependency of this package, it is alreay imported
subscribeMutationsGenerator(Users);


// note: leverage weak dependencies on packages
const Posts = Package['vulcan:posts'] ? Package['vulcan:posts'].default : null;
// check if vulcan:posts exists, if yes, add the mutations to Posts
if (!!Posts) {
  subscribeMutationsGenerator(Posts);
}

// check if vulcan:categories exists, if yes, add the mutations to Categories
const Categories = Package['vulcan:categories'] ? Package['vulcan:categories'].default : null;
if (!!Categories) {
  subscribeMutationsGenerator(Categories);
}

export default subscribeMutationsGenerator;
