import { ReactiveCache } from '/imports/reactiveCache';
import escapeForRegex from 'escape-string-regexp';
import DOMPurify from 'dompurify';

// Server-side text sanitization function
function sanitizeText(text) {
  if (typeof text !== 'string') return text;
  // Strip HTML tags and return only text content
  return text.replace(/<[^>]*>/g, '');
}

CardComments = new Mongo.Collection('card_comments');

/**
 * A comment on a card
 */
CardComments.attachSchema(
  new SimpleSchema({
    boardId: {
      /**
       * the board ID
       */
      type: String,
    },
    cardId: {
      /**
       * the card ID
       */
      type: String,
    },
    // XXX Rename in `content`? `text` is a bit vague...
    text: {
      /**
       * the text of the comment
       */
      type: String,
    },
    createdAt: {
      /**
       * when was the comment created
       */
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert) {
          return new Date();
        } else if (this.isUpsert) {
          return { $setOnInsert: new Date() };
        } else {
          this.unset();
        }
      },
    },
    modifiedAt: {
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert || this.isUpsert || this.isUpdate) {
          return new Date();
        } else {
          this.unset();
        }
      },
    },
    // XXX Should probably be called `authorId`
    userId: {
      /**
       * the author ID of the comment
       */
      type: String,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert && !this.isSet) {
          return this.userId;
        }
      },
    },
  }),
);

CardComments.allow({
  insert(userId, doc) {
    return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
  },
  update(userId, doc) {
    return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
  },
  remove(userId, doc) {
    return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
  },
  fetch: ['userId', 'boardId'],
});

CardComments.helpers({
  copy(newCardId) {
    this.cardId = newCardId;
    delete this._id;
    CardComments.insert(this);
  },

  user() {
    return ReactiveCache.getUser(this.userId);
  },

  reactions() {
    const cardCommentReactions = ReactiveCache.getCardCommentReaction({cardCommentId: this._id});
    return !!cardCommentReactions ? cardCommentReactions.reactions : [];
  },

  toggleReaction(reactionCodepoint) {
    if (reactionCodepoint !== sanitizeText(reactionCodepoint)) {
      return false;
    } else {

      const cardCommentReactions = ReactiveCache.getCardCommentReaction({cardCommentId: this._id});
      const reactions = !!cardCommentReactions ? cardCommentReactions.reactions : [];
      const userId = Meteor.userId();
      const reaction = reactions.find(r => r.reactionCodepoint === reactionCodepoint);

      // If no reaction is set for the codepoint, add this
      if (!reaction) {
        reactions.push({ reactionCodepoint, userIds: [userId] });
      } else {

        // toggle user reaction upon previous reaction state
        const userHasReacted = reaction.userIds.includes(userId);
        if (userHasReacted) {
          reaction.userIds.splice(reaction.userIds.indexOf(userId), 1);
          if (reaction.userIds.length === 0) {
            reactions.splice(reactions.indexOf(reaction), 1);
          }
        } else {
          reaction.userIds.push(userId);
        }
      }

      // If no reaction doc exists yet create otherwise update reaction set
      if (!!cardCommentReactions) {
        return CardCommentReactions.update({ _id: cardCommentReactions._id }, { $set: { reactions } });
      } else {
        return CardCommentReactions.insert({
          boardId: this.boardId,
          cardCommentId: this._id,
          cardId: this.cardId,
          reactions
        });
      }
    }
  }
});

CardComments.hookOptions.after.update = { fetchPrevious: false };

function commentCreation(userId, doc) {
  const card = ReactiveCache.getCard(doc.cardId);
  Activities.insert({
    userId,
    activityType: 'addComment',
    boardId: doc.boardId,
    cardId: doc.cardId,
    commentId: doc._id,
    listId: card.listId,
    swimlaneId: card.swimlaneId,
  });
}

CardComments.textSearch = (userId, textArray) => {
  const selector = {
    boardId: { $in: Boards.userBoardIds(userId) },
    $and: [],
  };

  for (const text of textArray) {
    selector.$and.push({ text: new RegExp(escapeForRegex(text), 'i') });
  }

  // eslint-disable-next-line no-console
  // console.log('cardComments selector:', selector);

  const comments = ReactiveCache.getCardComments(selector);
  // eslint-disable-next-line no-console
  // console.log('count:', comments.count());
  // eslint-disable-next-line no-console
  // console.log('cards with comments:', comments.map(com => { return com.cardId }));

  return comments;
};

