const {
  SUBSCRIBE_ALL_COMMENT_ADDED,
  SUBSCRIBE_ALL_COMMENT_EDITED,
  SUBSCRIBE_ALL_USER_BANNED,
  SUBSCRIBE_ALL_USER_CREATED,
  SUBSCRIBE_ALL_USER_SUSPENDED,
  SUBSCRIBE_ALL_USERNAME_APPROVED,
  SUBSCRIBE_ALL_USERNAME_CHANGED,
  SUBSCRIBE_ALL_USERNAME_FLAGGED,
  SUBSCRIBE_ALL_USERNAME_REJECTED,
  SUBSCRIBE_ALL_COMMENT_ACCEPTED,
  SUBSCRIBE_COMMENT_FLAGGED,
  SUBSCRIBE_COMMENT_REJECTED,
  SUBSCRIBE_COMMENT_RESET,
} = require('../../perms/constants');

const merge = require('lodash/merge');
const debug = require('debug')('talk:graph:setupFunctions');
const plugins = require('../../services/plugins');

const setupFunctions = {
  commentAdded: (options, args, comment, context) => {
    // Only privileged users can subscribe to all assets.
    if (
      !args.asset_id &&
      (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED))
    ) {
      return false;
    }

    // If user subscribes for statuses other than NONE and/or ACCEPTED statuses, it needs
    // special privileges.
    if (
      (!args.statuses ||
        args.statuses.some(status => !['NONE', 'ACCEPTED'].includes(status))) &&
      (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDED))
    ) {
      return false;
    }

    if (args.asset_id && comment.asset_id !== args.asset_id) {
      return false;
    }

    if (args.statuses && !args.statuses.includes(comment.status)) {
      return false;
    }

    return true;
  },
  commentEdited: (options, args, comment, context) => {
    if (
      !args.asset_id &&
      (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_EDITED))
    ) {
      return false;
    }
    return !args.asset_id || comment.asset_id === args.asset_id;
  },
  commentFlagged: (options, args, comment, context) => {
    if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_FLAGGED)) {
      return false;
    }
    return !args.asset_id || comment.asset_id === args.asset_id;
  },
  commentAccepted: (options, args, comment, context) => {
    if (args.asset_id) {
      // Allow sending back the comment for the user that has filtered based
      // on a specific asset.
      return comment.asset_id === args.asset_id;
    } else {
      // Only privileged users can subscribe to all assets.
      return context.user && context.user.can(SUBSCRIBE_ALL_COMMENT_ACCEPTED);
    }
  },
  commentRejected: (options, args, comment, context) => {
    if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_REJECTED)) {
      return false;
    }
    return !args.asset_id || comment.asset_id === args.asset_id;
  },
  commentReset: (options, args, comment, context) => {
    if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_RESET)) {
      return false;
    }
    return !args.asset_id || comment.asset_id === args.asset_id;
  },
  userSuspended: (options, args, user, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id &&
        !context.user.can(SUBSCRIBE_ALL_USER_SUSPENDED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  userBanned: (options, args, user, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id && !context.user.can(SUBSCRIBE_ALL_USER_BANNED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  usernameFlagged: (options, args, user, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id &&
        !context.user.can(SUBSCRIBE_ALL_USERNAME_FLAGGED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  usernameRejected: (options, args, user, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id &&
        !context.user.can(SUBSCRIBE_ALL_USERNAME_REJECTED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  usernameApproved: (options, args, user, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id &&
        !context.user.can(SUBSCRIBE_ALL_USERNAME_APPROVED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  usernameChanged: (options, args, { user }, context) => {
    if (
      !context.user ||
      (args.user_id !== user.id &&
        !context.user.can(SUBSCRIBE_ALL_USERNAME_CHANGED))
    ) {
      return false;
    }
    return !args.user_id || user.id === args.user_id;
  },
  userCreated: (options, args, user, ctx) =>
    ctx.user && ctx.user.can(SUBSCRIBE_ALL_USER_CREATED),
};

/**
 * Plugin support requires that we merge in existing setupFunctions with our new
 * plugin based ones. This allows plugins to extend existing setupFunctions as
 * well as provide new ones. We'll remap our internal representation of the
 * setupFunctions into the format needed by Apollo.
 */
module.exports = plugins.get('server', 'setupFunctions').reduce(
  (acc, { plugin, setupFunctions }) => {
    debug(`added plugin '${plugin.name}'`);

    return merge(acc, setupFunctions);
  },
  // Process the default setupFunctions.
  Object.entries(setupFunctions)
    .map(([key, filter]) => ({
      [key]: (options, args) => ({
        [key]: {
          filter: (user, ctx) => filter(options, args, user, ctx),
        },
      }),
    }))
    .reduce(
      (setupFunction, setupFunctions) => merge(setupFunctions, setupFunction),
      {}
    )
);
