import escapeStringRegexp from 'escape-string-regexp';
import UI from '../../config/ui/config';
import { DOC_VIEW } from '../../constants/eventTypes';
import moment from 'moment';

const REGEXP_FILTER_TYPE = 'regexpFilterType';
const EQUALITY_FILTER_TYPE = 'equalityFilterType';

/**
 * Get selecton filters combining find, sort selection filters for mongoDB query
 * @param {*} findCriteria what will be the find parameter in mongo query
 * @param {*} sortCriteria what will be the sort parameter in mongo query
 * @param {*} view current logbook view
 */
export function getSelectionFiltersForMongoQuery(
  findCriteria,
  sortCriteria,
  view
) {
  if (!findCriteria) {
    findCriteria = [];
  }
  if (!sortCriteria) {
    sortCriteria = { [UI.logbook.SORT_EVENTS_BY]: UI.logbook.SORTING_ORDER };
  }

  return {
    find: getSelectionFiltersBySearchCriteria(findCriteria, view),
    sort: sortCriteria,
  };
}

/**
 * Get the selection filter to be used in mongoDB query which returns all annotations and all notifications
 */
export function getSelectionFilterForAllAnnotationsAndNotifications() {
  const filter = {};
  const andExpressions = [];
  andExpressions.push(getNotificationOrAnnotationFilter());
  filter.$and = andExpressions;
  return filter;
}

/**
 * Get selection filters to be used in mongoDB query
 * @param {array} criteria user specified search criteria
 * @param {string} view current view of the logbook
 */
export function getSelectionFiltersBySearchCriteria(criteria, view) {
  const filter = {};
  const andExpressions = [];
  // the first part of the AND filter which selects annotation and notification systematically
  if (view === DOC_VIEW) {
    andExpressions.push({
      $or: [
        { type: 'annotation' },
        {
          $and: [
            { type: 'notification' },
            { previousVersionEvent: { $ne: null } },
          ],
        },
      ],
    });
  } else {
    andExpressions.push(getNotificationOrAnnotationFilter());
  }

  const userSpecificFilters = getUserSpecificSelectionFilters(criteria);
  if (userSpecificFilters) {
    andExpressions.push(userSpecificFilters);
  }
  filter.$and = andExpressions;
  return filter;
}

/**
 * Generate the user specific part of the selection filters from GUI specified search criteria.
 * @param {array} criteria search criteria as provided by the combosearch component. When not provided, this indicates that the user has not set any search criteria.
 * @returns {Object} user specific part of the selection filter query to be used for the mongoDB query
 */
export function getUserSpecificSelectionFilters(criteria) {
  let filters = {};
  const userSearchANDArray = [];
  if (criteria && criteria instanceof Array) {
    criteria.forEach((criterion) => {
      if (criterion.criteria && criterion.search) {
        if (criterion.criteria.toLowerCase() === 'everywhere') {
          const orFilter = [];
          let orFilterItem = popSingleFilter(
            {},
            { criteria: 'title', search: criterion.search },
            REGEXP_FILTER_TYPE
          );
          orFilter.push(orFilterItem);
          orFilterItem = popSingleFilter(
            {},
            { criteria: 'type', search: criterion.search },
            REGEXP_FILTER_TYPE
          );
          orFilter.push(orFilterItem);
          orFilterItem = popSingleFilter(
            {},
            { criteria: 'category', search: criterion.search },
            REGEXP_FILTER_TYPE
          );
          orFilter.push(orFilterItem);
          orFilterItem = popSingleFilter(
            {},
            { criteria: 'username', search: criterion.search },
            REGEXP_FILTER_TYPE
          );
          orFilter.push(orFilterItem);
          orFilterItem = popSingleFilter(
            {},
            { criteria: 'content.text', search: criterion.search },
            REGEXP_FILTER_TYPE
          );
          orFilter.push(orFilterItem);

          userSearchANDArray.push({ $or: orFilter });
        } else {
          const singleFilter = popSingleFilter(
            {},
            criterion,
            REGEXP_FILTER_TYPE
          );
          if (singleFilter) {
            userSearchANDArray.push(singleFilter);
          }
        }
      }
    });
    if (userSearchANDArray.length !== 0) {
      filters.$and = userSearchANDArray;
    } else {
      filters = null;
    }
  }

  return filters;
}

/**
 * Create the or filter which is used in all requests to get only event which type is annotation of
 * notification but not attachment.
 */
function getNotificationOrAnnotationFilter() {
  const filter = {};
  const orFilter = [];
  let orFilterItem = popSingleFilter(
    {},
    { criteria: 'type', search: 'annotation' },
    EQUALITY_FILTER_TYPE
  );
  orFilter.push(orFilterItem);
  orFilterItem = popSingleFilter(
    {},
    { criteria: 'type', search: 'notification' },
    EQUALITY_FILTER_TYPE
  );
  orFilter.push(orFilterItem);
  filter.$or = orFilter;
  return filter;
}

/**
 * pop a filter inside a array oof filters. Used for $and and $or filters
 * @param {Array} filter the array to pop the filter in
 * @param {*} criterion the criterion which is searched
 * @param {*} filterType the type of filter, 'regexpFilter' or 'equalityFilter'
 */
function popSingleFilter(filter, criterion, filterType) {
  if (criterion.criteria.toLowerCase() === 'author') {
    filter.username = regexpFilterOnSingleCriterion(criterion);
  } else if (criterion.criteria.toLowerCase() === 'creation date') {
    if (getDateFilterOnSingleCriterion(criterion)) {
      filter.creationDate = getDateFilterOnSingleCriterion(criterion);
    } else {
      return null;
    }
  } else if (criterion.criteria.toLowerCase() === 'content') {
    filter['content.text'] = regexpFilterOnSingleCriterion(criterion);
  } else {
    if (filterType === REGEXP_FILTER_TYPE) {
      filter[criterion.criteria.toLowerCase()] = regexpFilterOnSingleCriterion(
        criterion
      );
    } else if (filterType === EQUALITY_FILTER_TYPE) {
      filter = equalityFilterOnSingleCriterion(criterion);
    }
  }
  return filter;
}

/**
 * A filter used to search a single word inside text, using regexp, not dates
 * @param {*} criterion the single criterion
 */
function regexpFilterOnSingleCriterion(criterion) {
  return {
    $regex: `.*${escapeStringRegexp(criterion.search)}.*`,
    $options: 'i',
  };
}

function getDateFilterOnSingleCriterion(criterion) {
  if (criterion.momentDate) {
    if (criterion.search === 'before') {
      return {
        $lt: `${criterion.momentDate.format(
          moment.HTML5_FMT.DATETIME_LOCAL_MS
        )}Z`,
      };
    }
    if (criterion.search === 'after') {
      return {
        $gte: `${criterion.momentDate.format(
          moment.HTML5_FMT.DATETIME_LOCAL_MS
        )}Z`,
      };
    }
  }
  return null;
}

function equalityFilterOnSingleCriterion(criterion) {
  const filter = {};
  if (criterion && criterion.criteria && criterion.search) {
    filter[criterion.criteria.toLowerCase()] = criterion.search;
    return filter;
  }
}
