import { RootState, store } from "@/app/store";
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services/refreshCalendar";
import { getDisplayedCalendarRange } from "@/utils/CalendarRangeManager";
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
import { waitFor } from "@testing-library/dom";

jest.mock("@/features/Calendars/services/refreshCalendar");
jest.mock("@/utils/CalendarRangeManager");
jest.mock("@/app/store", () => ({
  store: {
    getState: jest.fn(),
  },
}));

describe("updateCalendars", () => {
  let mockDispatch: jest.Mock;
  const mockRange = {
    start: new Date("2025-01-15T10:00:00Z"),
    end: new Date("2025-01-16T10:00:00Z"),
  };

  const mockState = {
    calendars: {
      list: {
        "cal1/entry1": { id: "cal1/entry1", name: "Calendar 1", syncToken: 1 },
        "cal2/entry2": { id: "cal2/entry2", name: "Calendar 2", syncToken: 1 },
      },
      templist: {},
    },
  } as unknown as RootState;

  beforeEach(() => {
    jest.clearAllMocks();
    mockDispatch = jest.fn();
    (getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
    (store.getState as jest.Mock).mockReturnValue(mockState);
  });

  it("should not dispatch for non-object messages", () => {
    updateCalendars(null, mockDispatch);
    updateCalendars("string", mockDispatch);
    updateCalendars(123, mockDispatch);

    expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
  });

  it("should dispatch for registered calendars", () => {
    const message = {
      [WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
        "/calendars/cal1/entry1",
        "/calendars/cal2/entry2",
      ],
    };

    updateCalendars(message, mockDispatch);

    expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2);
  });

  it("should dispatch for calendar path updates", () => {
    const message = {
      "/calendars/cal1/entry1": { updated: true },
    };

    updateCalendars(message, mockDispatch);

    expect(refreshCalendarWithSyncToken).toHaveBeenCalled();
    expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
      calendar: mockState.calendars.list["cal1/entry1"],
      calType: undefined,
      calendarRange: mockRange,
    });
  });

  it("should use current displayed calendar range", () => {
    const message = {
      "/calendars/cal1/entry1": {},
    };

    updateCalendars(message, mockDispatch);

    expect(getDisplayedCalendarRange).toHaveBeenCalled();
  });

  it("should handle temp calendars", async () => {
    const stateWithTemp = {
      calendars: {
        list: {},
        templist: {
          "temp1/entry1": {
            id: "temp1/entry1",
            name: "Temp Calendar",
            syncToken: 1,
          },
        },
      },
    };

    (store.getState as jest.Mock).mockReturnValue(stateWithTemp);

    const message = {
      "/calendars/temp1/entry1": {},
    };

    updateCalendars(message, mockDispatch);

    await waitFor(() =>
      expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
        calendar: stateWithTemp.calendars.templist["temp1/entry1"],
        calType: "temp",
        calendarRange: mockRange,
      })
    );
  });

  it("should handle invalid calendar paths gracefully", () => {
    const message = {
      "/invalid/path": {},
      "not-a-path": {},
    };

    updateCalendars(message, mockDispatch);

    expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
  });
});
