import {
  TestData,
  Timezones,
  createBookingScenario,
  replaceDates,
} from "../../utils/bookingScenario/bookingScenario";
import type { ScenarioData } from "../../utils/bookingScenario/bookingScenario";

import { describe, expect, vi, test } from "vitest";

import { PeriodType } from "@calcom/prisma/enums";
import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util";

import { expectedSlotsForSchedule } from "./expects";
import { setupAndTeardown } from "./setupAndTeardown";

function getPeriodTypeData({
  type,
  periodDays,
  periodCountCalendarDays,
  periodStartDate,
  periodEndDate,
}: {
  type: PeriodType;
  periodDays?: number;
  periodCountCalendarDays?: boolean;
  periodStartDate?: Date;
  periodEndDate?: Date;
}) {
  if (type === PeriodType.ROLLING) {
    if (periodCountCalendarDays === undefined || !periodDays) {
      throw new Error("periodCountCalendarDays and periodDays are required for ROLLING period type");
    }
    return {
      periodType: PeriodType.ROLLING,
      periodDays,
      periodCountCalendarDays,
    };
  }

  if (type === PeriodType.ROLLING_WINDOW) {
    if (periodCountCalendarDays === undefined || !periodDays) {
      throw new Error("periodCountCalendarDays and periodDays are required for ROLLING period type");
    }
    return {
      periodType: PeriodType.ROLLING_WINDOW,
      periodDays,
      periodCountCalendarDays,
    };
  }

  if (type === PeriodType.RANGE) {
    if (!periodStartDate || !periodEndDate) {
      throw new Error("periodStartDate and periodEndDate are required for RANGE period type");
    }
    return {
      periodType: PeriodType.RANGE,
      periodStartDate,
      periodEndDate,
    };
  }
}

vi.mock("@calcom/lib/constants", () => ({
  IS_PRODUCTION: true,
  WEBAPP_URL: "http://localhost:3000",
  RESERVED_SUBDOMAINS: ["auth", "docs"],
  ROLLING_WINDOW_PERIOD_MAX_DAYS_TO_CHECK: 61,
}));

