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

const { runMigrations } = require('./run_migrations');
const Migration18 = require('./18');

// IMPORTANT: The migrations below are run on a database that may be very large
// due to attachments being directly stored inside the database. Please avoid
// any expensive operations, e.g. modifying all messages / attachments, etc., as
// it may cause out-of-memory errors for users with long histories:
// https://github.com/signalapp/Signal-Desktop/issues/2163
const migrations = [
  {
    version: '12.0',
    migrate(transaction, next) {
      console.log('Migration 12');
      console.log('creating object stores');
      const messages = transaction.db.createObjectStore('messages');
      messages.createIndex('conversation', ['conversationId', 'received_at'], {
        unique: false,
      });
      messages.createIndex('receipt', 'sent_at', { unique: false });
      messages.createIndex('unread', ['conversationId', 'unread'], {
        unique: false,
      });
      messages.createIndex('expires_at', 'expires_at', { unique: false });

      const conversations = transaction.db.createObjectStore('conversations');
      conversations.createIndex('inbox', 'active_at', { unique: false });
      conversations.createIndex('group', 'members', {
        unique: false,
        multiEntry: true,
      });
      conversations.createIndex('type', 'type', {
        unique: false,
      });
      conversations.createIndex('search', 'tokens', {
        unique: false,
        multiEntry: true,
      });

      transaction.db.createObjectStore('groups');

      transaction.db.createObjectStore('sessions');
      transaction.db.createObjectStore('identityKeys');
      transaction.db.createObjectStore('preKeys');
      transaction.db.createObjectStore('signedPreKeys');
      transaction.db.createObjectStore('items');

      console.log('creating debug log');
      transaction.db.createObjectStore('debug');

      next();
    },
  },
  {
    version: '13.0',
    migrate(transaction, next) {
      console.log('Migration 13');
      console.log('Adding fields to identity keys');
      const identityKeys = transaction.objectStore('identityKeys');
      const request = identityKeys.openCursor();
      const promises = [];
      request.onsuccess = event => {
        const cursor = event.target.result;
        if (cursor) {
          const attributes = cursor.value;
          attributes.timestamp = 0;
          attributes.firstUse = false;
          attributes.nonblockingApproval = false;
          attributes.verified = 0;
          promises.push(
            new Promise((resolve, reject) => {
              const putRequest = identityKeys.put(attributes, attributes.id);
              putRequest.onsuccess = resolve;
              putRequest.onerror = e => {
                console.log(e);
                reject(e);
              };
            })
          );
          cursor.continue();
        } else {
          // no more results
          // eslint-disable-next-line more/no-then
          Promise.all(promises).then(() => {
            next();
          });
        }
      };
      request.onerror = event => {
        console.log(event);
      };
    },
  },
  {
    version: '14.0',
    migrate(transaction, next) {
      console.log('Migration 14');
      console.log('Adding unprocessed message store');
      const unprocessed = transaction.db.createObjectStore('unprocessed');
      unprocessed.createIndex('received', 'timestamp', { unique: false });
      next();
    },
  },
  {
    version: '15.0',
    migrate(transaction, next) {
      console.log('Migration 15');
      console.log('Adding messages index for de-duplication');
      const messages = transaction.objectStore('messages');
      messages.createIndex('unique', ['source', 'sourceDevice', 'sent_at'], {
        unique: true,
      });
      next();
    },
  },
  {
    version: '16.0',
    migrate(transaction, next) {
      console.log('Migration 16');
      console.log('Dropping log table, since we now log to disk');
      transaction.db.deleteObjectStore('debug');
      next();
    },
  },
  {
    version: 17,
    async migrate(transaction, next) {
      console.log('Migration 17');

      const start = Date.now();

      const messagesStore = transaction.objectStore('messages');
      console.log('Create index from attachment schema version to attachment');
      messagesStore.createIndex('schemaVersion', 'schemaVersion', {
        unique: false,
      });

      const duration = Date.now() - start;

      console.log(
        'Complete migration to database version 17',
        `Duration: ${duration}ms`
      );
      next();
    },
  },
  {
    version: 18,
    migrate(transaction, next) {
      console.log('Migration 18');

      const start = Date.now();
      Migration18.run(transaction);
      const duration = Date.now() - start;

      console.log(
        'Complete migration to database version 18',
        `Duration: ${duration}ms`
      );
      next();
    },
  },
];

const database = {
  id: 'signal',
  nolog: true,
  migrations,
};

exports.run = ({ Backbone, databaseName } = {}) =>
  runMigrations({
    Backbone,
    database: Object.assign(
      {},
      database,
      isString(databaseName) ? { id: databaseName } : {}
    ),
  });

exports.getDatabase = () => ({
  name: database.id,
  version: exports.getLatestVersion(),
});

exports.getLatestVersion = () => {
  const lastMigration = last(migrations);
  if (!lastMigration) {
    return null;
  }

  return lastMigration.version;
};
