/* eslint-env browser */

const { head, isFunction, isObject, isString, last } = require('lodash');

const db = require('../database');
const { deferredToPromise } = require('../deferred_to_promise');

const closeDatabaseConnection = ({ Backbone } = {}) =>
  deferredToPromise(Backbone.sync('closeall'));

exports.runMigrations = async ({ Backbone, database, logger } = {}) => {
  if (
    !isObject(Backbone) ||
    !isObject(Backbone.Collection) ||
    !isFunction(Backbone.Collection.extend)
  ) {
    throw new TypeError('runMigrations: Backbone is required');
  }

  if (
    !isObject(database) ||
    !isString(database.id) ||
    !Array.isArray(database.migrations)
  ) {
    throw new TypeError('runMigrations: database is required');
  }
  if (!isObject(logger)) {
    throw new TypeError('runMigrations: logger is required');
  }

  const {
    firstVersion: firstMigrationVersion,
    lastVersion: lastMigrationVersion,
  } = getMigrationVersions(database);

  const databaseVersion = await db.getVersion(database.id);
  const isAlreadyUpgraded = databaseVersion >= lastMigrationVersion;

  logger.info('Database status', {
    firstMigrationVersion,
    lastMigrationVersion,
    databaseVersion,
    isAlreadyUpgraded,
  });

  if (isAlreadyUpgraded) {
    return;
  }

  const migrationCollection = new (Backbone.Collection.extend({
    database,
    storeName: 'items',
  }))();

  // Note: this legacy migration technique is required to bring old clients with
  //   data in IndexedDB forward into the new world of SQLCipher only.
  await deferredToPromise(migrationCollection.fetch({ limit: 1 }));

  logger.info('Close database connection');
  await closeDatabaseConnection({ Backbone });
};

const getMigrationVersions = database => {
  if (!isObject(database) || !Array.isArray(database.migrations)) {
    throw new TypeError("'database' is required");
  }

  const firstMigration = head(database.migrations);
  const lastMigration = last(database.migrations);

  const firstVersion = firstMigration
    ? parseInt(firstMigration.version, 10)
    : null;
  const lastVersion = lastMigration
    ? parseInt(lastMigration.version, 10)
    : null;

  return { firstVersion, lastVersion };
};