import { prisma } from "@calcom/prisma";
import type { Booking, Credential } from "@calcom/prisma/client";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";

import { BookingReferenceRepository } from "./BookingReferenceRepository";

const testRunId = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;

let testUserId: number;
let testCredential: Credential;
let testBooking: Booking;
const createdBookingReferenceIds: number[] = [];
const createdBookingIds: number[] = [];

async function clearTestBookingReferences() {
  if (createdBookingReferenceIds.length > 0) {
    await prisma.bookingReference.deleteMany({
      where: {
        id: {
          in: createdBookingReferenceIds,
        },
      },
    });
    createdBookingReferenceIds.length = 0;
  }
}

async function clearTestBookings() {
  if (createdBookingIds.length > 0) {
    await prisma.bookingReference.deleteMany({
      where: {
        bookingId: {
          in: createdBookingIds,
        },
      },
    });
    await prisma.booking.deleteMany({
      where: {
        id: {
          in: createdBookingIds,
        },
      },
    });
    createdBookingIds.length = 0;
  }
}

describe("BookingReferenceRepository Integration Tests", () => {
  beforeAll(async () => {
    let testUser = await prisma.user.findFirst({
      where: { email: "member0-acme@example.com" },
    });

    if (!testUser) {
      testUser = await prisma.user.create({
        data: {
          email: `bookingreference-test-${testRunId}@example.com`,
          username: `bookingreference-test-${testRunId}`,
        },
      });
    }
    testUserId = testUser.id;

    let credential = await prisma.credential.findFirst({
      where: { userId: testUserId, type: "google_calendar" },
    });

    if (!credential) {
      credential = await prisma.credential.create({
        data: {
          type: "google_calendar",
          key: {},
          userId: testUserId,
        },
      });
    }
    testCredential = credential;
  });

  beforeEach(async () => {
    testBooking = await prisma.booking.create({
      data: {
        uid: `test-booking-uid-${testRunId}-${Date.now()}`,
        title: "Test Booking",
        startTime: new Date(),
        endTime: new Date(),
        userId: testUserId,
      },
    });
    createdBookingIds.push(testBooking.id);
  });

  afterEach(async () => {
    await clearTestBookingReferences();
    await clearTestBookings();
  });

  afterAll(async () => {
    await clearTestBookingReferences();
    await clearTestBookings();
  });

  describe("replaceBookingReferences", () => {
    it("should soft-delete existing references and create new ones", async () => {
      const existingReference1 = await prisma.bookingReference.create({
        data: {
          type: "google_calendar",
          uid: "old-ref-1",
          meetingId: "old-meeting-1",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(existingReference1.id);

      const existingReference2 = await prisma.bookingReference.create({
        data: {
          type: "google_calendar",
          uid: "old-ref-2",
          meetingId: "old-meeting-2",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(existingReference2.id);

      const newReferences = [
        {
          type: "google_calendar",
          uid: "new-ref-1",
          meetingId: "new-meeting-1",
          credentialId: testCredential.id,
        },
        {
          type: "google_calendar",
          uid: "new-ref-2",
          meetingId: "new-meeting-2",
          credentialId: testCredential.id,
        },
      ];

      await BookingReferenceRepository.replaceBookingReferences({
        bookingId: testBooking.id,
        newReferencesToCreate: newReferences,
      });

      const allReferences = await prisma.bookingReference.findMany({
        where: {
          bookingId: testBooking.id,
        },
      });

      const oldReferences = allReferences.filter((ref) => ref.uid === "old-ref-1" || ref.uid === "old-ref-2");
      const newlyCreatedReferences = allReferences.filter(
        (ref) => ref.uid === "new-ref-1" || ref.uid === "new-ref-2"
      );

      expect(oldReferences).toHaveLength(2);
      expect(oldReferences.every((ref) => ref.deleted === true)).toBe(true);

      expect(newlyCreatedReferences).toHaveLength(2);
      expect(newlyCreatedReferences.every((ref) => ref.deleted === null)).toBe(true);

      newlyCreatedReferences.forEach((ref) => {
        createdBookingReferenceIds.push(ref.id);
      });
    });

    it("should only soft-delete references of the same type as new references being created", async () => {
      const googleRef = await prisma.bookingReference.create({
        data: {
          type: "google_calendar",
          uid: "google-ref",
          meetingId: "google-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(googleRef.id);

      const zoomRef = await prisma.bookingReference.create({
        data: {
          type: "zoom_video",
          uid: "zoom-ref",
          meetingId: "zoom-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(zoomRef.id);

      const newGoogleReferences = [
        {
          type: "google_calendar",
          uid: "new-google-ref",
          meetingId: "new-google-meeting",
          credentialId: testCredential.id,
        },
      ];

      await BookingReferenceRepository.replaceBookingReferences({
        bookingId: testBooking.id,
        newReferencesToCreate: newGoogleReferences,
      });

      const googleRefAfter = await prisma.bookingReference.findUnique({
        where: { id: googleRef.id },
      });
      expect(googleRefAfter?.deleted).toBe(true);

      const zoomRefAfter = await prisma.bookingReference.findUnique({
        where: { id: zoomRef.id },
      });
      expect(zoomRefAfter?.deleted).toBe(null);

      const newRef = await prisma.bookingReference.findFirst({
        where: {
          uid: "new-google-ref",
          bookingId: testBooking.id,
        },
      });
      expect(newRef).toBeDefined();
      expect(newRef?.deleted).toBe(null);
      if (newRef) {
        createdBookingReferenceIds.push(newRef.id);
      }
    });

    it("should persist soft-deleted records in database", async () => {
      const reference = await prisma.bookingReference.create({
        data: {
          type: "daily_video",
          uid: "daily-ref",
          meetingId: "daily-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(reference.id);

      const newReferences = [
        {
          type: "daily_video",
          uid: "new-daily-ref",
          meetingId: "new-daily-meeting",
          credentialId: testCredential.id,
        },
      ];

      await BookingReferenceRepository.replaceBookingReferences({
        bookingId: testBooking.id,
        newReferencesToCreate: newReferences,
      });

      const deletedRefInDb = await prisma.bookingReference.findUnique({
        where: { id: reference.id },
      });
      expect(deletedRefInDb).toBeDefined();
      expect(deletedRefInDb?.deleted).toBe(true);
      expect(deletedRefInDb?.uid).toBe("daily-ref");

      const newRef = await prisma.bookingReference.findFirst({
        where: {
          uid: "new-daily-ref",
          bookingId: testBooking.id,
        },
      });
      if (newRef) {
        createdBookingReferenceIds.push(newRef.id);
      }
    });
  });

  describe("findDailyVideoReferenceByRoomName", () => {
    it("should find active daily video references", async () => {
      const roomName = "test-daily-room-123";
      const dailyRef = await prisma.bookingReference.create({
        data: {
          type: "daily_video",
          uid: roomName,
          meetingId: roomName,
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(dailyRef.id);

      const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
        roomName,
      });

      expect(result).toBeDefined();
      expect(result?.id).toBe(dailyRef.id);
      expect(result?.uid).toBe(roomName);
      expect(result?.deleted).toBe(null);
    });

    it("should exclude soft-deleted daily video references", async () => {
      const roomName = "test-daily-room-deleted-456";
      const dailyRef = await prisma.bookingReference.create({
        data: {
          type: "daily_video",
          uid: roomName,
          meetingId: roomName,
          credentialId: testCredential.id,
          bookingId: testBooking.id,
          deleted: true,
        },
      });
      createdBookingReferenceIds.push(dailyRef.id);

      const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
        roomName,
      });

      expect(result).toBeNull();
    });

    it("should return null when room name does not exist", async () => {
      const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
        roomName: "non-existent-room",
      });

      expect(result).toBeNull();
    });

    it("should not return daily video references without a booking", async () => {
      const roomName = "test-daily-room-no-booking";
      const dailyRef = await prisma.bookingReference.create({
        data: {
          type: "daily_video",
          uid: roomName,
          meetingId: roomName,
          credentialId: testCredential.id,
          bookingId: null,
        },
      });
      createdBookingReferenceIds.push(dailyRef.id);

      const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
        roomName,
      });

      expect(result).toBeNull();
    });
  });

  describe("Soft-delete behavior verification", () => {
    it("should not return soft-deleted references in regular queries", async () => {
      const activeRef = await prisma.bookingReference.create({
        data: {
          type: "zoom_video",
          uid: "active-zoom",
          meetingId: "active-zoom-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
        },
      });
      createdBookingReferenceIds.push(activeRef.id);

      const deletedRef = await prisma.bookingReference.create({
        data: {
          type: "zoom_video",
          uid: "deleted-zoom",
          meetingId: "deleted-zoom-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
          deleted: true,
        },
      });
      createdBookingReferenceIds.push(deletedRef.id);

      const foundReferences = await prisma.bookingReference.findMany({
        where: {
          bookingId: testBooking.id,
          type: "zoom_video",
          deleted: null,
        },
      });

      expect(foundReferences).toHaveLength(1);
      expect(foundReferences[0].id).toBe(activeRef.id);
      expect(foundReferences[0].uid).toBe("active-zoom");
    });

    it("should allow querying soft-deleted references explicitly", async () => {
      const deletedRef = await prisma.bookingReference.create({
        data: {
          type: "office365_calendar",
          uid: "deleted-office365",
          meetingId: "deleted-office365-meeting",
          credentialId: testCredential.id,
          bookingId: testBooking.id,
          deleted: true,
        },
      });
      createdBookingReferenceIds.push(deletedRef.id);

      const foundDeletedReferences = await prisma.bookingReference.findMany({
        where: {
          bookingId: testBooking.id,
          deleted: true,
        },
      });

      expect(foundDeletedReferences.length).toBeGreaterThan(0);
      const ourDeletedRef = foundDeletedReferences.find((ref) => ref.id === deletedRef.id);
      expect(ourDeletedRef).toBeDefined();
      expect(ourDeletedRef?.uid).toBe("deleted-office365");
    });
  });
});
