import * as CalendarApi from "@/features/Calendars/CalendarApi";
import * as eventThunks from "@/features/Calendars/services";
import * as EventApi from "@/features/Events/EventApi";
import { CalendarEvent } from "@/features/Events/EventsTypes";
import EventUpdateModal from "@/features/Events/EventUpdateModal";
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../utils/Renderwithproviders";

jest.mock("@/features/Events/EventApi");
jest.mock("@/features/Calendars/CalendarApi");

describe("EventUpdateModal - Recurring Event 'Edit All' Handling", () => {
  const mockOnClose = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
    sessionStorage.clear();
  });

  const baseUID = "recurring-event-base";
  const calId = "667037022b752d0026472254/cal1";

  const preloadedState = {
    user: {
      userData: {
        sub: "test",
        email: "test@test.com",
        sid: "test-sid",
        openpaasId: "667037022b752d0026472254",
      },
      organiserData: {
        cn: "test",
        cal_address: "test@test.com",
      },
    },
    calendars: {
      list: {
        [calId]: {
          id: calId,
          name: "Test Calendar",
          color: "#FF0000",
          events: {},
          ownerEmails: ["test@test.com"],
        },
      },
      pending: false,
    },
  };

  describe("Master Event Display", () => {
    it("should fetch and display master event when editing 'all events' of a recurring series", async () => {
      // Given a recurring series with modified instances
      const masterEvent = {
        uid: baseUID,
        title: "Master Event Title",
        description: "Master Description",
        calId,
        start: "2025-01-15T10:00:00.000Z",
        end: "2025-01-15T11:00:00.000Z",
        repetition: { freq: "daily", interval: 1 },
        allday: false,
        timezone: "America/New_York",
        organizer: { cn: "test", cal_address: "test@test.com" },
        attendee: [{ cn: "test", cal_address: "test@test.com" }],
        URL: `/calendars/${calId}/${baseUID}.ics`,
      } as CalendarEvent;

      // Instance that was moved to different time
      const modifiedInstance = {
        ...masterEvent,
        uid: `${baseUID}/20250116`,
        title: "Modified Instance Title", // Different title
        start: "2025-01-16T14:00:00.000Z", // Different time (2PM instead of 10AM)
        end: "2025-01-16T15:00:00.000Z",
      };

      const stateWithRecurringEvent = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [baseUID]: masterEvent,
                [`${baseUID}/20250116`]: modifiedInstance,
              },
            },
          },
        },
      };

      // Mock getEvent to return master event
      const mockGetEvent = jest
        .spyOn(EventApi, "getEvent")
        .mockResolvedValue(masterEvent);

      renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={`${baseUID}/20250116`} // User clicked on modified instance
          typeOfAction="all"
        />,
        stateWithRecurringEvent
      );

      // Wait for master event to be fetched
      await waitFor(() => {
        expect(mockGetEvent).toHaveBeenCalledWith(
          expect.objectContaining({
            uid: baseUID, // Should fetch base UID, not instance
          }),
          true
        );
      });

      // Should display master event data, NOT modified instance data
      await waitFor(() => {
        const titleInput = screen.getByDisplayValue("Master Event Title");
        expect(titleInput).toBeInTheDocument();
      });

      // Should NOT show modified instance title
      expect(
        screen.queryByDisplayValue("Modified Instance Title")
      ).not.toBeInTheDocument();

      // Verify timezone dropdown shows master timezone
      await waitFor(() => {
        expect(screen.getByDisplayValue(/New York/i)).toBeDefined();
      });
    });

    it("should use master event directly if clicked event is already the master", async () => {
      const masterEvent = {
        uid: baseUID, // No recurrence-id
        title: "Master Event",
        calId,
        start: "2025-01-15T10:00:00.000Z",
        end: "2025-01-15T11:00:00.000Z",
        repetition: { freq: "weekly", interval: 1 },
        allday: false,
        organizer: { cn: "test", cal_address: "test@test.com" },
        URL: `/calendars/${calId}/${baseUID}.ics`,
      };

      const stateWithMaster = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [baseUID]: masterEvent,
              },
            },
          },
        },
      };

      const mockGetEvent = jest.spyOn(EventApi, "getEvent");

      renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={baseUID} // Already master event
          typeOfAction="all"
        />,
        stateWithMaster
      );

      await waitFor(() => {
        expect(screen.getByDisplayValue("Master Event")).toBeInTheDocument();
      });

      // Should NOT fetch from API since we already have master
      expect(mockGetEvent).not.toHaveBeenCalled();
    });

    it("should fallback to instance data if master event fetch fails", async () => {
      const instance = {
        uid: `${baseUID}/20250115`,
        title: "Instance Event",
        calId,
        start: "2025-01-15T10:00:00.000Z",
        end: "2025-01-15T11:00:00.000Z",
        repetition: { freq: "daily", interval: 1 },
        allday: false,
        organizer: { cn: "test", cal_address: "test@test.com" },
        URL: `/calendars/${calId}/${baseUID}.ics`,
      } as CalendarEvent;

      const stateWithInstance = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [`${baseUID}/20250115`]: instance,
              },
            },
          },
        },
      };

      // Mock getEvent to fail
      jest
        .spyOn(EventApi, "getEvent")
        .mockRejectedValue(new Error("Network error"));

      const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();

      renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={`${baseUID}/20250115`}
          typeOfAction="all"
        />,
        stateWithInstance
      );

      // Should still display the instance data as fallback
      await waitFor(() => {
        expect(screen.getByDisplayValue("Instance Event")).toBeInTheDocument();
      });

      expect(consoleErrorSpy).toHaveBeenCalledWith(
        "Failed to fetch master event:",
        expect.any(Error)
      );

      consoleErrorSpy.mockRestore();
    });
  });

  describe("Update All Events - Using Base UID", () => {
    it("should use base UID (not instance UID) when updating series with property changes", async () => {
      const masterEvent = {
        uid: baseUID,
        title: "Weekly Meeting",
        description: "Original description",
        calId,
        start: "2025-01-15T10:00:00.000Z",
        end: "2025-01-15T11:00:00.000Z",
        repetition: { freq: "weekly", interval: 1 },
        allday: false,
        organizer: { cn: "test", cal_address: "test@test.com" },
        URL: `/calendars/${calId}/${baseUID}.ics`,
      } as CalendarEvent;

      const instance1 = {
        ...masterEvent,
        uid: `${baseUID}/20250115`,
      };

      const instance2 = {
        ...masterEvent,
        uid: `${baseUID}/20250122`,
        start: "2025-01-22T10:00:00.000Z",
        end: "2025-01-22T11:00:00.000Z",
      };

      const stateWithSeries = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [baseUID]: masterEvent,
                [`${baseUID}/20250115`]: instance1,
                [`${baseUID}/20250122`]: instance2,
              },
            },
          },
        },
      };

      // Mock getEvent to return master
      jest.spyOn(EventApi, "getEvent").mockResolvedValue(masterEvent);
      const updateSeriesAsyncSpy = jest.spyOn(eventThunks, "updateSeriesAsync");

      renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={`${baseUID}/20250115`}
          typeOfAction="all"
        />,
        stateWithSeries
      );

      await waitFor(() => {
        expect(screen.getByDisplayValue("Weekly Meeting")).toBeInTheDocument();
      });

      // Change only the title (property change, not recurrence rules)
      const titleInput = screen.getByDisplayValue("Weekly Meeting");
      fireEvent.change(titleInput, { target: { value: "Updated Meeting" } });

      const saveButton = screen.getByRole("button", { name: "actions.save" });

      await act(async () => {
        fireEvent.click(saveButton);
      });

      // Verify updateSeriesAsync was dispatched with base UID
      await waitFor(() => {
        expect(updateSeriesAsyncSpy).toHaveBeenCalledWith(
          expect.objectContaining({
            event: expect.objectContaining({
              uid: "recurring-event-base",
              recurrenceId: undefined,
            }),
            removeOverrides: false,
          })
        );
      });
    });

    it("should use base UID when updating series with recurrence rule changes", async () => {
      const masterEvent = {
        uid: baseUID,
        title: "Daily Standup",
        calId,
        start: "2025-01-15T09:00:00.000Z",
        end: "2025-01-15T09:30:00.000Z",
        repetition: { freq: "daily", interval: 1 }, // Daily
        allday: false,
        timezone: "America/New_York",
        organizer: { cn: "test", cal_address: "test@test.com" },
        URL: `/calendars/${calId}/${baseUID}.ics`,
      } as CalendarEvent;

      const instance1 = {
        ...masterEvent,
        uid: `${baseUID}/20250115`,
      };

      const instance2 = {
        ...masterEvent,
        uid: `${baseUID}/20250116`,
        start: "2025-01-16T09:00:00.000Z",
        end: "2025-01-16T09:30:00.000Z",
      };

      const stateWithSeries = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [baseUID]: masterEvent,
                [`${baseUID}/20250115`]: instance1,
                [`${baseUID}/20250116`]: instance2,
              },
            },
          },
        },
      };

      jest.spyOn(EventApi, "getEvent").mockResolvedValue(masterEvent);
      jest.spyOn(CalendarApi, "getCalendar").mockResolvedValue({
        _embedded: { "dav:item": [] },
      } as any);

      const { store } = renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={`${baseUID}/20250115`}
          typeOfAction="all"
        />,
        stateWithSeries
      );

      await waitFor(() => {
        expect(screen.getByDisplayValue("Daily Standup")).toBeInTheDocument();
      });

      // Click More Options to access repeat settings
      const moreOptionsButton = screen.getByText("common.moreOptions");
      fireEvent.click(moreOptionsButton);

      await waitFor(() => {
        const repeatCheckbox = screen.getByLabelText("event.form.repeat");
        expect(repeatCheckbox).toBeInTheDocument();
        expect(repeatCheckbox).toBeChecked();
      });

      // Change from daily to weekly
      const frequencySelect = screen.getByText("event.repeat.frequency.days");
      fireEvent.mouseDown(frequencySelect);
      const weeklyOption = screen.getByRole("option", {
        name: "event.repeat.frequency.weeks",
      });
      fireEvent.click(weeklyOption);

      const saveButton = screen.getByRole("button", { name: "actions.save" });

      await act(async () => {
        fireEvent.click(saveButton);
        await new Promise((resolve) => setTimeout(resolve, 100));
      });

      // Verify all old instances were removed from store
      await waitFor(() => {
        const state = store.getState();
        const calendar = state.calendars.list[calId];

        expect(calendar.events[baseUID]).toBeUndefined();
        expect(calendar.events[`${baseUID}/20250115`]).toBeUndefined();
        expect(calendar.events[`${baseUID}/20250116`]).toBeUndefined();
      });
    });
  });

  describe("Master Event Date Preservation", () => {
    it("should preserve master event date when updating time in 'all events' mode", async () => {
      // Master event starts on Jan 15 at 10:00 AM
      const masterEvent = {
        uid: baseUID,
        title: "Daily Standup",
        calId,
        start: "2025-01-15T10:00:00.000Z",
        end: "2025-01-15T10:30:00.000Z",
        repetition: { freq: "daily", interval: 1 },
        allday: false,
        timezone: "UTC",
        organizer: { cn: "test", cal_address: "test@test.com" },
        URL: `/calendars/${calId}/${baseUID}.ics`,
      } as CalendarEvent;

      // Instance on Jan 17 (different date)
      const instance = {
        ...masterEvent,
        uid: `${baseUID}/20250117`,
        start: "2025-01-17T10:00:00.000Z",
        end: "2025-01-17T10:30:00.000Z",
      };

      const stateWithSeries = {
        ...preloadedState,
        calendars: {
          ...preloadedState.calendars,
          list: {
            [calId]: {
              ...preloadedState.calendars.list[calId],
              events: {
                [baseUID]: masterEvent,
                [`${baseUID}/20250117`]: instance,
              },
            },
          },
        },
      };

      jest.spyOn(EventApi, "getEvent").mockResolvedValue(masterEvent);
      jest
        .spyOn(EventApi, "putEvent")
        .mockResolvedValue({ status: 201 } as any);

      const updateSeriesAsyncSpy = jest.spyOn(eventThunks, "updateSeriesAsync");

      renderWithProviders(
        <EventUpdateModal
          open={true}
          onClose={mockOnClose}
          calId={calId}
          eventId={`${baseUID}/20250117`} // User clicked on Jan 17 instance
          typeOfAction="all"
        />,
        stateWithSeries
      );

      await waitFor(() => {
        expect(screen.getByDisplayValue("Daily Standup")).toBeInTheDocument();
      });

      // Change time to 2:00 PM (14:00)
      const input = screen.getByTestId("start-time-input");
      await userEvent.click(input);
      await userEvent.clear(input);
      await userEvent.type(input, "14:00{enter}");

      const saveButton = screen.getByRole("button", { name: "actions.save" });

      await act(async () => {
        fireEvent.click(saveButton);
        await new Promise((resolve) => setTimeout(resolve, 100));
      });

      // Verify the API was called with master date (Jan 15) + new time (14:00)
      await waitFor(() => {
        expect(updateSeriesAsyncSpy).toHaveBeenCalled();
        const callArgs = updateSeriesAsyncSpy.mock.calls[0][0];
        const updatedEvent = callArgs.event;

        // Should preserve Jan 15 date (master), not Jan 17 (clicked instance)
        expect(updatedEvent.start).toContain("2025-01-15");
        // But with new time 14:00 (in UTC, this is 14:00)
        expect(updatedEvent.start).toContain("14:00");
      });
    });
  });
});
