const path = require("path");
const crypto = require("crypto");

const Attendee = require(path.join(__dirname, "/schemas/Attendee"));
const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine"));
const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner"));
const Lottery = require(path.join(__dirname, "/schemas/Purchase"));

const Message = require(path.join(__dirname, "/message"));
const {
  WinnerNotFound,
  NoMoreAttendeesToWin,
  CouldNotFindNewWinnerAfterNTries,
  LotteryByDateNotFound
} = require(path.join(__dirname, "/vinlottisErrors"));

const archive = (date, raffles, stolen, wines) => {
  const { blue, red, yellow, green } = raffles;
  const bought = blue + red + yellow + green;
  date = date.setHours(0, 0, 0, 0);

  return Lottery.findOneAndUpdate(
    { date },
    {
      date,
      blue,
      red,
      yellow,
      green,
      bought,
      stolen,
      wines
    },
    { upsert: true }
  );
};

const lotteryByDate = date => {
  const startOfDay = new Date(date.setHours(0, 0, 0, 0));
  const endOfDay = new Date(date.setHours(24, 59, 59, 99));

  const query = [
    {
      $match: {
        date: {
          $gte: startOfDay,
          $lte: endOfDay
        }
      }
    },
    {
      $lookup: {
        from: "wines",
        localField: "wines",
        foreignField: "_id",
        as: "wines"
      }
    }
  ];

  const aggregateLottery = Lottery.aggregate(query);
  return aggregateLottery.project("-_id -__v").then(lotteries => {
    if (lotteries.length == 0) {
      throw new LotteryByDateNotFound(date);
    }
    return lotteries[0];
  });
};

const allLotteries = () => {
  return Lottery.find()
    .select("-_id -__v")
    .populate("wines");
};

const drawWinner = async () => {
  let allContestants = await Attendee.find({ winner: false });

  if (allContestants.length == 0) {
    throw new NoMoreAttendeesToWin();
  }

  let raffleColors = [];
  for (let i = 0; i < allContestants.length; i++) {
    let currentContestant = allContestants[i];
    for (let blue = 0; blue < currentContestant.blue; blue++) {
      raffleColors.push("blue");
    }
    for (let red = 0; red < currentContestant.red; red++) {
      raffleColors.push("red");
    }
    for (let green = 0; green < currentContestant.green; green++) {
      raffleColors.push("green");
    }
    for (let yellow = 0; yellow < currentContestant.yellow; yellow++) {
      raffleColors.push("yellow");
    }
  }

  raffleColors = shuffle(raffleColors);

  let colorToChooseFrom = raffleColors[Math.floor(Math.random() * raffleColors.length)];
  let findObject = { winner: false };

  findObject[colorToChooseFrom] = { $gt: 0 };

  let tries = 0;
  const maxTries = 3;
  let contestantsToChooseFrom = undefined;
  while (contestantsToChooseFrom == undefined && tries < maxTries) {
    const hit = await Attendee.find(findObject);
    if (hit && hit.length) {
      contestantsToChooseFrom = hit;
      break;
    }
    tries++;
  }
  if (contestantsToChooseFrom == undefined) {
    throw new CouldNotFindNewWinnerAfterNTries(maxTries);
  }

  let attendeeListDemocratic = [];

  let currentContestant;
  for (let i = 0; i < contestantsToChooseFrom.length; i++) {
    currentContestant = contestantsToChooseFrom[i];
    for (let y = 0; y < currentContestant[colorToChooseFrom]; y++) {
      attendeeListDemocratic.push({
        name: currentContestant.name,
        phoneNumber: currentContestant.phoneNumber,
        red: currentContestant.red,
        blue: currentContestant.blue,
        green: currentContestant.green,
        yellow: currentContestant.yellow
      });
    }
  }

  attendeeListDemocratic = shuffle(attendeeListDemocratic);

  let winner = attendeeListDemocratic[Math.floor(Math.random() * attendeeListDemocratic.length)];

  let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({
    timestamp_drawn: 1
  });

  let newWinnerElement = new VirtualWinner({
    name: winner.name,
    phoneNumber: winner.phoneNumber,
    color: colorToChooseFrom,
    red: winner.red,
    blue: winner.blue,
    green: winner.green,
    yellow: winner.yellow,
    id: sha512(winner.phoneNumber, genRandomString(10)),
    timestamp_drawn: new Date().getTime()
  });

  await newWinnerElement.save();
  await Attendee.updateOne({ name: winner.name, phoneNumber: winner.phoneNumber }, { $set: { winner: true } });

  return { winner, color: colorToChooseFrom, winners };
};

/** - - UTILS - - **/
const genRandomString = function(length) {
  return crypto
    .randomBytes(Math.ceil(length / 2))
    .toString("hex") /** convert to hexadecimal format */
    .slice(0, length); /** return required number of characters */
};

const sha512 = function(password, salt) {
  var hash = crypto.createHmac("md5", salt); /** Hashing algorithm sha512 */
  hash.update(password);
  var value = hash.digest("hex");
  return value;
};

function shuffle(array) {
  let currentIndex = array.length,
    temporaryValue,
    randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

module.exports = {
  drawWinner,
  archive,
  lotteryByDate,
  allLotteries
};
