const express = require("express");
const router = express.Router();
const mongo = require("mongodb");
const MongoClient = mongo.MongoClient;
const connectionUrl = process.env.MONGO_CONNECTION_STRING;
const functions = require("./functions");

router.get("/round/:roundId", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }

      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      let id = undefined;
      try {
        id = mongo.ObjectID(req.params.roundId); // get roundId
      } catch (error) {
        res.status(404).send("No document with specified id was found");
        return;
      }

      db.collection(collection)
        .findOne({
          _id: id,
        })
        .then((result) => {
          if (!result) {
            res.status(404).send("No document with specified id found");
            return;
          }
          res.json(result);
          client.close();
        })
        .catch((err) => console.log(err));
    }
  );
});

function updateHighscore(client, gameid, playerid, value) {
  return new Promise((resolve, reject) => {
    const db = client.db("gameWare");
    const collection = "highscores";

    db.collection(collection).updateOne(
      {
        gameId: mongo.ObjectID(gameid),
        playerId: mongo.ObjectID(playerid),
      },
      { $max: { value: value } },
      { upsert: true },
      (err, result) => {
        if (err) {
          resolve(500);
          return;
        }
        resolve(result);
      }
    );
  });
}

router.get("/latestpoints/:tournamentId/:playerId", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }
      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      try {
        tournamentid = mongo.ObjectID(req.params.tournamentId); // get tournamentId
        playerid = mongo.ObjectID(req.params.playerId); // get playerId
      } catch (error) {
        res.status(404).send("Invalid IDs");
        return;
      }
      db.collection(collection)
        .find({
          tournamentId: tournamentid,
          playerId: playerid,
          tournamentPoints: { $gt: 0 },
        })
        .sort({ roundNr: -1 })
        .limit(1)
        .project({ tournamentPoints: 1 })
        .toArray((err, result) => {
          if (err) {
            res.sendStatus(500);
            //console.log(err);
            return;
          }
          res.json(result);
          client.close();
        });
    }
  );
});

router.get("/tournamentpoints/:tournamentId/:playerId", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }
      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      try {
        tournamentid = mongo.ObjectID(req.params.tournamentId); // get tournamentId
        playerid = mongo.ObjectID(req.params.playerId); // get playerId
      } catch (error) {
        res.status(404).send("Invalid IDs");
        return;
      }
      db.collection(collection)
        .find({
          tournamentId: tournamentid,
          playerId: playerid,
        })
        .toArray((err, result) => {
          if (err) {
            res.sendStatus(500);
            //console.log(err);
            return;
          }
          let sum = 0;
          for (let i = 0; i < result.length; i++) {
            sum += result[i].tournamentPoints;
          }
          res.json({ tournamentPoints: sum });
          client.close();
        });
    }
  );
});

router.get("/all/:tournamentId", (req, res) => {
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }

      const db = client.db("gameWare");
      const collection = "rounds";

      try {
        tournamentid = mongo.ObjectID(req.params.tournamentId); // get tournamentId
      } catch (error) {
        res.status(400).send("Invalid ID");
        return;
      }

      if (tournamentid == null) {
        res.status(400).send("Missing fields");
        return;
      }

      db.collection(collection)
        .find({
          tournamentId: tournamentid,
        })
        .sort({
          roundNr: -1,
        })
        .project({
          playerId: 1,
          tournamentPoints: 1,
        })
        .toArray((err, result) => {
          if (err) {
            res.sendStatus(500);
            //console.log(err);
            return;
          }
          let uniqueIds = [];
          let idobjects = [];
          let object = [];
          for (let i = 0; i < result.length; i++) {
            if (!uniqueIds.includes(String(result[i].playerId))) {
              uniqueIds.push(String(result[i].playerId));
              idobjects.push(result[i].playerId);
            }
          }
          db.collection("players")
            .find({
              _id: { $in: idobjects },
            })
            .project({ name: 1 })
            .toArray((err, nameresult) => {
              if (err) {
                res.sendStatus(500);
                //console.log(err);
                return;
              }
              for (let i = 0; i < uniqueIds.length; i++) {
                player = uniqueIds[i];
                let total = 0;
                let latest = 0;
                for (let j = 0; j < result.length; j++) {
                  if (String(result[j].playerId) == player) {
                    let points = result[j].tournamentPoints;
                    if (points != 0 && latest == 0) {
                      latest = points;
                    }
                    total += points;
                  }
                }
                let name;
                for (let j = 0; j < nameresult.length; j++) {
                  if (String(nameresult[j]._id) === player) {
                    name = nameresult[j].name;
                  }
                }
                object.push({
                  id: player,
                  name: name,
                  totalPoints: total,
                  latestPoints: latest,
                });
              }
              object.sort((a, b) => (a.totalPoints > b.totalPoints ? -1 : 1));
              res.json(object);
              client.close();
            });
        });
    }
  );
});

