import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { setup } from "../../__testData__/controller-test";
import { ObjectId } from "mongodb";
import * as LeaderboardDal from "../../../src/dal/leaderboards";
import * as ConnectionsDal from "../../../src/dal/connections";
import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard";
import * as Configuration from "../../../src/init/configuration";
import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
import { XpLeaderboardEntry } from "@monkeytype/schemas/leaderboards";

const { mockApp, uid } = setup();
const configuration = Configuration.getCachedConfiguration();

const allModes = [
  "10",
  "25",
  "50",
  "100",
  "15",
  "30",
  "60",
  "120",
  "zen",
  "custom",
];

describe("Loaderboard Controller", () => {
  describe("get leaderboard", () => {
    const getLeaderboardMock = vi.spyOn(LeaderboardDal, "get");
    const getLeaderboardCountMock = vi.spyOn(LeaderboardDal, "getCount");

    beforeEach(() => {
      getLeaderboardMock.mockClear();
      getLeaderboardCountMock.mockClear();
      getLeaderboardCountMock.mockResolvedValue(42);
    });

    it("should get for english time 60", async () => {
      //GIVEN

      const resultData = {
        count: 42,
        pageSize: 50,
        entries: [
          {
            wpm: 20,
            acc: 90,
            timestamp: 1000,
            raw: 92,
            consistency: 80,
            uid: "user1",
            name: "user1",
            discordId: "discordId",
            discordAvatar: "discordAvatar",
            rank: 1,
            badgeId: 1,
            isPremium: true,
          },
          {
            wpm: 10,
            acc: 80,
            timestamp: 1200,
            raw: 82,
            uid: "user2",
            name: "user2",
            rank: 2,
          },
        ],
      };
      const mockData = resultData.entries.map((it) => ({
        ...it,
        _id: new ObjectId(),
      }));
      getLeaderboardMock.mockResolvedValue(mockData);
      getLeaderboardCountMock.mockResolvedValue(42);

      //WHEN

      const { body } = await mockApp
        .get("/leaderboards")
        .query({ language: "english", mode: "time", mode2: "60" })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Leaderboard retrieved",
        data: resultData,
      });

      expect(getLeaderboardMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        0,
        50,
        false,
        undefined,
      );

      expect(getLeaderboardCountMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        undefined,
      );
    });

    it("should get for english time 60 with page", async () => {
      //GIVEN
      getLeaderboardMock.mockResolvedValue([]);
      getLeaderboardCountMock.mockResolvedValue(0);
      const page = 0;
      const pageSize = 25;

      //WHEN

      const { body } = await mockApp
        .get("/leaderboards")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          page,
          pageSize,
        })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Leaderboard retrieved",
        data: {
          count: 0,
          pageSize: 25,
          entries: [],
        },
      });

      expect(getLeaderboardMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        page,
        pageSize,
        false,
        undefined,
      );
    });

    it("should get for friendsOnly", async () => {
      //GIVEN
      await enableConnectionsFeature(true);
      getLeaderboardMock.mockResolvedValue([]);
      getLeaderboardCountMock.mockResolvedValue(2);

      //WHEN

      const { body } = await mockApp
        .get("/leaderboards")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          friendsOnly: true,
        })
        .expect(200);

      //THEN
      expect(body.data.count).toEqual(2);

      expect(getLeaderboardMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        0,
        50,
        false,
        uid,
      );
      expect(getLeaderboardCountMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        uid,
      );
    });

    describe("should get for modes", async () => {
      beforeEach(() => {
        getLeaderboardMock.mockResolvedValue([]);
      });

      const testCases = [
        { mode: "time", mode2: "15", language: "english", expectStatus: 200 },
        { mode: "time", mode2: "60", language: "english", expectStatus: 200 },
        { mode: "time", mode2: "30", language: "english", expectStatus: 404 },
        { mode: "words", mode2: "15", language: "english", expectStatus: 404 },
        { mode: "time", mode2: "15", language: "spanish", expectStatus: 404 },
      ];
      it.for(testCases)(
        `expect $expectStatus for mode $mode, mode2 $mode2, lang $language`,
        async ({ mode, mode2, language, expectStatus }) => {
          await mockApp
            .get("/leaderboards")
            .query({ language, mode, mode2 })
            .expect(expectStatus);
        },
      );
    });

    it("fails for missing query", async () => {
      const { body } = await mockApp.get("/leaderboards").expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Required',
          '"mode" Required',
          '"mode2" Needs to be either a number, "zen" or "custom".',
        ],
      });
    });
    it("fails for invalid query", async () => {
      const { body } = await mockApp
        .get("/leaderboards")
        .query({
          language: "en?gli.sh",
          mode: "unknownMode",
          mode2: "unknownMode2",
          page: -1,
          pageSize: 500,
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Invalid enum value. Must be a supported language',
          `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
          '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
          '"page" Number must be greater than or equal to 0',
          '"pageSize" Number must be less than or equal to 200',
        ],
      });
    });
    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          extra: "value",
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });
    it("fails while leaderboard is updating", async () => {
      //GIVEN
      getLeaderboardMock.mockResolvedValue(false);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
        })
        .expect(503);

      expect(body.message).toEqual(
        "Leaderboard is currently updating. Please try again in a few seconds.",
      );
    });
  });

  describe("get rank", () => {
    const getLeaderboardRankMock = vi.spyOn(LeaderboardDal, "getRank");

    afterEach(() => {
      getLeaderboardRankMock.mockClear();
    });

    it("fails withouth authentication", async () => {
      await mockApp
        .get("/leaderboards/rank")
        .query({ language: "english", mode: "time", mode2: "60" })
        .expect(401);
    });

    it("should get for english time 60", async () => {
      //GIVEN

      const entryId = new ObjectId();
      const resultEntry = {
        _id: entryId,
        wpm: 10,
        acc: 80,
        timestamp: 1200,
        raw: 82,
        uid: "user2",
        name: "user2",
        rank: 2,
      };
      getLeaderboardRankMock.mockResolvedValue(resultEntry);

      //WHEN

      const { body } = await mockApp
        .get("/leaderboards/rank")
        .query({ language: "english", mode: "time", mode2: "60" })
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Rank retrieved",
        data: { ...resultEntry, _id: undefined },
      });

      expect(getLeaderboardRankMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        uid,
        false,
      );
    });
    it("should get for english time 60 friends only", async () => {
      //GIVEN
      await enableConnectionsFeature(true);
      getLeaderboardRankMock.mockResolvedValue({} as any);

      //WHEN
      await mockApp
        .get("/leaderboards/rank")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          friendsOnly: true,
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(getLeaderboardRankMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        uid,
        true,
      );
    });
    it("should get null if no rank", async () => {
      //GIVEN
      await enableConnectionsFeature(true);
      getLeaderboardRankMock.mockResolvedValue(null);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/rank")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          friendsOnly: true,
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(getLeaderboardRankMock).toHaveBeenCalledWith(
        "time",
        "60",
        "english",
        uid,
        true,
      );
      expect(body).toEqual({
        message: "Rank retrieved",
        data: null,
      });
    });
    it("should get with ape key", async () => {
      await acceptApeKeys(true);
      const apeKey = await mockAuthenticateWithApeKey(uid, await configuration);

      await mockApp
        .get("/leaderboards/rank")
        .query({ language: "english", mode: "time", mode2: "60" })
        .set("authorization", "ApeKey " + apeKey)
        .expect(200);
    });
    it("should get for mode", async () => {
      getLeaderboardRankMock.mockResolvedValue({} as any);
      for (const mode of ["time", "words", "quote", "zen", "custom"]) {
        const response = await mockApp
          .get("/leaderboards/rank")
          .set("Authorization", `Bearer ${uid}`)
          .query({ language: "english", mode, mode2: "custom" });
        expect(response.status, "for mode " + mode).toEqual(200);
      }
    });

    it("should get for mode2", async () => {
      getLeaderboardRankMock.mockResolvedValue({} as any);
      for (const mode2 of allModes) {
        const response = await mockApp
          .get("/leaderboards/rank")
          .set("Authorization", `Bearer ${uid}`)
          .query({ language: "english", mode: "words", mode2 });

        expect(response.status, "for mode2 " + mode2).toEqual(200);
      }
    });
    it("fails for missing query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Required',
          '"mode" Required',
          '"mode2" Needs to be either a number, "zen" or "custom".',
        ],
      });
    });
    it("fails for invalid query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/rank")
        .query({
          language: "en?gli.sh",
          mode: "unknownMode",
          mode2: "unknownMode2",
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Invalid enum value. Must be a supported language',
          `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
          '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
        ],
      });
    });
    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/rank")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          extra: "value",
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });
    it("fails while leaderboard is updating", async () => {
      //GIVEN
      getLeaderboardRankMock.mockResolvedValue(false);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/rank")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(503);

      expect(body.message).toEqual(
        "Leaderboard is currently updating. Please try again in a few seconds.",
      );
    });
  });

  describe("get daily leaderboard", () => {
    const getDailyLeaderboardMock = vi.spyOn(
      DailyLeaderboards,
      "getDailyLeaderboard",
    );
    const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
    const getResultMock = vi.fn();

    beforeEach(async () => {
      [getDailyLeaderboardMock, getFriendsUidsMock, getResultMock].forEach(
        (it) => it.mockClear(),
      );
      vi.useFakeTimers();
      vi.setSystemTime(1722606812000);
      await dailyLeaderboardEnabled(true);

      getDailyLeaderboardMock.mockReturnValue({
        getResults: getResultMock,
      } as any);

      getResultMock.mockResolvedValue(null);
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it("should get for english time 60", async () => {
      //GIVEN
      const lbConf = (await configuration).dailyLeaderboards;
      const premiumEnabled = (await configuration).users.premium.enabled;

      const resultData = {
        minWpm: 10,
        entries: [
          {
            name: "user1",
            rank: 1,
            wpm: 20,
            acc: 90,
            timestamp: 1000,
            raw: 92,
            consistency: 80,
            uid: "user1",
            discordId: "discordId",
            discordAvatar: "discordAvatar",
          },
          {
            wpm: 10,
            rank: 2,
            acc: 80,
            timestamp: 1200,
            raw: 82,
            consistency: 72,
            uid: "user2",
            name: "user2",
          },
        ],
      };

      getResultMock.mockResolvedValue({
        count: 2,
        minWpm: 10,
        entries: resultData,
      });

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({ language: "english", mode: "time", mode2: "60" })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Daily leaderboard retrieved",
        data: {
          count: 2,
          pageSize: 50,
          minWpm: 10,
          entries: resultData,
        },
      });

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        -1,
      );

      expect(getResultMock).toHaveBeenCalledWith(
        0,
        50,
        lbConf,
        premiumEnabled,
        undefined,
      );
    });

    it("should get for english time 60 for yesterday", async () => {
      //GIVEN
      const lbConf = (await configuration).dailyLeaderboards;

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          daysBefore: 1,
        })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Daily leaderboard retrieved",
        data: {
          entries: [],
          count: 0,
          pageSize: 50,
          minWpm: 0,
        },
      });

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        1722470400000,
      );
    });
    it("should get for english time 60 with page and pageSize", async () => {
      //GIVEN
      const lbConf = (await configuration).dailyLeaderboards;
      const premiumEnabled = (await configuration).users.premium.enabled;
      const page = 2;
      const pageSize = 25;

      getResultMock.mockResolvedValue({ entries: [] });

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          page,
          pageSize,
        })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Daily leaderboard retrieved",
        data: {
          entries: [],
          count: 0,
          pageSize,
          minWpm: 0,
        },
      });

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        -1,
      );

      expect(getResultMock).toHaveBeenCalledWith(
        page,
        pageSize,
        lbConf,
        premiumEnabled,
        undefined,
      );
    });

    it("should get for friends", async () => {
      //GIVEN
      const lbConf = (await configuration).dailyLeaderboards;
      const premiumEnabled = (await configuration).users.premium.enabled;
      await enableConnectionsFeature(true);
      const friends = [
        new ObjectId().toHexString(),
        new ObjectId().toHexString(),
      ];
      getFriendsUidsMock.mockResolvedValue(friends);

      //WHEN
      await mockApp
        .get("/leaderboards/daily")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          friendsOnly: true,
        })
        .expect(200);

      //THEN

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        -1,
      );

      expect(getResultMock).toHaveBeenCalledWith(
        0,
        50,
        lbConf,
        premiumEnabled,
        friends,
      );
    });

    it("fails for daysBefore not one", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          daysBefore: 2,
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ['"daysBefore" Invalid literal value, expected 1'],
      });
    });

    it("fails if daily leaderboards are disabled", async () => {
      await dailyLeaderboardEnabled(false);

      const { body } = await mockApp.get("/leaderboards/daily").expect(503);

      expect(body.message).toEqual(
        "Daily leaderboards are not available at this time.",
      );
    });

    it("should get for mode", async () => {
      for (const mode of ["time", "words", "quote", "zen", "custom"]) {
        const response = await mockApp
          .get("/leaderboards/daily")
          .query({ language: "english", mode, mode2: "custom" });
        expect(response.status, "for mode " + mode).toEqual(200);
      }
    });

    it("should get for mode2", async () => {
      for (const mode2 of allModes) {
        const response = await mockApp
          .get("/leaderboards/daily")
          .query({ language: "english", mode: "words", mode2 });

        expect(response.status, "for mode2 " + mode2).toEqual(200);
      }
    });

    it("fails for missing query", async () => {
      const { body } = await mockApp.get("/leaderboards").expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Required',
          '"mode" Required',
          '"mode2" Needs to be either a number, "zen" or "custom".',
        ],
      });
    });

    it("fails for invalid query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "en?gli.sh",
          mode: "unknownMode",
          mode2: "unknownMode2",
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Invalid enum value. Must be a supported language',
          `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
          '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
        ],
      });
    });
    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          extra: "value",
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });
    it("fails while leaderboard is missing", async () => {
      //GIVEN
      getDailyLeaderboardMock.mockReturnValue(null);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
        })
        .expect(404);

      expect(body.message).toEqual(
        "There is no daily leaderboard for this mode",
      );
    });
  });

  describe("get daily leaderboard rank", () => {
    const getDailyLeaderboardMock = vi.spyOn(
      DailyLeaderboards,
      "getDailyLeaderboard",
    );
    const getRankMock = vi.fn();
    const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");

    beforeEach(async () => {
      [getDailyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach((it) =>
        it.mockClear(),
      );

      getDailyLeaderboardMock.mockReturnValue({
        getRank: getRankMock,
      } as any);

      vi.useFakeTimers();
      vi.setSystemTime(1722606812000);
      await dailyLeaderboardEnabled(true);
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it("fails withouth authentication", async () => {
      await mockApp
        .get("/leaderboards/daily/rank")

        .query({ language: "english", mode: "time", mode2: "60" })
        .expect(401);
    });
    it("should get for english time 60", async () => {
      //GIVEN
      const lbConf = (await configuration).dailyLeaderboards;
      const rankData = {
        min: 100,
        count: 1000,
        rank: 12,
        entry: {
          wpm: 10,
          rank: 2,
          acc: 80,
          timestamp: 1200,
          raw: 82,
          consistency: 72,
          uid: "user2",
          name: "user2",
        },
      };

      getRankMock.mockResolvedValue(rankData);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .set("Authorization", `Bearer ${uid}`)
        .query({ language: "english", mode: "time", mode2: "60" })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Daily leaderboard rank retrieved",
        data: rankData,
      });

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        -1,
      );

      expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
    });

    it("should get for english time 60 friends only", async () => {
      //GIVEN
      await enableConnectionsFeature(true);
      const lbConf = (await configuration).dailyLeaderboards;
      getRankMock.mockResolvedValue({});
      const friends = ["friendOne", "friendTwo"];
      getFriendsUidsMock.mockResolvedValue(friends);

      //WHEN
      await mockApp
        .get("/leaderboards/daily/rank")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          friendsOnly: true,
        })
        .expect(200);

      //THEN

      expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
        "english",
        "time",
        "60",
        lbConf,
        -1,
      );

      expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends);
      expect(getFriendsUidsMock).toHaveBeenCalledWith(uid);
    });

    it("fails if daily leaderboards are disabled", async () => {
      await dailyLeaderboardEnabled(false);

      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(503);

      expect(body.message).toEqual(
        "Daily leaderboards are not available at this time.",
      );
    });

    it("should get for mode", async () => {
      for (const mode of ["time", "words", "quote", "zen", "custom"]) {
        const response = await mockApp
          .get("/leaderboards/daily/rank")
          .set("Authorization", `Bearer ${uid}`)
          .query({ language: "english", mode, mode2: "custom" });
        expect(response.status, "for mode " + mode).toEqual(200);
      }
    });

    it("should get for mode2", async () => {
      for (const mode2 of allModes) {
        const response = await mockApp
          .get("/leaderboards/daily/rank")
          .set("Authorization", `Bearer ${uid}`)
          .query({ language: "english", mode: "words", mode2 });

        expect(response.status, "for mode2 " + mode2).toEqual(200);
      }
    });

    it("fails for missing query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Required',
          '"mode" Required',
          '"mode2" Needs to be either a number, "zen" or "custom".',
        ],
      });
    });

    it("fails for invalid query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .query({
          language: "en?gli.sh",
          mode: "unknownMode",
          mode2: "unknownMode2",
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: [
          '"language" Invalid enum value. Must be a supported language',
          `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
          '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
        ],
      });
    });

    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
          extra: "value",
        })
        .set("Authorization", `Bearer ${uid}`)
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });

    it("fails while leaderboard is missing", async () => {
      //GIVEN
      getDailyLeaderboardMock.mockReturnValue(null);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/daily/rank")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          language: "english",
          mode: "time",
          mode2: "60",
        })
        .expect(404);

      expect(body.message).toEqual(
        "There is no daily leaderboard for this mode",
      );
    });
  });

  describe("get xp weekly leaderboard", () => {
    const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
    const getResultMock = vi.fn();
    const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");

    beforeEach(async () => {
      [getXpWeeklyLeaderboardMock, getResultMock, getFriendsUidsMock].forEach(
        (it) => it.mockClear(),
      );
      vi.useFakeTimers();
      vi.setSystemTime(1722606812000);
      await weeklyLeaderboardEnabled(true);

      getXpWeeklyLeaderboardMock.mockReturnValue({
        getResults: getResultMock,
      } as any);

      getResultMock.mockResolvedValue(null);
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it("should get", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;

      const resultData: XpLeaderboardEntry[] = [
        {
          totalXp: 100,
          rank: 1,
          timeTypedSeconds: 100,
          uid: "user1",
          name: "user1",
          discordId: "discordId",
          discordAvatar: "discordAvatar",
          lastActivityTimestamp: 1000,
        },
        {
          totalXp: 75,
          rank: 2,
          timeTypedSeconds: 200,
          uid: "user2",
          name: "user2",
          discordId: "discordId2",
          discordAvatar: "discordAvatar2",
          lastActivityTimestamp: 2000,
        },
      ];

      getResultMock.mockResolvedValue({ count: 2, entries: resultData });

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly")
        .query({})
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard retrieved",
        data: {
          entries: resultData,
          count: 2,
          pageSize: 50,
        },
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);

      expect(getResultMock).toHaveBeenCalledWith(
        0,
        50,
        lbConf,
        false,
        undefined,
      );
    });

    it("should get for last week", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly")
        .query({
          weeksBefore: 1,
        })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard retrieved",
        data: {
          count: 0,
          entries: [],
          pageSize: 50,
        },
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
        lbConf,
        1721606400000,
      );
    });

    it("should get with skip and limit", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;
      const page = 2;
      const pageSize = 25;

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly")
        .query({
          page,
          pageSize,
        })
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard retrieved",
        data: {
          entries: [],
          count: 0,
          pageSize,
        },
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);

      expect(getResultMock).toHaveBeenCalledWith(
        page,
        pageSize,
        lbConf,
        false,
        undefined,
      );
    });

    it("should get for friends", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;
      await enableConnectionsFeature(true);
      const page = 2;
      const pageSize = 25;
      const friends = [
        new ObjectId().toHexString(),
        new ObjectId().toHexString(),
      ];
      getFriendsUidsMock.mockResolvedValue(friends);

      //WHEN
      await mockApp
        .get("/leaderboards/xp/weekly")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          page,
          pageSize,
          friendsOnly: true,
        })
        .expect(200);

      //THEN

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);

      expect(getResultMock).toHaveBeenCalledWith(
        page,
        pageSize,
        lbConf,
        false,
        friends,
      );
    });

    it("fails if daily leaderboards are disabled", async () => {
      await weeklyLeaderboardEnabled(false);

      const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(503);

      expect(body.message).toEqual(
        "Weekly XP leaderboards are not available at this time.",
      );
    });

    it("fails for weeksBefore not one", async () => {
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly")
        .query({
          weeksBefore: 2,
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
      });
    });

    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly")
        .query({
          extra: "value",
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });

    it("fails while leaderboard is missing", async () => {
      //GIVEN
      getXpWeeklyLeaderboardMock.mockReturnValue(null);

      //WHEN
      const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(404);

      expect(body.message).toEqual("XP leaderboard for this week not found.");
    });
  });

  describe("get xp weekly leaderboard rank", () => {
    const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
    const getRankMock = vi.fn();
    const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");

    beforeEach(async () => {
      [getXpWeeklyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach(
        (it) => it.mockClear(),
      );

      await weeklyLeaderboardEnabled(true);
      vi.useFakeTimers();
      vi.setSystemTime(1722606812000);

      getXpWeeklyLeaderboardMock.mockReturnValue({
        getRank: getRankMock,
      } as any);
    });

    it("fails withouth authentication", async () => {
      await mockApp.get("/leaderboards/xp/weekly/rank").expect(401);
    });

    it("should get", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;

      const resultData: XpLeaderboardEntry = {
        totalXp: 100,
        rank: 1,
        timeTypedSeconds: 100,
        uid: "user1",
        name: "user1",
        discordId: "discordId",
        discordAvatar: "discordAvatar",
        lastActivityTimestamp: 1000,
      };

      getRankMock.mockResolvedValue(resultData);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard rank retrieved",
        data: resultData,
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);

      expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
    });

    it("should get for last week", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;
      getRankMock.mockResolvedValue({});

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .query({ weeksBefore: 1 })
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard rank retrieved",
        data: {},
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
        lbConf,
        1721606400000,
      );

      expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
    });

    it("should get for friendsOnly", async () => {
      //GIVEN
      const lbConf = (await configuration).leaderboards.weeklyXp;
      await enableConnectionsFeature(true);
      getRankMock.mockResolvedValue({});
      const friends = ["friendOne", "friendTwo"];
      getFriendsUidsMock.mockResolvedValue(friends);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .query({ friendsOnly: true })
        .set("Authorization", `Bearer ${uid}`)
        .expect(200);

      //THEN
      expect(body).toEqual({
        message: "Weekly xp leaderboard rank retrieved",
        data: {},
      });

      expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);

      expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends);
    });

    it("fails if daily leaderboards are disabled", async () => {
      await weeklyLeaderboardEnabled(false);

      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(503);

      expect(body.message).toEqual(
        "Weekly XP leaderboards are not available at this time.",
      );
    });

    it("fails for weeksBefore not one", async () => {
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          weeksBefore: 2,
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
      });
    });

    it("fails for unknown query", async () => {
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .set("Authorization", `Bearer ${uid}`)
        .query({
          extra: "value",
        })
        .expect(422);

      expect(body).toEqual({
        message: "Invalid query schema",
        validationErrors: ["Unrecognized key(s) in object: 'extra'"],
      });
    });

    it("fails while leaderboard is missing", async () => {
      //GIVEN
      getXpWeeklyLeaderboardMock.mockReturnValue(null);

      //WHEN
      const { body } = await mockApp
        .get("/leaderboards/xp/weekly/rank")
        .set("Authorization", `Bearer ${uid}`)
        .expect(404);

      expect(body.message).toEqual("XP leaderboard for this week not found.");
    });
  });
});

async function acceptApeKeys(enabled: boolean): Promise<void> {
  const mockConfig = await configuration;
  mockConfig.apeKeys = { ...mockConfig.apeKeys, acceptKeys: enabled };

  vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
    mockConfig,
  );
}

async function dailyLeaderboardEnabled(enabled: boolean): Promise<void> {
  const mockConfig = await configuration;
  mockConfig.dailyLeaderboards = {
    ...mockConfig.dailyLeaderboards,
    enabled: enabled,
  };

  vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
    mockConfig,
  );
}
async function weeklyLeaderboardEnabled(enabled: boolean): Promise<void> {
  const mockConfig = await configuration;
  mockConfig.leaderboards.weeklyXp = {
    ...mockConfig.leaderboards.weeklyXp,
    enabled,
  };

  vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
    mockConfig,
  );
}
async function enableConnectionsFeature(enabled: boolean): Promise<void> {
  const mockConfig = await configuration;
  mockConfig.connections = { ...mockConfig.connections, enabled };

  vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
    mockConfig,
  );
}
