define(function (require) {
  const _ = require('lodash');
  const moment = require('moment');

  // gets parsed value if given arg is a moment object
  function timeValue(val) {
    return moment.isMoment(val) ? val.valueOf() : val;
  }

  // returns a properly formatted millisecond timestamp index constraint
  function msConstraint(comparison, value) {
    return {
      [comparison]: timeValue(value),
      format: 'epoch_millis'
    };
  }

  // returns a new object with any indexes removed that do not include the
  // time field
  //
  // fixme: this really seems like a bug that needs to be fixed in
  //        elasticsearch itself, but this workaround will do for now
  function omitIndicesWithoutTimeField(indices, timeFieldName) {
    return _.pick(indices, index => index.fields[timeFieldName]);
  }

  return function CalculateIndicesFactory(Promise, es) {

    // Uses the field stats api to determine the names of indices that need to
    // be queried against that match the given pattern and fall within the
    // given time range
    function calculateIndices(pattern, timeFieldName, start, stop, sortDirection) {
      return getFieldStats(pattern, timeFieldName, start, stop)
      .then(resp => omitIndicesWithoutTimeField(resp.indices, timeFieldName))
      .then(indices => sortIndexStats(indices, timeFieldName, sortDirection));
    };

    // creates the configuration hash that must be passed to the elasticsearch
    // client
    function getFieldStats(pattern, timeFieldName, start, stop) {
      const constraints = {};
      if (start) {
        constraints.max_value = msConstraint('gte', start);
      }
      if (stop) {
        constraints.min_value = msConstraint('lte', stop);
      }

      return es.fieldStats({
        index: pattern,
        level: 'indices',
        body: {
          fields: [ timeFieldName ],
          index_constraints: {
            [timeFieldName]: constraints
          }
        }
      });
    }

    function sortIndexStats(indices, timeFieldName, sortDirection) {
      const desc = sortDirection === 'desc';
      const leader = desc ? 'max' : 'min';

      let indexDetails = _(indices).map((stats, index) => {
        const field = stats.fields[timeFieldName];

        // TODO: remove when we get to es 2.2, see elastic/elasticsearch#14404
        let min = field.min_value;
        if (typeof min === 'string') min = moment(min).valueOf();

        let max = field.max_value;
        if (typeof max === 'string') max = moment(max).valueOf();

        return { index, min, max };
      });

      if (sortDirection) {
        indexDetails = indexDetails.sortByOrder([leader], [sortDirection]);
      }

      return indexDetails.value();
    }

    return calculateIndices;
  };
});
