text: import moment from 'moment';
  import Users from '../../models/users';
  import Boards from '../../models/boards';
  import Lists from '../../models/lists';
  import Swimlanes from '../../models/swimlanes';
  import Cards from '../../models/cards';
  import CardComments from '../../models/cardComments';
  import Attachments from '../../models/attachments';
  import Checklists from '../../models/checklists';
  import ChecklistItems from '../../models/checklistItems';
  import SessionData from '../../models/usersessiondata';
  import CustomFields from '../../models/customFields';
  import {
    DEFAULT_LIMIT,
    OPERATOR_ASSIGNEE,
    OPERATOR_BOARD,
    OPERATOR_COMMENT,
    OPERATOR_DUE,
    OPERATOR_HAS,
    OPERATOR_LABEL,
    OPERATOR_LIMIT,
    OPERATOR_LIST,
    OPERATOR_MEMBER,
    OPERATOR_SORT,
    OPERATOR_STATUS,
    OPERATOR_SWIMLANE,
    OPERATOR_USER,
    ORDER_ASCENDING,
    PREDICATE_ALL,
    PREDICATE_ARCHIVED,
    PREDICATE_ASSIGNEES,
    PREDICATE_ATTACHMENT,
    PREDICATE_CHECKLIST,
    PREDICATE_CREATED_AT,
    PREDICATE_DESCRIPTION,
    PREDICATE_DUE_AT,
    PREDICATE_END_AT,
    PREDICATE_ENDED,
    PREDICATE_MEMBERS,
    PREDICATE_MODIFIED_AT,
    PREDICATE_PRIVATE,
    PREDICATE_PUBLIC,
    PREDICATE_START_AT,
    PREDICATE_SYSTEM,
  } from '../../config/search-const';
  import { QueryErrors, QueryParams, Query } from '../../config/query-classes';
  
  const escapeForRegex = require('escape-string-regexp');
  
  Meteor.publish('card', cardId => {
    check(cardId, String);
    return Cards.find({ _id: cardId });
  });
  
  Meteor.publish('myCards', function(sessionId) {
    const queryParams = new QueryParams();
    queryParams.addPredicate(OPERATOR_USER, Meteor.user().username);
  
    return findCards(sessionId, buildQuery(queryParams));
  });
  
  // Meteor.publish('dueCards', function(sessionId, allUsers = false) {
  //   check(sessionId, String);
  //   check(allUsers, Boolean);
  //
  //   // eslint-disable-next-line no-console
  //   // console.log('all users:', allUsers);
  //
  //   const queryParams = {
  //     has: [{ field: 'dueAt', exists: true }],
  //     limit: 25,
  //     skip: 0,
  //     sort: { name: 'dueAt', order: 'des' },
  //   };
  //
  //   if (!allUsers) {
  //     queryParams.users = [Meteor.user().username];
  //   }
  //
  //   return buildQuery(sessionId, queryParams);
  // });
  
  Meteor.publish('globalSearch', function(sessionId, params, text) {
    check(sessionId, String);
    check(params, Object);
    check(text, String);
  
    // eslint-disable-next-line no-console
    // console.log('queryParams:', params);
  
    return findCards(sessionId, buildQuery(new QueryParams(params, text)));
  });
  
  function buildSelector(queryParams) {
    const userId = Meteor.userId();
  
    const errors = new QueryErrors();
  
    let selector = {};
  
    // eslint-disable-next-line no-console
    // console.log('queryParams:', queryParams);
  
    if (queryParams.selector) {
      selector = queryParams.selector;
    } else {
      const boardsSelector = {};
  
      let archived = false;
      let endAt = null;
      if (queryParams.hasOperator(OPERATOR_STATUS)) {
        queryParams.getPredicates(OPERATOR_STATUS).forEach(status => {
          if (status === PREDICATE_ARCHIVED) {
            archived = true;
          } else if (status === PREDICATE_ALL) {
            archived = null;
          } else if (status === PREDICATE_ENDED) {
            endAt = { $nin: [null, ''] };
          } else if ([PREDICATE_PRIVATE, PREDICATE_PUBLIC].includes(status)) {
            boardsSelector.permission = status;
          }
        });
      }
      selector = {
        type: 'cardType-card',
        // boardId: { $in: Boards.userBoardIds(userId) },
        $and: [],
      };
  
      if (archived !== null) {
        if (archived) {
          selector.boardId = {
            $in: Boards.userBoardIds(userId, null, boardsSelector),
          };
          selector.$and.push({
            $or: [
              {
                boardId: {
                  $in: Boards.userBoardIds(userId, archived, boardsSelector),
                },
              },
              { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
              { listId: { $in: Lists.archivedListIds() } },
              { archived: true },
            ],
          });
        } else {
          selector.boardId = {
            $in: Boards.userBoardIds(userId, false, boardsSelector),
          };
          selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
          selector.listId = { $nin: Lists.archivedListIds() };
          selector.archived = false;
        }
      } else {
        selector.boardId = {
          $in: Boards.userBoardIds(userId, null, boardsSelector),
        };
      }
      if (endAt !== null) {
        selector.endAt = endAt;
      }
  
      if (queryParams.hasOperator(OPERATOR_BOARD)) {
        const queryBoards = [];
        queryParams.hasOperator(OPERATOR_BOARD).forEach(query => {
          const boards = Boards.userSearch(userId, {
            title: new RegExp(escapeForRegex(query), 'i'),
          });
          if (boards.count()) {
            boards.forEach(board => {
              queryBoards.push(board._id);
            });
          } else {
            errors.addNotFound(OPERATOR_BOARD, query);
          }
        });
  
        selector.boardId.$in = queryBoards;
      }
  
      if (queryParams.hasOperator(OPERATOR_SWIMLANE)) {
        const querySwimlanes = [];
        queryParams.getPredicates(OPERATOR_SWIMLANE).forEach(query => {
          const swimlanes = Swimlanes.find({
            title: new RegExp(escapeForRegex(query), 'i'),
          });
          if (swimlanes.count()) {
            swimlanes.forEach(swim => {
              querySwimlanes.push(swim._id);
            });
          } else {
            errors.addNotFound(OPERATOR_SWIMLANE, query);
          }
        });
  
        // eslint-disable-next-line no-prototype-builtins
        if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
          selector.swimlaneId = { $in: [] };
        }
        selector.swimlaneId.$in = querySwimlanes;
      }
  
      if (queryParams.hasOperator(OPERATOR_LIST)) {
        const queryLists = [];
        queryParams.getPredicates(OPERATOR_LIST).forEach(query => {
          const lists = Lists.find({
            title: new RegExp(escapeForRegex(query), 'i'),
          });
          if (lists.count()) {
            lists.forEach(list => {
              queryLists.push(list._id);
            });
          } else {
            errors.addNotFound(OPERATOR_LIST, query);
          }
        });
  
        // eslint-disable-next-line no-prototype-builtins
        if (!selector.hasOwnProperty('listId')) {
          selector.listId = { $in: [] };
        }
        selector.listId.$in = queryLists;
      }
  
      if (queryParams.hasOperator(OPERATOR_COMMENT)) {
        const cardIds = CardComments.textSearch(
          userId,
          queryParams.getPredicates(OPERATOR_COMMENT),
          com => {
            return com.cardId;
          },
        );
        if (cardIds.length) {
          selector._id = { $in: cardIds };
        } else {
          queryParams.getPredicates(OPERATOR_COMMENT).forEach(comment => {
            errors.addNotFound(OPERATOR_COMMENT, comment);
          });
        }
      }
  
      [OPERATOR_DUE, 'createdAt', 'modifiedAt'].forEach(field => {
        if (queryParams.hasOperator(field)) {
          selector[field] = {};
          const predicate = queryParams.getPredicate(field);
          selector[field][predicate.operator] = new Date(predicate.value);
        }
      });
  
      const queryUsers = {};
      queryUsers[OPERATOR_ASSIGNEE] = [];
      queryUsers[OPERATOR_MEMBER] = [];
  
      if (queryParams.hasOperator(OPERATOR_USER)) {
        queryParams.getPredicates(OPERATOR_USER).forEach(username => {
          const user = Users.findOne({ username });
          if (user) {
            queryUsers[OPERATOR_MEMBER].push(user._id);
            queryUsers[OPERATOR_ASSIGNEE].push(user._id);
          } else {
            errors.addNotFound(OPERATOR_USER, username);
          }
        });
      }
  
      [OPERATOR_MEMBER, OPERATOR_ASSIGNEE].forEach(key => {
        if (queryParams.hasOperator(key)) {
          queryParams.getPredicates(key).forEach(query => {
            const users = Users.find({
              username: query,
            });
            if (users.count()) {
              users.forEach(user => {
                queryUsers[key].push(user._id);
              });
            } else {
              errors.addNotFound(key, query);
            }
          });
        }
      });
  
      if (
        queryUsers[OPERATOR_MEMBER].length &&
        queryUsers[OPERATOR_ASSIGNEE].length
      ) {
        selector.$and.push({
          $or: [
            { members: { $in: queryUsers[OPERATOR_MEMBER] } },
            { assignees: { $in: queryUsers[OPERATOR_ASSIGNEE] } },
          ],
        });
      } else if (queryUsers[OPERATOR_MEMBER].length) {
        selector.members = { $in: queryUsers[OPERATOR_MEMBER] };
      } else if (queryUsers[OPERATOR_ASSIGNEE].length) {
        selector.assignees = { $in: queryUsers[OPERATOR_ASSIGNEE] };
      }
  
      if (queryParams.hasOperator(OPERATOR_LABEL)) {
        queryParams.getPredicates(OPERATOR_LABEL).forEach(label => {
          const queryLabels = [];
  
          let boards = Boards.userSearch(userId, {
            labels: { $elemMatch: { color: label.toLowerCase() } },
          });
  
          if (boards.count()) {
            boards.forEach(board => {
              // eslint-disable-next-line no-console
              // console.log('board:', board);
              // eslint-disable-next-line no-console
              // console.log('board.labels:', board.labels);
              board.labels
                .filter(boardLabel => {
                  return boardLabel.color === label.toLowerCase();
                })
                .forEach(boardLabel => {
                  queryLabels.push(boardLabel._id);
                });
            });
          } else {
            // eslint-disable-next-line no-console
            // console.log('label:', label);
            const reLabel = new RegExp(escapeForRegex(label), 'i');
            // eslint-disable-next-line no-console
            // console.log('reLabel:', reLabel);
            boards = Boards.userSearch(userId, {
              labels: { $elemMatch: { name: reLabel } },
            });
  
            if (boards.count()) {
              boards.forEach(board => {
                board.labels
                  .filter(boardLabel => {
                    if (!boardLabel.name) {
                      return false;
                    }
                    return boardLabel.name.match(reLabel);
                  })
                  .forEach(boardLabel => {
                    queryLabels.push(boardLabel._id);
                  });
              });
            } else {
              errors.addNotFound(OPERATOR_LABEL, label);
            }
          }
  
          selector.labelIds = { $in: _.uniq(queryLabels) };
        });
      }
  
      if (queryParams.hasOperator(OPERATOR_HAS)) {
        queryParams.getPredicates(OPERATOR_HAS).forEach(has => {
          switch (has.field) {
            case PREDICATE_ATTACHMENT:
              selector.$and.push({
                _id: {
                  $in: Attachments.find({}, { fields: { cardId: 1 } }).map(
                    a => a.cardId,
                  ),
                },
              });
              break;
            case PREDICATE_CHECKLIST:
              selector.$and.push({
                _id: {
                  $in: Checklists.find({}, { fields: { cardId: 1 } }).map(
                    a => a.cardId,
                  ),
                },
              });
              break;
            case PREDICATE_DESCRIPTION:
            case PREDICATE_START_AT:
            case PREDICATE_DUE_AT:
            case PREDICATE_END_AT:
              if (has.exists) {
                selector[has.field] = { $exists: true, $nin: [null, ''] };
              } else {
                selector[has.field] = { $in: [null, ''] };
              }
              break;
            case PREDICATE_ASSIGNEES:
            case PREDICATE_MEMBERS:
              if (has.exists) {
                selector[has.field] = { $exists: true, $nin: [null, []] };
              } else {
                selector[has.field] = { $in: [null, []] };
              }
              break;
          }
        });
      }
  
      if (queryParams.text) {
        const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
  
        const items = ChecklistItems.find(
          { title: regex },
          { fields: { cardId: 1 } },
        );
        const checklists = Checklists.find(
          {
            $or: [
              { title: regex },
              { _id: { $in: items.map(item => item.checklistId) } },
            ],
          },
          { fields: { cardId: 1 } },
        );
  
        const attachments = Attachments.find({ 'original.name': regex });
  
        const comments = CardComments.find(
          { text: regex },
          { fields: { cardId: 1 } },
        );
  
        selector.$and.push({
          $or: [
            { title: regex },
            { description: regex },
            { customFields: { $elemMatch: { value: regex } } },
            // {
            //   _id: {
            //     $in: CardComments.textSearch(userId, [queryParams.text]).map(
            //       com => com.cardId,
            //     ),
            //   },
            // },
            { _id: { $in: checklists.map(list => list.cardId) } },
            { _id: { $in: attachments.map(attach => attach.cardId) } },
            { _id: { $in: comments.map(com => com.cardId) } },
          ],
        });
      }
  
      if (selector.$and.length === 0) {
        delete selector.$and;
      }
    }
  
    // eslint-disable-next-line no-console
    // console.log('selector:', selector);
    // eslint-disable-next-line no-console
    // console.log('selector.$and:', selector.$and);
  
    const query = new Query();
    query.selector = selector;
    query.params = queryParams;
    query._errors = errors;
  
    return query;
  }
  
  function buildProjection(query) {
    let skip = 0;
    if (query.params.skip) {
      skip = query.params.skip;
    }
    let limit = DEFAULT_LIMIT;
    const configLimit = parseInt(process.env.RESULTS_PER_PAGE, 10);
    if (!isNaN(configLimit) && configLimit > 0) {
      limit = configLimit;
    }
  
    if (query.params.hasOperator(OPERATOR_LIMIT)) {
      limit = query.params.getPredicate(OPERATOR_LIMIT);
    }
  
    const projection = {
      fields: {
        _id: 1,
        archived: 1,
        boardId: 1,
        swimlaneId: 1,
        listId: 1,
        title: 1,
        type: 1,
        sort: 1,
        members: 1,
        assignees: 1,
        colors: 1,
        dueAt: 1,
        createdAt: 1,
        modifiedAt: 1,
        labelIds: 1,
        customFields: 1,
      },
      sort: {
        boardId: 1,
        swimlaneId: 1,
        listId: 1,
        sort: 1,
      },
      skip,
      limit,
    };
  
    if (query.params.hasOperator(OPERATOR_SORT)) {
      const order =
        query.params.getPredicate(OPERATOR_SORT).order === ORDER_ASCENDING
          ? 1
          : -1;
      switch (query.params.getPredicate(OPERATOR_SORT).name) {
        case PREDICATE_DUE_AT:
          projection.sort = {
            dueAt: order,
            boardId: 1,
            swimlaneId: 1,
            listId: 1,
            sort: 1,
          };
          break;
        case PREDICATE_MODIFIED_AT:
          projection.sort = {
            modifiedAt: order,
            boardId: 1,
            swimlaneId: 1,
            listId: 1,
            sort: 1,
          };
          break;
        case PREDICATE_CREATED_AT:
          projection.sort = {
            createdAt: order,
            boardId: 1,
            swimlaneId: 1,
            listId: 1,
            sort: 1,
          };
          break;
        case PREDICATE_SYSTEM:
          projection.sort = {
            boardId: order,
            swimlaneId: order,
            listId: order,
            modifiedAt: order,
            sort: order,
          };
          break;
      }
    }
  
    // eslint-disable-next-line no-console
    // console.log('projection:', projection);
  
    query.projection = projection;
  
    return query;
  }
  
  function buildQuery(queryParams) {
    const query = buildSelector(queryParams);
  
    return buildProjection(query);
  }
  
  Meteor.publish('brokenCards', function(sessionId) {
    check(sessionId, String);
  
    const params = new QueryParams();
    params.addPredicate(OPERATOR_STATUS, PREDICATE_ALL);
    const query = buildQuery(params);
    query.selector.$or = [
      { boardId: { $in: [null, ''] } },
      { swimlaneId: { $in: [null, ''] } },
      { listId: { $in: [null, ''] } },
    ];
    // console.log('brokenCards selector:', query.selector);
  
    return findCards(sessionId, query);
  });
  
  Meteor.publish('nextPage', function(sessionId) {
    check(sessionId, String);
  
    const session = SessionData.findOne({ sessionId });
    const projection = session.getProjection();
    projection.skip = session.lastHit;
  
    return findCards(sessionId, new Query(session.getSelector(), projection));
  });
  
  Meteor.publish('previousPage', function(sessionId) {
    check(sessionId, String);
  
    const session = SessionData.findOne({ sessionId });
    const projection = session.getProjection();
    projection.skip = session.lastHit - session.resultsCount - projection.limit;
  
    return findCards(sessionId, new Query(session.getSelector(), projection));
  });
  
  function findCards(sessionId, query) {
    const userId = Meteor.userId();
  
    // eslint-disable-next-line no-console
    // console.log('selector:', query.selector);
    // console.log('selector.$and:', query.selector.$and);
    // eslint-disable-next-line no-console
    // console.log('projection:', projection);
    let cards;
    if (!query.hasErrors()) {
      cards = Cards.find(query.selector, query.projection);
    }
    // eslint-disable-next-line no-console
    // console.log('count:', cards.count());
  
    const update = {
      $set: {
        totalHits: 0,
        lastHit: 0,
        resultsCount: 0,
        cards: [],
        selector: SessionData.pickle(query.selector),
        projection: SessionData.pickle(query.projection),
        errors: query.errors(),
      },
    };
  
    if (cards) {
      update.$set.totalHits = cards.count();
      update.$set.lastHit =
        query.projection.skip + query.projection.limit < cards.count()
          ? query.projection.skip + query.projection.limit
          : cards.count();
      update.$set.cards = cards.map(card => {
        return card._id;
      });
      update.$set.resultsCount = update.$set.cards.length;
    }
  
    SessionData.upsert({ userId, sessionId }, update);
  
    // remove old session data
    SessionData.remove({
      userId,
      modifiedAt: {
        $lt: new Date(
          moment()
            .subtract(1, 'day')
            .format(),
        ),
      },
    });
  
    if (cards) {
      const boards = [];
      const swimlanes = [];
      const lists = [];
      const customFieldIds = [];
      const users = [this.userId];
  
      cards.forEach(card => {
        if (card.boardId) boards.push(card.boardId);
        if (card.swimlaneId) swimlanes.push(card.swimlaneId);
        if (card.listId) lists.push(card.listId);
        if (card.members) {
          card.members.forEach(userId => {
            users.push(userId);
          });
        }
        if (card.assignees) {
          card.assignees.forEach(userId => {
            users.push(userId);
          });
        }
        if (card.customFields) {
          card.customFields.forEach(field => {
            customFieldIds.push(field._id);
          });
        }
      });
  
      const fields = {
        _id: 1,
        title: 1,
        archived: 1,
        sort: 1,
        type: 1,
      };
  
      return [
        cards,
        Boards.find(
          { _id: { $in: boards } },
          { fields: { ...fields, labels: 1, color: 1 } },
        ),
        Swimlanes.find(
          { _id: { $in: swimlanes } },
          { fields: { ...fields, color: 1 } },
        ),
        Lists.find({ _id: { $in: lists } }, { fields }),
        CustomFields.find({ _id: { $in: customFieldIds } }),
        Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
        Checklists.find({ cardId: { $in: cards.map(c => c._id) } }),
        Attachments.find({ cardId: { $in: cards.map(c => c._id) } }),
        CardComments.find({ cardId: { $in: cards.map(c => c._id) } }),
        SessionData.find({ userId, sessionId }),
      ];
    }
  
    return [SessionData.find({ userId, sessionId })];
  }
  