'use strict';

/**
 * Module dependencies
 */

// Core
const path = require('path');
const fs = require('fs');
// Public node modules.
const _ = require('lodash');
const bookshelf = require('bookshelf');

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

/**
 * Bookshelf hook
 */

/**
 * Default options
 */

const defaults = {
  defaultConnection: 'default',
  host: 'localhost',
};

const isBookshelfConnection = ({ connector }) => connector === 'bookshelf';

module.exports = function(strapi) {
  const { connections } = strapi.config;
  const bookshelfConnections = Object.keys(connections).filter(key =>
    isBookshelfConnection(connections[key])
  );

  function initialize() {
    initKnex(strapi);

    const GLOBALS = {};

    const connectionsPromises = bookshelfConnections.map(connectionName => {
      const connection = connections[connectionName];

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

      // Create Bookshelf instance for this connection.
      const ORM = new bookshelf(strapi.connections[connectionName]);

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

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

      const ctx = {
        GLOBALS,
        connection,
        ORM,
      };

      return mountConnection(connectionName, ctx);
    });

    return Promise.all(connectionsPromises);
  }

  async function mountConnection(connectionName, ctx) {
    if (strapi.models['core_store'].connection === connectionName) {
      await mountCoreStore(ctx);
    }

    const finalizeMountings = await Promise.all([
      mountComponents(connectionName, ctx),
      mountApis(connectionName, ctx),
      mountAdmin(connectionName, ctx),
      mountPlugins(connectionName, ctx),
    ]);

    for (const finalizeMounting of _.flattenDeep(finalizeMountings)) {
      await finalizeMounting();
    }
  }

  function mountCoreStore(ctx) {
    return mountModels(
      {
        models: {
          core_store: strapi.models['core_store'],
        },
        target: strapi.models,
      },
      ctx,
      { selfFinalize: true }
    );
  }

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

    return mountModels(options, ctx);
  }

  function mountApis(connectionName, ctx) {
    const options = {
      models: _.pickBy(
        strapi.models,
        ({ connection }, name) => connection === connectionName && name !== 'core_store'
      ),
      target: strapi.models,
    };

    return mountModels(options, ctx);
  }

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

    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,
          },
          ctx
        );
      })
    );
  }

  async function destroy() {
    await Promise.all(bookshelfConnections.map(connName => strapi.connections[connName].destroy()));
  }

  return {
    defaults,
    initialize,
    getQueryParams,
    buildQuery,
    queries,
    destroy,
    ...relations,
    get defaultTimestamps() {
      return ['created_at', 'updated_at'];
    },
  };
};