router.get("/specific/:tournamentId/:playerId/:roundNr", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }
      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      let tournamentId,
        playerId,
        roundNr = undefined;
      try {
        tournamentId = mongo.ObjectID(req.params.tournamentId); // get tournamentId
        playerId = mongo.ObjectID(req.params.playerId); // get playerId
        roundNr = parseInt(req.params.roundNr); // get roundNr
      } catch (error) {
        res.status(404).send("No document with specified params found");
        return;
      }

      db.collection(collection)
        .findOne({
          tournamentId,
          playerId,
          roundNr,
        })
        .then((result) => {
          if (!result) {
            res.status(404).send("No document with specified id params found");
            return;
          }
          res.json(result);
          client.close();
        })
        .catch((err) => res.sendStatus(500));
    }
  );
});

router.post("/", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }

      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      const values = {
        tournamentId: null,
        playerId: null,
        gameId: null,
        roundNr: null,
        deadlineDate: null,
      };

      try {
        values.tournamentId = mongo.ObjectID(req.body.tournamentId);
        values.playerId = mongo.ObjectID(req.body.playerId);
        values.gameId = mongo.ObjectID(req.body.gameId);
        values.roundNr = !isNaN(parseInt(req.body.roundNr))
          ? parseInt(req.body.roundNr)
          : null;
        values.deadlineDate = getDate(req.body.deadlineDate);
      } catch (error) {
        res.status(404).send("No document with specified id was found");
        console.log(error);
        return;
      }

      // validating body
      Object.keys(values).forEach((val) => {
        if (!req.body[val]) {
          values[val] = null;
        }
      });
      // values contains a null field
      if (Object.values(values).some((x) => x === null)) {
        res.status(404).send("Missing fields in request");
        return;
      }

      values.scoreValue = 0;
      values.hasPlayed = false;

      db.collection(collection).insertOne(values, (err, result) => {
        if (err) {
          res.sendStatus(500); // Internal server error
          return;
        }
        res.json(result.ops[0]);
        client.close();
      });
    }
  );
});

router.put("/:roundid/:tournamentid", (req, res) => {
  // Connect to database
  MongoClient.connect(
    connectionUrl,
    { useNewUrlParser: true, useUnifiedTopology: true },
    (err, client) => {
      // Unable to connect to database
      if (err) {
        res.sendStatus(500); // Internal server error
        return;
      }

      // Using the database gameWare and collection rounds
      const db = client.db("gameWare");
      const collection = "rounds";

      let id,
        scoreValue,
        tournament = null;
      try {
        id = mongo.ObjectID(req.params.roundid); // get roundId
        tournament = mongo.ObjectID(req.params.tournamentid);
        scoreValue = !isNaN(parseInt(req.body.scoreValue))
          ? parseInt(req.body.scoreValue)
          : null;
      } catch (error) {
        res.status(400).send("No document with specified id was found");
        return;
      }
      if (!id || !tournament || scoreValue === null) {
        res.status(400).send("Request body not valid");
        return;
      }

      db.collection(collection)
        .findOneAndUpdate(
          { _id: id, scoreValue: 0, hasPlayed: false },
          { $set: { scoreValue: scoreValue, hasPlayed: true } },
          { returnOriginal: false }
        )
        .then((updatedDocument) => {
          if (updatedDocument.value) {
            //console.log("Successfully updated document");
            updateHighscore(
              client,
              updatedDocument.value.gameId,
              updatedDocument.value.playerId,
              updatedDocument.value.scoreValue
            );
            roundcheck(client, tournament, id).then((checkresult) => {
              res.json(checkresult);
              client.close();
              return;
            });
            // Return updated document
          } else {
            //console.log("Failed to update doucment");
            res.status(404).send("No document with specified id was found");
            client.close();
          }
        })
        .catch((err) => console.log(err));
    }
  );
});

