import { expect } from "@playwright/test";
import type { Page } from "@playwright/test";

import dayjs from "@calcom/dayjs";
import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import type { CredentialPayload } from "@calcom/types/Credential";
import { test } from "@calcom/web/playwright/lib/fixtures";
import { selectSecondAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils";

import metadata from "../_metadata";
import GoogleCalendarService from "../lib/CalendarService";
import { createBookingAndFetchGCalEvent, deleteBookingAndEvent, assertValueExists } from "./testUtils";

test.describe("Google Calendar", async () => {
  // Skip till the tests are flaky
  // eslint-disable-next-line playwright/no-skipped-test
  test.describe.skip("Test using the primary calendar", async () => {
    let qaUsername: string;
    let qaGCalCredential: CredentialPayload;
    test.beforeAll(async () => {
      let runIntegrationTest = false;
      const errorMessage = "Could not run test";

      test.skip(!!APP_CREDENTIAL_SHARING_ENABLED, "Credential sharing enabled");

      if (process.env.E2E_TEST_CALCOM_GCAL_KEYS) {
        const gCalKeys = JSON.parse(process.env.E2E_TEST_CALCOM_GCAL_KEYS);
        await prisma.app.update({
          where: {
            slug: "google-calendar",
          },
          data: {
            keys: gCalKeys,
          },
        });
      } else {
        test.skip(!process.env.E2E_TEST_CALCOM_GCAL_KEYS, "GCal keys not found");
      }

      test.skip(!process.env.E2E_TEST_CALCOM_QA_EMAIL, "QA email not found");
      test.skip(!process.env.E2E_TEST_CALCOM_QA_PASSWORD, "QA password not found");

      if (process.env.E2E_TEST_CALCOM_QA_EMAIL && process.env.E2E_TEST_CALCOM_QA_PASSWORD) {
        qaGCalCredential = await prisma.credential.findFirstOrThrow({
          where: {
            user: {
              email: process.env.E2E_TEST_CALCOM_QA_EMAIL,
            },
            type: metadata.type,
          },
          include: {
            user: {
              select: {
                email: true,
              },
            },
          },
        });
        test.skip(!qaGCalCredential, "Google QA credential not found");

        const qaUserQuery = await prisma.user.findFirstOrThrow({
          where: {
            email: process.env.E2E_TEST_CALCOM_QA_EMAIL,
          },
          select: {
            id: true,
            username: true,
          },
        });

        test.skip(!qaUserQuery, "QA user not found");

        assertValueExists(qaUserQuery.username, "qaUsername");
        qaUsername = qaUserQuery.username;

        test.skip(!qaUsername, "QA username not found");

        const googleCalendarService = new GoogleCalendarService(qaGCalCredential);

        const calendars = await googleCalendarService.listCalendars();

        const primaryCalendarName = calendars.find((calendar) => calendar.primary)?.name;
        assertValueExists(primaryCalendarName, "primaryCalendarName");

        await prisma.destinationCalendar.upsert({
          where: {
            userId: qaUserQuery.id,
            externalId: primaryCalendarName,
            eventTypeId: undefined,
          },
          update: {},
          create: {
            integration: "google_calendar",
            userId: qaUserQuery.id,
            externalId: primaryCalendarName,
            credentialId: qaGCalCredential.id,
          },
        });

        if (qaGCalCredential && qaUsername) runIntegrationTest = true;
      }

      test.skip(!runIntegrationTest, errorMessage);
    });

    test("On new booking, event should be created on GCal", async ({ page }) => {
      const { gCalEvent, gCalReference, booking, authedCalendar } = await createBookingAndFetchGCalEvent(
        page as Page,
        qaGCalCredential,
        qaUsername
      );

      assertValueExists(gCalEvent.start?.timeZone, "gCalEvent");
      assertValueExists(gCalEvent.end?.timeZone, "gCalEvent");

      // Ensure that the start and end times are matching
      const startTimeMatches = dayjs(booking.startTime).isSame(
        dayjs(gCalEvent.start.dateTime).tz(gCalEvent.start.timeZone)
      );
      const endTimeMatches = dayjs(booking.endTime).isSame(
        dayjs(gCalEvent.end?.dateTime).tz(gCalEvent.end.timeZone)
      );
      expect(startTimeMatches && endTimeMatches).toBe(true);

      // Ensure that the titles are matching
      expect(booking.title).toBe(gCalEvent.summary);

      // Ensure that the attendee is on the event
      const bookingAttendee = booking?.attendees[0].email;
      const attendeeInGCalEvent = gCalEvent.attendees?.find((attendee) => attendee.email === bookingAttendee);
      expect(attendeeInGCalEvent).toBeTruthy();

      await deleteBookingAndEvent(authedCalendar, booking.uid, gCalReference.uid);
    });

    test("On reschedule, event should be updated on GCal", async ({ page }) => {
      // Reschedule the booking and check the gCalEvent's time is also changed
      // On reschedule gCal UID stays the same
      const { gCalReference, booking, authedCalendar } = await createBookingAndFetchGCalEvent(
        page,
        qaGCalCredential,
        qaUsername
      );

      await page.locator('[data-testid="reschedule-link"]').click();

      await selectSecondAvailableTimeSlotNextMonth(page);
      await page.locator('[data-testid="confirm-reschedule-button"]').click();

      await expect(page.locator("[data-testid=success-page]")).toBeVisible();

      const rescheduledBookingUrl = await page.url();
      const rescheduledBookingUid = rescheduledBookingUrl.match(/booking\/([^\/?]+)/);

      assertValueExists(rescheduledBookingUid, "rescheduledBookingUid");

      // Get the rescheduled booking start and end times
      const rescheduledBooking = await prisma.booking.findFirst({
        where: {
          uid: rescheduledBookingUid[1],
        },
        select: {
          startTime: true,
          endTime: true,
        },
      });
      assertValueExists(rescheduledBooking, "rescheduledBooking");

      // The GCal event UID persists after reschedule but should get the rescheduled data
      const gCalRescheduledEventResponse = await authedCalendar.events.get({
        calendarId: "primary",
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        //@ts-ignore
        eventId: gCalReference.uid,
      });

      expect(gCalRescheduledEventResponse.status).toBe(200);

      const rescheduledGCalEvent = gCalRescheduledEventResponse.data;

      assertValueExists(rescheduledGCalEvent.start?.timeZone, "rescheduledGCalEvent");
      assertValueExists(rescheduledGCalEvent.end?.timeZone, "rescheduledGCalEvent");

      // Ensure that the new start and end times are matching
      const rescheduledStartTimeMatches = dayjs(rescheduledBooking.startTime).isSame(
        dayjs(rescheduledGCalEvent.start?.dateTime).tz(rescheduledGCalEvent.start?.timeZone)
      );
      const rescheduledEndTimeMatches = dayjs(rescheduledBooking.endTime).isSame(
        dayjs(rescheduledGCalEvent.end?.dateTime).tz(rescheduledGCalEvent.end.timeZone)
      );
      expect(rescheduledStartTimeMatches && rescheduledEndTimeMatches).toBe(true);

      // After test passes we can delete the bookings and GCal event
      await deleteBookingAndEvent(authedCalendar, booking.uid, gCalReference.uid);

      await prisma.booking.delete({
        where: {
          uid: rescheduledBookingUid[1],
        },
      });
    });

    test("When canceling the booking, the GCal event should also be deleted", async ({ page }) => {
      const { gCalReference, booking, authedCalendar } = await createBookingAndFetchGCalEvent(
        page,
        qaGCalCredential,
        qaUsername
      );

      // Cancel the booking
      await page.locator('[data-testid="cancel"]').click();
      await page.locator('[data-testid="confirm_cancel"]').click();
      // Query for the bookingUID and ensure that it doesn't exist on GCal

      await page.waitForSelector('[data-testid="cancelled-headline"]');

      const canceledGCalEventResponse = await authedCalendar.events.get({
        calendarId: "primary",
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        //@ts-ignore
        eventId: gCalReference.uid,
      });

      expect(canceledGCalEventResponse.data.status).toBe("cancelled");

      // GCal API sees canceled events as already deleted
      await deleteBookingAndEvent(authedCalendar, booking.uid);
    });
  });
});
