const errors = require('@feathersjs/errors');
const Debug = require('debug');
const merge = require('lodash.merge');
const { NotAuthenticated } = require('@feathersjs/errors');
const debug = Debug('@feathersjs/authentication:hooks:authenticate');

module.exports = function authenticate (_strategies, options = {}) {
  if (!_strategies) {
    throw new Error(`The 'authenticate' hook requires one of your registered passport strategies.`);
  }

  const strategies = Array.isArray(_strategies) ? _strategies : [ _strategies ];

  return function (hook) {
    const app = hook.app;

    // If called internally or we are already authenticated skip
    if (!hook.params.provider || hook.params.authenticated) {
      return Promise.resolve(hook);
    }

    if (hook.type !== 'before') {
      return Promise.reject(new Error(`The 'authenticate' hook should only be used as a 'before' hook.`));
    }

    hook.data = hook.data || {};

    const strategy = hook.data.strategy || strategies[0];

    if (strategies.indexOf(strategy) === -1) {
      return Promise.reject(new NotAuthenticated(`Strategy ${strategy} is not permitted`));
    }

    // Handle the case where authenticate hook was registered without a passport strategy specified
    if (!strategy) {
      return Promise.reject(new errors.GeneralError(`You must provide an authentication 'strategy'`));
    }

    // The client must send a `strategy` name.
    if (!app.passport._strategy(strategy)) {
      return Promise.reject(new errors.BadRequest(`Authentication strategy '${strategy}' is not registered.`));
    }

    // NOTE (EK): Passport expects an express/connect
    // like request object. So we need to create one.
    let request = {
      query: hook.data,
      body: hook.data,
      params: hook.params,
      headers: hook.params.headers || {},
      cookies: hook.params.cookies || {},
      session: {}
    };

    const strategyOptions = merge({}, app.passport.options(strategy), options);

    debug(`Attempting to authenticate using ${strategy} strategy with options`, strategyOptions);

    return app.authenticate(strategy, strategyOptions)(request).then((result = {}) => {
      if (result.fail && options.allowUnauthenticated !== true) {
        // TODO (EK): Reject with something...
        // You get back result.challenge and result.status
        if (strategyOptions.failureRedirect) {
          // TODO (EK): Bypass the service?
          // hook.result = true
          Object.defineProperty(hook.data, '__redirect', { value: { status: 302, url: strategyOptions.failureRedirect } });
        }

        const { challenge, status = 401 } = result;
        let message = challenge && challenge.message ? challenge.message : challenge;

        if (strategyOptions.failureMessage) {
          message = strategyOptions.failureMessage;
        }

        return Promise.reject(new errors[status](message, challenge));
      }

      if (result.success || options.allowUnauthenticated === true) {
        hook.params = Object.assign({ authenticated: result.success }, hook.params, result.data);

        // Add the user to the original request object so it's available in the socket handler
        Object.assign(request.params, hook.params);

        if (strategyOptions.successRedirect) {
          // TODO (EK): Bypass the service?
          // hook.result = true
          Object.defineProperty(hook.data, '__redirect', { value: { status: 302, url: strategyOptions.successRedirect } });
        }
      } else if (result.redirect) {
        // TODO (EK): Bypass the service?
        // hook.result = true
        Object.defineProperty(hook.data, '__redirect', { value: { status: result.status, url: result.url } });
      }

      return Promise.resolve(hook);
    });
  };
};