if (Meteor.isServer) {
  // Comments are often fetched within a card, so we create an index to make these
  // queries more efficient.
  Meteor.startup(() => {
    CardComments._collection.createIndex({ modifiedAt: -1 });
    CardComments._collection.createIndex({ cardId: 1, createdAt: -1 });
  });

  CardComments.after.insert((userId, doc) => {
    commentCreation(userId, doc);
  });

  CardComments.after.update((userId, doc) => {
    const card = ReactiveCache.getCard(doc.cardId);
    Activities.insert({
      userId,
      activityType: 'editComment',
      boardId: doc.boardId,
      cardId: doc.cardId,
      commentId: doc._id,
      listId: card.listId,
      swimlaneId: card.swimlaneId,
    });
  });

  CardComments.before.remove((userId, doc) => {
    const card = ReactiveCache.getCard(doc.cardId);
    Activities.insert({
      userId,
      activityType: 'deleteComment',
      boardId: doc.boardId,
      cardId: doc.cardId,
      commentId: doc._id,
      listId: card.listId,
      swimlaneId: card.swimlaneId,
    });
    const activity = ReactiveCache.getActivity({ commentId: doc._id });
    if (activity) {
      Activities.remove(activity._id);
    }
  });
}

//CARD COMMENT REST API
if (Meteor.isServer) {
  /**
   * @operation get_all_comments
   * @summary Get all comments attached to a card
   *
   * @param {string} boardId the board ID of the card
   * @param {string} cardId the ID of the card
   * @return_type [{_id: string,
   *                comment: string,
   *                authorId: string}]
   */
  JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (
    req,
    res,
  ) {
    try {
      const paramBoardId = req.params.boardId;
      const paramCardId = req.params.cardId;
      Authentication.checkBoardAccess(req.userId, paramBoardId);
      JsonRoutes.sendResult(res, {
        code: 200,
        data: ReactiveCache.getCardComments({
          boardId: paramBoardId,
          cardId: paramCardId,
        }).map(function (doc) {
          return {
            _id: doc._id,
            comment: doc.text,
            authorId: doc.userId,
          };
        }),
      });
    } catch (error) {
      JsonRoutes.sendResult(res, {
        code: 200,
        data: error,
      });
    }
  });

  /**
   * @operation get_comment
   * @summary Get a comment on a card
   *
   * @param {string} boardId the board ID of the card
   * @param {string} cardId the ID of the card
   * @param {string} commentId the ID of the comment to retrieve
   * @return_type CardComments
   */
  JsonRoutes.add(
    'GET',
    '/api/boards/:boardId/cards/:cardId/comments/:commentId',
    function (req, res) {
      try {
        const paramBoardId = req.params.boardId;
        const paramCommentId = req.params.commentId;
        const paramCardId = req.params.cardId;
        Authentication.checkBoardAccess(req.userId, paramBoardId);
        JsonRoutes.sendResult(res, {
          code: 200,
          data: ReactiveCache.getCardComment({
            _id: paramCommentId,
            cardId: paramCardId,
            boardId: paramBoardId,
          }),
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    },
  );

  /**
   * @operation new_comment
   * @summary Add a comment on a card
   *
   * @param {string} boardId the board ID of the card
   * @param {string} cardId the ID of the card
   * @param {string} authorId the user who 'posted' the comment
   * @param {string} text the content of the comment
   * @return_type {_id: string}
   */
  JsonRoutes.add(
    'POST',
    '/api/boards/:boardId/cards/:cardId/comments',
    function (req, res) {
      try {
        const paramBoardId = req.params.boardId;
        const paramCardId = req.params.cardId;
        Authentication.checkBoardAccess(req.userId, paramBoardId);
        const id = CardComments.direct.insert({
          userId: req.body.authorId,
          text: req.body.comment,
          cardId: paramCardId,
          boardId: paramBoardId,
        });

        JsonRoutes.sendResult(res, {
          code: 200,
          data: {
            _id: id,
          },
        });

        const cardComment = ReactiveCache.getCardComment({
          _id: id,
          cardId: paramCardId,
          boardId: paramBoardId,
        });
        commentCreation(req.body.authorId, cardComment);
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    },
  );

  /**
   * @operation delete_comment
   * @summary Delete a comment on a card
   *
   * @param {string} boardId the board ID of the card
   * @param {string} cardId the ID of the card
   * @param {string} commentId the ID of the comment to delete
   * @return_type {_id: string}
   */
  JsonRoutes.add(
    'DELETE',
    '/api/boards/:boardId/cards/:cardId/comments/:commentId',
    function (req, res) {
      try {
        const paramBoardId = req.params.boardId;
        const paramCommentId = req.params.commentId;
        const paramCardId = req.params.cardId;
        Authentication.checkBoardAccess(req.userId, paramBoardId);
        CardComments.remove({
          _id: paramCommentId,
          cardId: paramCardId,
          boardId: paramBoardId,
        });
        JsonRoutes.sendResult(res, {
          code: 200,
          data: {
            _id: paramCardId,
          },
        });
      } catch (error) {
        JsonRoutes.sendResult(res, {
          code: 200,
          data: error,
        });
      }
    },
  );
}

export default CardComments;
