const h = require('../helper');
const MongoClient = require('mongodb').MongoClient;
let collectionsMaybe = null, connecting = false, connectionCallbacks = [];

const mongoUrl = 'mongodb://tfb-database:27017';
const dbName = 'hello_world';

/**
 * Note! The benchmarks say we should use "id" as a property name.
 * However, Mongo provides a default index on "_id", so to be equivalent to the other tests, we use
 * the same, default index provided by the database.
 *
 */

const getCollections = async () => {
  // mongoose creates a queue of requests during connection, so we don't have to wait.
  // however, with the raw driver we need to connect first, or sometimes the test will fail randomly
  if (collectionsMaybe) {
    return collectionsMaybe;
  }
  if (connecting) {
    const promise = new Promise((resolve) => {
      connectionCallbacks.push(resolve);
    });
    return await promise;
  }
  connecting = true;
  const client = await MongoClient.connect(mongoUrl);
  collectionsMaybe = {
    World: null,
    Fortune: null
  };
  collectionsMaybe.World = client.db(dbName).collection('world');
  collectionsMaybe.Fortune = client.db(dbName).collection('fortune');

  // resolve pending requests in buffer
  for (const callback of connectionCallbacks) {
    callback(collectionsMaybe);
  }

  return collectionsMaybe;
}

const toClientWorld = (world) => {
  if (world) {
    world.id = world._id;
    delete world._id;
  }
  return world;
};


const mongodbRandomWorld = async () => {
  const collections = await getCollections();
  return toClientWorld(await collections.World.findOne({
    _id: h.randomTfbNumber()
  }));
};

const mongodbGetAllFortunes = async () => {
  const collections = await getCollections();
  return (await collections.Fortune.find({}).toArray()).map(toClientWorld);
};

async function getUpdateRandomWorld() {
  const collections = await getCollections();
  const world = await collections.World.findOne({
    _id: h.randomTfbNumber()
  });
  world.randomNumber = h.randomTfbNumber();
  await collections.World.updateOne({
    _id: world._id
  }, {
    $set: {
      randomNumber: world.randomNumber
    }
  })
  return toClientWorld(world);
}

module.exports = {

  SingleQuery: async (req, res) => {
    const result = await mongodbRandomWorld();
    h.addTfbHeaders(res, 'json');
    res.end(JSON.stringify(result));
  },

  MultipleQueries: async (queryCount, req, res) => {
    const queryFunctions = [];
    for (let i = 0; i < queryCount; i++) {
      queryFunctions.push(mongodbRandomWorld());
    }
    const results = await Promise.all(queryFunctions);

    h.addTfbHeaders(res, 'json');
    res.end(JSON.stringify(results));
  },

  Fortunes: async (req, res) => {
    const fortunes = await mongodbGetAllFortunes();
    fortunes.push(h.additionalFortune());
    fortunes.sort(function (a, b) {
      return a.message.localeCompare(b.message);
    });
    h.addTfbHeaders(res, 'html');
    res.end(h.fortunesTemplate({fortunes}));
  },

  Updates: async (queryCount, req, res) => {
    const promises = [];

    for (let i = 1; i <= queryCount; i++) {
      promises.push(getUpdateRandomWorld());
    }

    h.addTfbHeaders(res, 'json');
    res.end(JSON.stringify(await Promise.all(promises)));
  }

};
