/**
 * Mongo Factory functions
 *
 *  Copyright (C) 2010-2020 R-T Specialty, LLC.
 *
 *  This file is part of the Liza Data Collection Framework.
 *
 *  liza is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * These definitions are for a very old mongodb library, which will be
 * once we get around to updating node.  Quite a failure on the maintenance
 * front.
 *
 * instantiate objects for MongoDb
 */

import {AsyncDataFactory} from './DataFactory';
import {Db, Collection, MongoClient} from 'mongodb';
import {MongoServerDao} from '../../server/db/MongoServerDao';

export interface AsyncMongoDataFactory extends AsyncDataFactory {
  createDatabase(): Promise<Db>;
  createDao(db: Db | Promise<Db>): Promise<MongoServerDao>;
}

/**
 * Used to create Mongo services
 *
 * @param env     - env name
 * @param ts_ctor - unix timestamp constructor
 *
 * @return a Mongo Factory
 */
export const MongoFactory = (env: string) => (
  ts_ctor: () => UnixTimestamp
): AsyncMongoDataFactory => {
  return {
    createDatabase: async (): Promise<Db> => {
      return getClient(process.env).then(client => client.db('program'));
    },

    createDao: async (db: Db | Promise<Db>) => {
      const [quote_collection, sequence_collection] = await Promise.all([
        getQuotesCollection(await db),
        getSeqCollection(await db),
      ]);

      return new MongoServerDao(
        quote_collection,
        sequence_collection,
        env,
        ts_ctor
      );
    },
  };
};

/**
 * Attempts to connect to the database and retrieve the quotes collection
 *
 * @param db - the mongo database
 *
 * @return the collection
 */
export function getQuotesCollection(db: Db): Promise<Collection> {
  return new Promise<Collection>((resolve, reject) => {
    db.collection('quotes', {}, (err, coll) => {
      if (err) {
        reject(err);
      }

      coll
        .createIndex({id: 1}, {unique: true})
        .then(() => coll.createIndex({published: 1}))
        .then(() => coll.createIndex({deltaError: 1}))
        .then(() => {
          return coll.createIndex([
            ['published', 1],
            ['deltaError', 1],
          ]);
        })
        .then(() => resolve(coll));
    });
  });
}

/**
 * Attempts to connect to the database and retrieve the quotes collection
 *
 * @param db - the mongo database
 *
 * @return the collection
 */
export function getSeqCollection(db: Db): Promise<Collection> {
  return new Promise<Collection>((resolve, reject) => {
    db.collection('seq', {}, (err, coll) => {
      if (err) {
        reject(err);
      }

      resolve(coll);
    });
  });
}

/**
 * Create a mongodb client from the environment
 *
 * @param env - the environment variables
 *
 * @return the mongo client
 */
export function getClient(env: NodeJS.ProcessEnv): Promise<MongoClient> {
  const host = env.MONGODB_HOST || '127.0.0.1',
    port = env.MONGODB_PORT || 27017,
    replset = env.LIZA_MONGODB_REPLSET,
    ha = +(env.LIZA_MONGODB_HA || 0) === 1,
    host_a = env.LIZA_MONGODB_HOST_A,
    port_a = +(env.LIZA_MONGODB_PORT_A || 0),
    host_b = env.LIZA_MONGODB_HOST_B,
    port_b = +(env.LIZA_MONGODB_PORT_B || 0);

  const connection_string =
    'mongodb://' +
    (ha
      ? host_a + ':' + port_a + ',' + host_b + ':' + port_b
      : host + ':' + port);
  const options = ha
    ? {useUnifiedTopology: true, replicaSet: replset || 'rs0'}
    : {useUnifiedTopology: true};

  return MongoClient.connect(connection_string, options);
}
