import { describe, it, expect, beforeEach } from "vitest";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";

import type { BookingWebhookEventDTO } from "../../dto/types";
import { PayloadBuilderFactory, type PayloadBuilderSet } from "./PayloadBuilderFactory";
import * as V2021_10_20 from "./v2021-10-20";
import { WebhookVersion as WebhookVersionEnum } from "../../interface/IWebhookRepository";

describe("PayloadBuilderFactory", () => {
  let factory: PayloadBuilderFactory;
  let defaultBuilders: PayloadBuilderSet;

  beforeEach(() => {
    defaultBuilders = {
      booking: new V2021_10_20.BookingPayloadBuilder(),
      ooo: new V2021_10_20.OOOPayloadBuilder(),
      recording: new V2021_10_20.RecordingPayloadBuilder(),
      meeting: new V2021_10_20.MeetingPayloadBuilder(),
    };

    factory = new PayloadBuilderFactory(WebhookVersionEnum.V_2021_10_20, defaultBuilders);
  });

  describe("Constructor", () => {
    it("should initialize with default version and builders", () => {
      expect(factory).toBeInstanceOf(PayloadBuilderFactory);
      expect(factory.getRegisteredVersions()).toContain(WebhookVersionEnum.V_2021_10_20);
    });

    it("should enforce required parameters at compile-time", () => {
      // TypeScript ensures parameters are provided - no runtime check needed
      expect(factory).toBeDefined();
      expect(factory.getRegisteredVersions()).toHaveLength(1);
    });
  });

  describe("Version Registration", () => {
    it("should register new version with complete builder set", () => {
      const newVersionBuilders: PayloadBuilderSet = {
        booking: new V2021_10_20.BookingPayloadBuilder(),
        ooo: new V2021_10_20.OOOPayloadBuilder(),
        recording: new V2021_10_20.RecordingPayloadBuilder(),
        meeting: new V2021_10_20.MeetingPayloadBuilder(),
      };

      factory.registerVersion("2024-12-01", newVersionBuilders);

      expect(factory.getRegisteredVersions()).toContain("2024-12-01");
      expect(factory.getRegisteredVersions()).toHaveLength(2);
    });

    it("should allow overwriting existing version", () => {
      const newBuilders: PayloadBuilderSet = {
        booking: new V2021_10_20.BookingPayloadBuilder(),
        ooo: new V2021_10_20.OOOPayloadBuilder(),
        recording: new V2021_10_20.RecordingPayloadBuilder(),
        meeting: new V2021_10_20.MeetingPayloadBuilder(),
      };

      factory.registerVersion(WebhookVersionEnum.V_2021_10_20, newBuilders);

      expect(factory.getRegisteredVersions()).toHaveLength(1);
    });
  });

  describe("Builder Routing", () => {
    it("should route booking events to booking builder", () => {
      const bookingTriggers = [
        WebhookTriggerEvents.BOOKING_CREATED,
        WebhookTriggerEvents.BOOKING_CANCELLED,
        WebhookTriggerEvents.BOOKING_REQUESTED,
        WebhookTriggerEvents.BOOKING_RESCHEDULED,
        WebhookTriggerEvents.BOOKING_REJECTED,
        WebhookTriggerEvents.BOOKING_PAID,
        WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED,
        WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED,
      ];

      bookingTriggers.forEach((trigger) => {
        const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
        expect(builder).toBe(defaultBuilders.booking);
      });
    });

    it("should route OOO events to OOO builder", () => {
      const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, WebhookTriggerEvents.OOO_CREATED);
      expect(builder).toBe(defaultBuilders.ooo);
    });

    it("should route recording events to recording builder", () => {
      const recordingTriggers = [
        WebhookTriggerEvents.RECORDING_READY,
        WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED,
      ];

      recordingTriggers.forEach((trigger) => {
        const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
        expect(builder).toBe(defaultBuilders.recording);
      });
    });

    it("should route meeting events to meeting builder", () => {
      const meetingTriggers = [WebhookTriggerEvents.MEETING_STARTED, WebhookTriggerEvents.MEETING_ENDED];

      meetingTriggers.forEach((trigger) => {
        const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
        expect(builder).toBe(defaultBuilders.meeting);
      });
    });

  });

  describe("Fallback Behavior", () => {
    it("should fallback to default version when requested version not found", () => {
      const builder = factory.getBuilder(
        WebhookVersionEnum.V_2099_99_99,
        WebhookTriggerEvents.BOOKING_CREATED
      );

      // Should get default builder, not throw
      expect(builder).toBe(defaultBuilders.booking);
    });

    it("should log warning when falling back to default", () => {
      // Just verify it doesn't throw - logging is tested elsewhere
      expect(() => {
        factory.getBuilder("invalid-version", WebhookTriggerEvents.BOOKING_CREATED);
      }).not.toThrow();
    });

    it("should never return undefined builder", () => {
      const unknownVersion = WebhookVersionEnum.V_2099_99_99;
      const builder = factory.getBuilder(unknownVersion, WebhookTriggerEvents.BOOKING_CREATED);

      expect(builder).toBeDefined();
      expect(builder).toBe(defaultBuilders.booking);
    });
  });

  describe("Type Safety", () => {
    it("should return correctly typed builder for each trigger", () => {
      // These type assertions verify compile-time type safety
      const bookingBuilder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.BOOKING_CREATED
      );
      const oooBuilder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.OOO_CREATED
      );
      const recordingBuilder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.RECORDING_READY
      );
      const meetingBuilder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.MEETING_STARTED
      );

      // Runtime verification
      expect(bookingBuilder).toBeDefined();
      expect(oooBuilder).toBeDefined();
      expect(recordingBuilder).toBeDefined();
      expect(meetingBuilder).toBeDefined();
    });

    it("should build valid payload with correctly typed DTO", () => {
      const mockDTO: BookingWebhookEventDTO = {
        triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
        createdAt: "2024-01-15T10:00:00Z",
        booking: {
          id: 1,
          eventTypeId: 1,
          userId: 1,
          smsReminderNumber: null,
        },
        eventType: {
          eventTitle: "Test Event",
          eventDescription: "Test Description",
          requiresConfirmation: false,
          price: 0,
          currency: "USD",
          length: 30,
        },
        evt: {
          type: "test-event",
          title: "Test Meeting",
          description: "Meeting description",
          startTime: "2024-01-15T10:00:00Z",
          endTime: "2024-01-15T10:30:00Z",
          organizer: {
            id: 1,
            email: "organizer@test.com",
            name: "Test Organizer",
            timeZone: "UTC",
            language: { locale: "en" },
          },
          attendees: [
            {
              email: "attendee@test.com",
              name: "Test Attendee",
              timeZone: "UTC",
              language: { locale: "en" },
            },
          ],
          uid: "booking-uid-123",
          customInputs: {},
          responses: {},
          userFieldsResponses: {},
        },
      };

      const builder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.BOOKING_CREATED
      );
      const payload = builder.build(mockDTO);

      expect(payload).toBeDefined();
      expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_CREATED);
      expect(payload.payload).toBeDefined();
    });
  });

  describe("Multiple Versions", () => {
    it("should support multiple versions simultaneously", () => {
      const v2Builders: PayloadBuilderSet = {
        booking: new V2021_10_20.BookingPayloadBuilder(),
        ooo: new V2021_10_20.OOOPayloadBuilder(),
        recording: new V2021_10_20.RecordingPayloadBuilder(),
        meeting: new V2021_10_20.MeetingPayloadBuilder(),
      };

      factory.registerVersion(WebhookVersionEnum.V_2024_12_01, v2Builders);

      // Both versions should work
      const v1Builder = factory.getBuilder(
        WebhookVersionEnum.V_2021_10_20,
        WebhookTriggerEvents.BOOKING_CREATED
      );
      const v2Builder = factory.getBuilder(
        WebhookVersionEnum.V_2024_12_01,
        WebhookTriggerEvents.BOOKING_CREATED
      );

      expect(v1Builder).toBe(defaultBuilders.booking);
      expect(v2Builder).toBe(v2Builders.booking);
      expect(v1Builder).not.toBe(v2Builder); // Different instances
    });
  });
});
