const _ = require('lodash');
const runner = require('./runner');

module.exports = (validationChains, message) => (req, res, next) => {
  const run = chain => runner(req, getContext(chain));

  const contexts = _.flatMap(validationChains, chain => {
    return Array.isArray(chain) ? chain.map(getContext) : getContext(chain);
  });

  const promises = validationChains.map(chain => {
    const group = Array.isArray(chain) ? chain : [chain];
    return Promise.all(group.map(run)).then(results => _.flatten(results));
  });

  return Promise.all(promises).then(results => {
    req._validationContexts = (req._validationContexts || []).concat(contexts);
    req._validationErrors = req._validationErrors || [];

    const failedGroupContexts = findFailedGroupContexts(results, validationChains);
    req._validationOneOfFailures = (req._validationOneOfFailures || []).concat(failedGroupContexts);

    const empty = results.some(result => result.length === 0);
    if (!empty) {
      req._validationErrors.push({
        param: '_error',
        msg: getDynamicMessage(message || 'Invalid value(s)', req),
        nestedErrors: _.flatten(results, true)
      });
    }

    next();
    return results;
  }).catch(next);
};

function getContext(chain) {
  return chain._context;
}

function findFailedGroupContexts(results, validationChains) {
  return _(results)
    // If the group is free of errors, the empty array plays the trick of filtering such group.
    .flatMap((result, index) => result.length > 0 ? validationChains[index] : [])
    .map(getContext)
    .value();
}

function getDynamicMessage(message, req) {
  if (typeof message !== 'function') {
    return message;
  }

  return message({ req });
}