'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const path = require('path');
const fs = require('fs');
const url = require('url');
const _ = require('lodash');
const mongoose = require('mongoose');
require('mongoose-long')(mongoose);
const Mongoose = mongoose.Mongoose;

const relations = require('./relations');
const buildQuery = require('./buildQuery');
const getQueryParams = require('./get-query-params');
const mountModels = require('./mount-models');
const queries = require('./queries');

/**
 * Mongoose hook
 */

const defaults = {
  defaultConnection: 'default',
  host: 'localhost',
  port: 27017,
  database: 'strapi',
  authenticationDatabase: '',
  ssl: false,
  debug: false,
};

const isMongooseConnection = ({ connector }) =>
  connector === 'strapi-hook-mongoose';

module.exports = function(strapi) {
  function initialize() {
    const { connections } = strapi.config;

    const connectionsPromises = Object.keys(connections)
      .filter(key => isMongooseConnection(connections[key]))
      .map(async connectionName => {
        const connection = connections[connectionName];
        const instance = new Mongoose();

        _.defaults(connection.settings, strapi.config.hook.settings.mongoose);

        const {
          uri,
          host,
          port,
          username,
          password,
          database,
          srv,
        } = connection.settings;

        const uriOptions = uri ? url.parse(uri, true).query : {};
        const { authenticationDatabase, ssl, debug } = _.defaults(
          connection.options,
          uriOptions,
          strapi.config.hook.settings.mongoose
        );
        const isSrv = srv === true || srv === 'true';

        // Connect to mongo database
        const connectOptions = {};

        if (!_.isEmpty(username)) {
          connectOptions.user = username;

          if (!_.isEmpty(password)) {
            connectOptions.pass = password;
          }
        }

        if (!_.isEmpty(authenticationDatabase)) {
          connectOptions.authSource = authenticationDatabase;
        }

        connectOptions.ssl = ssl === true || ssl === 'true';
        connectOptions.useNewUrlParser = true;
        connectOptions.dbName = database;
        connectOptions.useCreateIndex = true;

        try {
          /* FIXME: for now, mongoose doesn't support srv auth except the way including user/pass in URI.
           * https://github.com/Automattic/mongoose/issues/6881 */
          await instance.connect(
            uri ||
              `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${
                !isSrv ? ':' + port : ''
              }/`,
            connectOptions
          );
        } catch (error) {
          const err = new Error(
            `Error connecting to the Mongo database. ${error.message}`
          );
          delete err.stack;
          throw err;
        }

        const initFunctionPath = path.resolve(
          strapi.config.appPath,
          'config',
          'functions',
          'mongoose.js'
        );

        if (fs.existsSync(initFunctionPath)) {
          require(initFunctionPath)(instance, connection);
        }

        instance.set('debug', debug === true || debug === 'true');
        instance.set('useFindAndModify', false);

        const ctx = {
          instance,
          connection,
        };

        return Promise.all([
          mountGroups(connectionName, ctx),
          mountApis(connectionName, ctx),
          mountAdmin(connectionName, ctx),
          mountPlugins(connectionName, ctx),
        ]);
      });

    return Promise.all(connectionsPromises);
  }

  function mountGroups(connectionName, ctx) {
    const options = {
      models: _.pickBy(
        strapi.groups,
        ({ connection }) => connection === connectionName
      ),
      target: strapi.groups,
      plugin: false,
    };

    return mountModels(options, ctx);
  }

  function mountApis(connectionName, ctx) {
    const options = {
      models: _.pickBy(
        strapi.models,
        ({ connection }) => connection === connectionName
      ),
      target: strapi.models,
      plugin: false,
    };

    return mountModels(options, ctx);
  }

  function mountAdmin(connectionName, ctx) {
    const options = {
      models: _.pickBy(
        strapi.admin.models,
        ({ connection }) => connection === connectionName
      ),
      target: strapi.admin.models,
      plugin: false,
    };

    return mountModels(options, ctx);
  }

  function mountPlugins(connectionName, ctx) {
    return Promise.all(
      Object.keys(strapi.plugins).map(name => {
        const plugin = strapi.plugins[name];
        return mountModels(
          {
            models: _.pickBy(
              plugin.models,
              ({ connection }) => connection === connectionName
            ),
            target: plugin.models,
            plugin: name,
          },
          ctx
        );
      })
    );
  }

  return {
    defaults,
    initialize,
    getQueryParams,
    buildQuery,
    queries,
    ...relations,
  };
};

@@ -1379,16 +1379,45 @@
    srv,%0A
+          useUnifiedTopology%0A
         
@@ -2315,16 +2315,91 @@
  = true;
+%0A        connectOptions.useUnifiedTopology = useUnifiedTopology %7C%7C 'false';
 %0A%0A      

Connect to mongo db 4.0.x using mongoose 5.7.0