describe("getSchedule", () => {
  setupAndTeardown();
  describe("Future Limits", () => {
    describe("PeriodType=ROLLING", () => {
      test("When the time of the first slot of current day hasn't reached", async () => {
        // In IST timezone, it is 2024-05-31T07:00:00
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING",
                periodDays: 2,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          // All slots on current day are available
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // No Timeslots beyond plus2Date as that is beyond the rolling period
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });
      });

      test("When the time of the last slot of current day has passed", async () => {
        // In IST timezone, it is 2024-05-31T07:00:00
        vi.setSystemTime("2024-05-31T11:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING",
                periodDays: 2,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        // No timeslots of current day available
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: todayDateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // No Timeslots beyond plus2Date as that is beyond the rolling period
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });
      });

      test("When the first timeslot of current day has passed", async () => {
        // In IST timezone, it is 2024-05-31T10:00:00
        vi.setSystemTime("2024-05-31T04:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING",
                periodDays: 2,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        // No timeslots of current day available
        expect(scheduleForEvent).toHaveTimeSlots(
          // First timeslot not available
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430.slice(1),
          {
            doExactMatch: true,
            dateString: todayDateString,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // No Timeslots beyond plus2Date as that is beyond the rolling period
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });
      });

      test("When the time of the first slot of current day hasn't reached and there is a day fully booked in between. Also, we are counting only business days.", async () => {
        // In IST timezone, it is 2024-05-31T07:00:00
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        // Friday
        const todayDateString = "2024-05-31";
        // Saturday
        const plus1DateString = "2024-06-01";
        // Sunday
        const plus2DateString = "2024-06-02";
        // Monday
        const plus3DateString = "2024-06-03";
        // Tuesday
        const plus4DateString = "2024-06-04";
        // Wednesday
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING",
                periodDays: 2,
                periodCountCalendarDays: false,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          // All slots on current day are available
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        // Being a Saturday, plus1Date is available as per Availability but not counted in periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        // Being a Saturday, plus2Date is available as per Availability but not counted in periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // Day1 of periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus3DateString,
            doExactMatch: true,
          }
        );

        // Day2 of periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus4DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus5DateString,
        });
      });

      describe("Borderline cases", () => {
        test("When it is the very first second of the day", async () => {
          // In IST timezone, it is 2024-05-31T00:00:00
          vi.setSystemTime("2024-05-30T18:30:00Z");
          const yesterdayDateString = "2024-05-30";
          const todayDateString = "2024-05-31";
          const plus1DateString = "2024-06-01";
          const plus2DateString = "2024-06-02";
          const plus3DateString = "2024-06-03";
          const plus4DateString = "2024-06-04";
          const plus5DateString = "2024-06-05";

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                ...getPeriodTypeData({
                  type: "ROLLING",
                  periodDays: 2,
                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEvent = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              // Because this time is in GMT, it will be 00:00 in IST with todayDateString
              startTime: `${yesterdayDateString}T18:30:00.000Z`,
              endTime: `${plus5DateString}T18:29:59.999Z`,
              timeZone: Timezones["+5:30"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          console.log({ scheduleForEvent });

          expect(scheduleForEvent).toHaveTimeSlots(
            // All slots on current day are available
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: todayDateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus1DateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus2DateString,
              doExactMatch: true,
            }
          );

          // No Timeslots beyond plus2Date as that is beyond the rolling period
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus3DateString,
          });

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus4DateString,
          });
        });

        test("When it is the very last second of the day", async () => {
          // In IST timezone, it is 2024-05-31T23:59:00
          vi.setSystemTime("2024-05-31T18:29:00Z");
          const yesterdayDateString = "2024-05-30";
          const todayDateString = "2024-05-31";
          const plus1DateString = "2024-06-01";
          const plus2DateString = "2024-06-02";
          const plus3DateString = "2024-06-03";
          const plus4DateString = "2024-06-04";
          const plus5DateString = "2024-06-05";

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                ...getPeriodTypeData({
                  type: "ROLLING",
                  periodDays: 2,
                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEvent = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              // Because this time is in GMT, it will be 00:00 in IST with todayDateString
              startTime: `${yesterdayDateString}T18:30:00.000Z`,
              endTime: `${plus5DateString}T18:29:59.999Z`,
              timeZone: Timezones["+5:30"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: todayDateString,
          });

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus1DateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus2DateString,
              doExactMatch: true,
            }
          );

          // No Timeslots beyond plus2Date as that is beyond the rolling period
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus3DateString,
          });

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus4DateString,
          });
        });
      });

      describe("GMT-11 Browsing", () => {
        test("When the time of the first slot of current day hasn't reached", async () => {
          // In IST timezone, it is 2024-05-31T07:00:00
          vi.setSystemTime("2024-05-31T01:30:00Z");
          const yesterdayDateString = "2024-05-30";
          const todayDateString = "2024-05-31";
          const plus1DateString = "2024-06-01";
          const plus2DateString = "2024-06-02";
          const plus3DateString = "2024-06-03";
          const plus5DateString = "2024-06-05";

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                ...getPeriodTypeData({
                  type: "ROLLING",
                  periodDays: 2,
                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEvent = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              // Because this time is in GMT, it will be 00:00 in IST with todayDateString
              startTime: `${yesterdayDateString}T18:30:00.000Z`,
              endTime: `${plus5DateString}T18:29:59.999Z`,
              timeZone: Timezones["-11:00"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          const allTimeSlotsForToday = [
            "2024-05-31T11:30:00.000Z",
            "2024-06-01T04:30:00.000Z",
            "2024-06-01T05:30:00.000Z",
            "2024-06-01T06:30:00.000Z",
            "2024-06-01T07:30:00.000Z",
            "2024-06-01T08:30:00.000Z",
            "2024-06-01T09:30:00.000Z",
            "2024-06-01T10:30:00.000Z",
          ];

          expect(scheduleForEvent).toHaveTimeSlots(
            [
              // "2024-05-30T04:30:00.000Z", // Not available as before the start of the range
              "2024-05-31T04:30:00.000Z",
              "2024-05-31T05:30:00.000Z",
              "2024-05-31T06:30:00.000Z",
              "2024-05-31T07:30:00.000Z",
              "2024-05-31T08:30:00.000Z",
              "2024-05-31T09:30:00.000Z",
              "2024-05-31T10:30:00.000Z",
            ],
            {
              dateString: yesterdayDateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            // All slots on current day are available
            allTimeSlotsForToday,
            {
              dateString: todayDateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            replaceDates(allTimeSlotsForToday, {
              "2024-05-31": "2024-06-01",
              "2024-06-01": "2024-06-02",
            }),
            {
              dateString: plus1DateString,
              doExactMatch: true,
            }
          );

          //No Timeslots beyond plus2Date as that is beyond the rolling period
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus2DateString,
          });

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus3DateString,
          });
        });
      });
    });

    describe("PeriodType=ROLLING_WINDOW", () => {
      test("When the time of the first slot of current day hasn't reached and there is a day fully booked in between. It makes `periodDays` available", async () => {
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING_WINDOW",
                periodDays: 3,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
          bookings: [
            {
              userId: 101,
              eventTypeId: 1,
              status: "ACCEPTED",
              // Fully book plus2 Date
              startTime: `${plus1DateString}T18:30:00.000Z`,
              endTime: `${plus2DateString}T18:30:00.000Z`,
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        // plus2Date is fully booked. So, instead we will have timeslots one day later
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus2DateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus3DateString,
            doExactMatch: true,
          }
        );

        // No Timeslots on plus4Date as beyond the rolling period
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });
      });

      test("When the time of the last slot of current day has passed and there is a day fully booked in between. It makes `periodDays` available", async () => {
        // In IST timezone, it is 2024-05-31T07:00:00
        vi.setSystemTime("2024-05-31T11:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING_WINDOW",
                periodDays: 3,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
          bookings: [
            {
              userId: 101,
              eventTypeId: 1,
              status: "ACCEPTED",
              // Fully book plus2 Date
              startTime: `${plus1DateString}T18:30:00.000Z`,
              endTime: `${plus2DateString}T18:30:00.000Z`,
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: todayDateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        // plus2Date is fully booked. So, instead we will have timeslots one day later
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus2DateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus3DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus4DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus5DateString,
        });
      });

      test("When the first timeslot of current day has passed and there is a day fully booked in between. It makes `periodDays` available", async () => {
        // In IST timezone, it is 2024-05-31T10:00
        vi.setSystemTime("2024-05-31T04:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING_WINDOW",
                periodDays: 3,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
          bookings: [
            {
              userId: 101,
              eventTypeId: 1,
              status: "ACCEPTED",
              // Fully book plus2 Date
              startTime: `${plus1DateString}T18:30:00.000Z`,
              endTime: `${plus2DateString}T18:30:00.000Z`,
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430.slice(1),
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        // plus2Date is fully booked. So, instead we will have timeslots one day later
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus2DateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus3DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus5DateString,
        });
      });

      test("When the time of the first slot of current day hasn't reached and there is a day fully booked in between. Also, we are counting only business days. It makes weekends + `periodDays` available", async () => {
        // In Ist timezone, it is 2024-05-31T01:30:00 which is a Friday
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        // Saturday
        const plus1DateString = "2024-06-01";
        // Sunday
        const plus2DateString = "2024-06-02";
        // Monday
        const plus3DateString = "2024-06-03";
        // Tuesday
        const plus4DateString = "2024-06-04";
        // Wednesday
        const plus5DateString = "2024-06-05";
        // Thursday
        const plus6DateString = "2024-06-06";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING_WINDOW",
                periodDays: 3,
                periodCountCalendarDays: false,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
          bookings: [
            {
              userId: 101,
              eventTypeId: 1,
              status: "ACCEPTED",
              // Fully book plus3 Date
              startTime: `${plus2DateString}T18:30:00.000Z`,
              endTime: `${plus3DateString}T18:30:00.000Z`,
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        // Day1 of periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        // plus1Date is a Saturday and available as per Availability but not counted in periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        // plus2Date is a Sunday and available as per Availability but not counted in periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // plus3Date is fully booked. So, instead we will have timeslots one day later
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        // Day2 of periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus4DateString,
            doExactMatch: true,
          }
        );

        // Day3 of periodDays
        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus5DateString,
            doExactMatch: true,
          }
        );

        // Beyond periodDays
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus6DateString,
        });
      });

      describe("Borderline cases", () => {
        test("When it is the very first second of the day", async () => {
          // In IST timezone, it is 2024-05-31T00:00
          vi.setSystemTime("2024-05-30T18:30:00Z");
          const yesterdayDateString = "2024-05-30";
          const todayDateString = "2024-05-31";
          const plus1DateString = "2024-06-01";
          const plus2DateString = "2024-06-02";
          const plus3DateString = "2024-06-03";
          const plus4DateString = "2024-06-04";
          const plus5DateString = "2024-06-05";

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                ...getPeriodTypeData({
                  type: "ROLLING_WINDOW",
                  periodDays: 3,
                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
            bookings: [
              {
                userId: 101,
                eventTypeId: 1,
                status: "ACCEPTED",
                // Fully book plus2 Date
                startTime: `${plus1DateString}T18:30:00.000Z`,
                endTime: `${plus2DateString}T18:30:00.000Z`,
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEvent = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              // Because this time is in GMT, it will be 00:00 in IST with todayDateString
              startTime: `${yesterdayDateString}T18:30:00.000Z`,
              endTime: `${plus5DateString}T18:29:59.999Z`,
              timeZone: Timezones["+5:30"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: todayDateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus1DateString,
              doExactMatch: true,
            }
          );

          // plus2Date is fully booked. So, instead we will have timeslots one day later
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus2DateString,
          });

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus3DateString,
              doExactMatch: true,
            }
          );

          // No Timeslots on plus4Date as beyond the rolling period
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus4DateString,
          });
        });
        test("When it is the very last second of the day", async () => {
          // In IST timezone, it is 2024-05-31T23:59
          vi.setSystemTime("2024-05-31T18:29:00Z");
          const yesterdayDateString = "2024-05-30";
          const todayDateString = "2024-05-31";
          const plus1DateString = "2024-06-01";
          const plus2DateString = "2024-06-02";
          const plus3DateString = "2024-06-03";
          const plus4DateString = "2024-06-04";
          const plus5DateString = "2024-06-05";

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                ...getPeriodTypeData({
                  type: "ROLLING_WINDOW",
                  periodDays: 3,
                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
            bookings: [
              {
                userId: 101,
                eventTypeId: 1,
                status: "ACCEPTED",
                // Fully book plus2 Date
                startTime: `${plus1DateString}T18:30:00.000Z`,
                endTime: `${plus2DateString}T18:30:00.000Z`,
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEvent = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              // Because this time is in GMT, it will be 00:00 in IST with todayDateString
              startTime: `${yesterdayDateString}T18:30:00.000Z`,
              endTime: `${plus5DateString}T18:29:59.999Z`,
              timeZone: Timezones["+5:30"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: todayDateString,
          });

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus1DateString,
              doExactMatch: true,
            }
          );

          // plus2Date is fully booked. So, instead we will have timeslots one day later
          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus2DateString,
          });

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus3DateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveTimeSlots(
            expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
            {
              dateString: plus4DateString,
              doExactMatch: true,
            }
          );

          expect(scheduleForEvent).toHaveDateDisabled({
            dateString: plus5DateString,
          });
        });
      });

      test("When the time of the first slot of current day hasn't reached and there is a day fully booked in between. Also, weekend availability is not there and one of the days is on a weekend. It makes `periodDays` available", async () => {
        // In Ist timezone, it is 2024-05-31T01:30:00 which is a Friday
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        // Saturday
        const plus1DateString = "2024-06-01";
        // Sunday
        const plus2DateString = "2024-06-02";
        // Monday
        const plus3DateString = "2024-06-03";
        // Tuesday
        const plus4DateString = "2024-06-04";
        // Wednesday
        const plus5DateString = "2024-06-05";
        // Thursday
        const plus6DateString = "2024-06-06";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              ...getPeriodTypeData({
                type: "ROLLING_WINDOW",
                periodDays: 3,
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHoursNoWeekends],
            },
          ],
          bookings: [
            {
              userId: 101,
              eventTypeId: 1,
              status: "ACCEPTED",
              // Fully book plus3 Date
              startTime: `${plus2DateString}T18:30:00.000Z`,
              endTime: `${plus3DateString}T18:30:00.000Z`,
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: todayDateString,
            doExactMatch: true,
          }
        );

        // plus1Date is a Saturday and not available
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus1DateString,
        });

        // plus2Date is a Sunday and not available
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus2DateString,
        });

        // plus3Date is fully booked. So, instead we will have timeslots one day later
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus4DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus5DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus6DateString,
        });
      });
    });

    describe("PeriodType=RANGE", () => {
      test("Basic test", async () => {
        vi.setSystemTime("2024-05-31T01:30:00Z");
        const yesterdayDateString = "2024-05-30";
        const todayDateString = "2024-05-31";
        const plus1DateString = "2024-06-01";
        const plus2DateString = "2024-06-02";
        const plus3DateString = "2024-06-03";
        const plus4DateString = "2024-06-04";
        const plus5DateString = "2024-06-05";

        const scenarioData = {
          eventTypes: [
            {
              id: 1,
              length: 60,
              // Makes today and tomorrow only available
              ...getPeriodTypeData({
                type: "RANGE",
                // dayPlus1InIst
                periodStartDate: new Date(`${todayDateString}T18:30:00.000Z`),
                // datePlus2InIst
                periodEndDate: new Date(`${plus1DateString}T18:30:00.000Z`),
                periodCountCalendarDays: true,
              }),
              users: [
                {
                  id: 101,
                },
              ],
            },
          ],
          users: [
            {
              ...TestData.users.example,
              id: 101,
              schedules: [TestData.schedules.IstWorkHours],
            },
          ],
        } satisfies ScenarioData;

        await createBookingScenario(scenarioData);

        const scheduleForEvent = await getSchedule({
          input: {
            eventTypeId: 1,
            eventTypeSlug: "",
            usernameList: [],
            // Because this time is in GMT, it will be 00:00 in IST with todayDateString
            startTime: `${yesterdayDateString}T18:30:00.000Z`,
            endTime: `${plus5DateString}T18:29:59.999Z`,
            timeZone: Timezones["+5:30"],
            isTeamEvent: false,
            orgSlug: null,
          },
        });

        // Before the start of the range
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: todayDateString,
        });

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus1DateString,
            doExactMatch: true,
          }
        );

        expect(scheduleForEvent).toHaveTimeSlots(
          expectedSlotsForSchedule["IstWorkHours"].interval["1hr"].allPossibleSlotsStartingAt430,
          {
            dateString: plus2DateString,
            doExactMatch: true,
          }
        );

        // After the range ends
        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus3DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });

        expect(scheduleForEvent).toHaveDateDisabled({
          dateString: plus4DateString,
        });
      });

      describe("GMT-11 Browsing", () => {
        test("should show correct timeslots only for 24th and 25th July(of IST Timezone)", async () => {
          vi.setSystemTime(`2024-07-05T01:30:00Z`);

          const scenarioData = {
            eventTypes: [
              {
                id: 1,
                length: 60,
                // Makes today and tomorrow only available
                ...getPeriodTypeData({
                  type: "RANGE",

                  // Only 25th and 26th(as per the event timezone(IST)) should be available
                  periodStartDate: new Date(`2024-07-24T18:30:00.000Z`), // 25th July in IST
                  periodEndDate: new Date(`2024-07-25T18:30:00.000Z`), // 26th July in IST

                  periodCountCalendarDays: true,
                }),
                users: [
                  {
                    id: 101,
                  },
                ],
              },
            ],
            users: [
              {
                ...TestData.users.example,
                id: 101,
                schedules: [TestData.schedules.IstWorkHours],
              },
            ],
          } satisfies ScenarioData;

          await createBookingScenario(scenarioData);

          const scheduleForEventForPagoTz = await getSchedule({
            input: {
              eventTypeId: 1,
              eventTypeSlug: "",
              usernameList: [],
              startTime: `2024-06-30T18:30:00.000Z`,
              endTime: `2024-07-31T18:29:59.999Z`,
              timeZone: Timezones["-11:00"],
              isTeamEvent: false,
              orgSlug: null,
            },
          });

          /**
           * Current day in test is 5th July, so verify that earlier timeslots than 24th July are disabled
           */
          {
            expect(scheduleForEventForPagoTz).toHaveDateDisabled({
              dateString: "2024-07-21",
            });

            expect(scheduleForEventForPagoTz).toHaveDateDisabled({
              dateString: "2024-07-22",
            });

            expect(scheduleForEventForPagoTz).toHaveDateDisabled({
              dateString: "2024-07-23",
            });
          }

          expect(scheduleForEventForPagoTz).toHaveTimeSlots(
            [
              "2024-07-25T04:30:00.000Z",
              "2024-07-25T05:30:00.000Z",
              "2024-07-25T06:30:00.000Z",
              "2024-07-25T07:30:00.000Z",
              "2024-07-25T08:30:00.000Z",
              "2024-07-25T09:30:00.000Z",
              "2024-07-25T10:30:00.000Z",
            ],
            {
              // 25th timeslots are shown mostly on 24th of Pago Pago
              dateString: "2024-07-24",
              doExactMatch: true,
            }
          );

          expect(scheduleForEventForPagoTz).toHaveTimeSlots(
            [
              "2024-07-25T11:30:00.000Z",
              "2024-07-26T04:30:00.000Z",
              "2024-07-26T05:30:00.000Z",
              "2024-07-26T06:30:00.000Z",
              "2024-07-26T07:30:00.000Z",
              "2024-07-26T08:30:00.000Z",
              "2024-07-26T09:30:00.000Z",
              "2024-07-26T10:30:00.000Z",
            ],
            {
              dateString: "2024-07-25",
              doExactMatch: true,
            }
          );

          expect(scheduleForEventForPagoTz).toHaveTimeSlots(["2024-07-26T11:30:00.000Z"], {
            dateString: "2024-07-26",
            doExactMatch: true,
          });

          /**
           * Verify that timeslots beyond 26th July are disabled
           */
          {
            expect(scheduleForEventForPagoTz).toHaveDateDisabled({
              dateString: "2024-07-27",
            });

            expect(scheduleForEventForPagoTz).toHaveDateDisabled({
              dateString: "2024-07-28",
            });
          }
        });
      });
    });
  });
});