function roundcheck(client, tournament, roundid) {
  return new Promise((resolve, reject) => {
    functions.getCurrentRound(client, tournament).then((roundResult) => {
      // gets the current round of the tournament
      if (roundResult == 500) {
        resolve(500);
      }
      const active = roundResult.currentRound;
      const start = roundResult.startTime;
      const maxPlayers = roundResult.maxPlayers;
      const currentPlayers = roundResult.currentPlayers;
      const name = roundResult.name;

      functions.fetchRounds(client, tournament, active).then((rounds) => {
        // fetched the round objects of the active round
        let activePlayer;
        for (let i = 0; i < rounds.length; i++) {
          if (String(rounds[i]._id) == String(roundid)) {
            activePlayer = rounds[i].playerId;
            break;
          }
        }
        functions
          .checkRounds(rounds, start, maxPlayers, active, currentPlayers)
          .then((resultarray) => {
            // check to find out if [0] tournament will move on, [1] which players are timed out
            // [2] which players are still in the tournament
            let result = resultarray[0];
            let timedOut = resultarray[1];
            let left = resultarray[2];
            let timedOutRounds = resultarray[3];
            let end = false;
            if ((left.length < 2 && active != 1) || left.length == 0) {
              end = true;
              //if there are less then two people left in the first round, or zero people left in the first round
              //the tournament ends
            }

            functions
              .timeOut(client, tournament, timedOut, timedOutRounds)
              .then(
                //updates the state of the tournament and ends it if necessary
                () => {
                  functions
                    .notifyPlayers(client, timedOut, true, tournament, name)
                    .then(() => {
                      const indeks = left.indexOf(activePlayer);
                      left.splice(indeks, 1);
                      if (end) {
                        functions
                          .notifyPlayers(client, left, false, tournament, name)
                          .then(() => {
                            functions
                              .endTournament(client, tournament)
                              .then(() => {
                                getRound(client, roundid).then((round) => {
                                  resolve({
                                    round: round,
                                    roundCheck: {
                                      active: false,
                                      nextRound: false,
                                    },
                                  });
                                });
                              });
                          });
                      } else if (result) {
                        //Tournament moves on to next round
                        let eligibleRounds = [];
                        for (let i = 0; i < rounds.length; i++) {
                          let round = rounds[i];
                          if (round.hasPlayed === true) {
                            eligibleRounds.push(round);
                          }
                        }
                        //Here we find the rounds that were actually completed
                        for (let i = 0; i < eligibleRounds.length; i++) {
                          let round = eligibleRounds[i];
                          functions.updateScore(
                            client,
                            round._id,
                            i + 1,
                            eligibleRounds.length
                          );
                          //And here we update the tournament score
                        }
                        functions
                          .updateTournamentRound(client, tournament)
                          .then((result) => {
                            //updates the tournaments round if there are more rounds left
                            if (result.value) {
                              tourny = result.value;
                              const game =
                                tourny.games[
                                  (tourny.currentRound - 1) %
                                    tourny.games.length
                                ];
                              const date = new Date();
                              date.setTime(
                                date.getTime() +
                                  tourny.timePerRound * 24 * 60 * 60 * 1000
                              );

                              functions
                                .newRound(
                                  client,
                                  tournament,
                                  tourny.players,
                                  game,
                                  tourny.currentRound,
                                  date
                                )
                                .then(() => {
                                  getRound(client, roundid).then((round) => {
                                    resolve({
                                      round: round,
                                      roundCheck: {
                                        active: true,
                                        nextRound: true,
                                      },
                                    });
                                  });
                                });
                              // created new rounds for all the players left
                            } else {
                              // if the tournament now is done we simply end it
                              functions
                                .endTournament(client, tournament)
                                .then(() => {
                                  functions
                                    .notifyPlayers(
                                      client,
                                      left,
                                      false,
                                      tournament,
                                      name
                                    )
                                    .then(() => {
                                      getRound(client, roundid).then(
                                        (round) => {
                                          resolve({
                                            round: round,
                                            roundCheck: {
                                              active: false,
                                              nextRound: false,
                                            },
                                          });
                                        }
                                      );
                                    });
                                });
                            }
                          });
                      } else {
                        getRound(client, roundid).then((round) => {
                          resolve({
                            round: round,
                            roundCheck: {
                              // Tournament still waiting for other players
                              active: true,
                              nextRound: false,
                            },
                          });
                        });
                      }
                    });
                }
              );
          });
      });
    });
  });
}

function getRound(client, id) {
  return new Promise((resolve, reject) => {
    const db = client.db("gameWare");
    const collection = "rounds";

    db.collection(collection).findOne({ _id: id }, (err, result) => {
      if (err) {
        resolve(500); // Internal server error
        return;
      }
      resolve(result);
      return;
    });
  });
}

// Export API routes
module.exports = router;

function getDate(dateString) {
  date = new Date(dateString);
  if (isNaN(date.getTime())) {
    return null;
  }
  return date;
}