const path = require("path");

const Winner = require(path.join(__dirname, "/schemas/Highscore"));
const wineRepository = require(path.join(__dirname, "/wine"));

class HistoryByDateNotFound extends Error {
  constructor(message = "History for given date not found.") {
    super(message);
    this.name = "HistoryByDateNotFound";
    this.statusCode = 404;
  }
}

class HistoryForUserNotFound extends Error {
  constructor(message = "History for given user not found.") {
    super(message);
    this.name = "HistoryForUserNotFound";
    this.statusCode = 404;
  }
}

// highscore
const addWinnerWithWine = async (winner, wine) => {
  const exisitingWinner = await Winner.findOne({
    name: winner.name
  });
  const savedWine = await wineRepository.addWine(wine);

  const date = new Date();
  date.setHours(5, 0, 0, 0);
  const winObject = {
    date: date,
    wine: savedWine,
    color: winner.color
  };

  if (exisitingWinner == undefined) {
    const newWinner = new Winner({
      name: winner.name,
      wins: [winObject]
    });

    await newWinner.save();
  } else {
    exisitingWinner.wins.push(winObject);
    exisitingWinner.markModified("wins");
    await exisitingWinner.save();
  }

  return exisitingWinner;
};

// lottery
const all = (includeWines = false) => {
  if (includeWines === false) {
    return Winner.find().sort("-wins.date");
  } else {
    return Winner.find()
      .sort("-wins.date")
      .populate("wins.wine");
  }
};

// lottery
const byDate = date => {
  const startQueryDate = new Date(date.setHours(0, 0, 0, 0));
  const endQueryDate = new Date(date.setHours(24, 59, 59, 99));
  const query = [
    {
      $match: {
        "wins.date": {
          $gte: startQueryDate,
          $lte: endQueryDate
        }
      }
    },
    { $unwind: "$wins" },
    {
      $match: {
        "wins.date": {
          $gte: startQueryDate,
          $lte: endQueryDate
        }
      }
    },
    {
      $lookup: {
        from: "wines",
        localField: "wins.wine",
        foreignField: "_id",
        as: "wins.wine"
      }
    },
    { $unwind: "$wins.wine" },
    {
      $project: {
        name: "$name",
        date: "$wins.date",
        color: "$wins.color",
        wine: "$wins.wine"
      }
    }
  ];

  return Winner.aggregate(query).then(winners => {
    if (winners.length == 0) {
      throw new HistoryByDateNotFound();
    }
    return winners;
  });
};

// highscore
const byName = (name, sort = "desc") => {
  return Winner.findOne({ name }, ["name", "wins"])
    .sort("-wins.date")
    .populate("wins.wine")
    .then(winner => {
      if (winner) {
        winner.wins = sort !== "asc" ? winner.wins.reverse() : winner.wins;
        return winner;
      } else {
        throw new HistoryForUserNotFound();
      }
    });
};

// highscore
const search = (query, sort = "desc") => {
  return Winner.find({ name: { $regex: query, $options: "i" } }, ["name"]).then(winners => {
    if (winners) {
      winners = sort === "desc" ? winners.reverse() : winners;
      return winners;
    } else {
      throw new HistoryForUserNotFound();
    }
  });
};

// lottery
const latest = () => {
  const query = [
    {
      $unwind: "$wins"
    },
    {
      $lookup: {
        from: "wines",
        localField: "wins.wine",
        foreignField: "_id",
        as: "wins.wine"
      }
    },
    {
      $group: {
        _id: "$wins.date",
        winners: {
          $push: {
            _id: "$_id",
            name: "$name",
            color: "$wins.color",
            wine: "$wins.wine"
          }
        }
      }
    },
    {
      $project: {
        date: "$_id",
        winners: "$winners"
      }
    },
    {
      $sort: {
        _id: -1
      }
    },
    {
      $limit: 1
    }
  ];

  return Winner.aggregate(query).then(winners => winners[0]);
};

// lottery - byDate
const groupByDate = (includeWines = false, sort = "asc") => {
  const sortDirection = sort == "asc" ? -1 : 1;
  const query = [
    {
      $unwind: "$wins"
    },
    {
      $group: {
        _id: "$wins.date",
        winners: {
          $push: {
            _id: "$_id",
            name: "$name",
            color: "$wins.color",
            wine: "$wins.wine"
          }
        }
      }
    },
    {
      $project: {
        date: "$_id",
        winners: "$winners"
      }
    },
    {
      $sort: {
        date: sortDirection
      }
    }
  ];

  if (includeWines) {
    query.splice(1, 0, {
      $lookup: {
        from: "wines",
        localField: "wins.wine",
        foreignField: "_id",
        as: "wins.wine"
      }
    });
  }

  return Winner.aggregate(query);
};

// highscore - byColor
const groupByColor = (includeWines = false) => {
  const query = [
    {
      $unwind: "$wins"
    },
    {
      $group: {
        _id: "$wins.color",
        winners: {
          $push: {
            _id: "$_id",
            name: "$name",
            date: "$wins.date",
            wine: "$wins.wine"
          }
        },
        count: { $sum: 1 }
      }
    },
    {
      $project: {
        color: "$_id",
        count: "$count",
        winners: "$winners"
      }
    },
    {
      $sort: {
        _id: -1
      }
    }
  ];

  if (includeWines) {
    query.splice(1, 0, {
      $lookup: {
        from: "wines",
        localField: "wins.wine",
        foreignField: "_id",
        as: "wins.wine"
      }
    });
  }

  return Winner.aggregate(query);
};

// highscore - byWineOccurences

// highscore - byWinCount
const orderByWins = (includeWines = false, limit = undefined) => {
  let query = [
    {
      $project: {
        name: "$name",
        wins: "$wins",
        totalWins: { $size: "$wins" }
      }
    },
    {
      $sort: {
        totalWins: -1,
        "wins.date": -1
      }
    }
  ];

  if (includeWines) {
    const includeWinesSubQuery = [
      {
        $unwind: "$wins"
      },
      {
        $lookup: {
          from: "wines",
          localField: "wins.wine",
          foreignField: "_id",
          as: "wins.wine"
        }
      },
      {
        $unwind: "$wins._id"
      },
      {
        $group: {
          _id: "$_id",
          name: { $first: "$name" },
          totalWins: { $first: "$totalWins" },
          wins: { $push: "$wins" }
        }
      }
    ];

    query = includeWinesSubQuery.concat(query);
  }

  return Winner.aggregate(query).then(winners => {
    if (limit == null) {
      return winners;
    }

    return winners.slice(0, limit);
  });
};

module.exports = {
  addWinnerWithWine,
  all,
  byDate,
  byName,
  search,
  latest,
  groupByDate,
  groupByColor,
  orderByWins
};
