import reducer, {
  addEvent,
  removeEvent,
  createCalendar,
  updateEventLocal,
  removeTempCal,
  getCalendarsListAsync,
  getTempCalendarsListAsync,
  getCalendarDetailAsync,
  putEventAsync,
  getEventAsync,
  patchCalendarAsync,
  removeCalendarAsync,
  moveEventAsync,
  patchACLCalendarAsync,
  createCalendarAsync,
  addSharedCalendarAsync,
  deleteEventAsync,
} from "../../../src/features/Calendars/CalendarSlice";

import * as calAPI from "../../../src/features/Calendars/CalendarApi";
import * as userAPI from "../../../src/features/User/userAPI";

import { configureStore } from "@reduxjs/toolkit";
import { Calendars } from "../../../src/features/Calendars/CalendarTypes";
import { CalendarEvent } from "../../../src/features/Events/EventsTypes";

jest.mock("../../../src/features/Calendars/CalendarApi");
jest.mock("../../../src/features/User/userAPI");
jest.mock("../../../src/features/Events/EventApi");
jest.mock("../../../src/features/Events/eventUtils");
jest.mock("../../../src/utils/apiUtils");

describe("CalendarSlice", () => {
  const initialState = {
    list: {},
    templist: {},
    pending: false,
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("reducers", () => {
    it("createCalendar adds new calendar", () => {
      const action = createCalendar({
        name: "Test Cal",
        color: "#ff0000",
        description: "desc",
      });
      const state = reducer(initialState, action);
      const values = Object.values(state.list);
      expect(values[0].name).toBe("Test Cal");
      expect(values[0].color).toBe("#ff0000");
    });

    it("addEvent adds event to calendar", () => {
      const calId = "user/cal";
      const event = { uid: "event1", title: "My Event" } as any;
      const stateWithCal = {
        ...initialState,
        list: { [calId]: { id: calId, events: {} } as any },
      };
      const state = reducer(
        stateWithCal,
        addEvent({ calendarUid: calId, event })
      );
      expect(state.list[calId].events["event1"]).toEqual(
        expect.objectContaining({ uid: "event1" })
      );
    });

    it("removeEvent deletes event", () => {
      const calId = "user/cal";
      const stateWithEvent = {
        ...initialState,
        list: {
          [calId]: {
            id: calId,
            events: { e1: { uid: "e1" } },
          } as unknown as Calendars,
        },
      };
      const state = reducer(
        stateWithEvent,
        removeEvent({ calendarUid: calId, eventUid: "e1" })
      );
      expect(state.list[calId].events).toEqual({});
    });

    it("updateEventLocal updates an event", () => {
      const calId = "user/cal";
      const stateWithEvent = {
        ...initialState,
        list: {
          [calId]: {
            id: calId,
            events: { e1: { uid: "e1", title: "Old" } },
          } as unknown as Calendars,
        },
      };
      const state = reducer(
        stateWithEvent,
        updateEventLocal({ calId, event: { uid: "e1", title: "New" } as any })
      );
      expect(state.list[calId].events.e1.title).toBe("New");
    });

    it("removeTempCal deletes temp calendar", () => {
      const stateWithTemp = {
        ...initialState,
        templist: { temp1: { id: "temp1" } as any },
      };
      const state = reducer(stateWithTemp, removeTempCal("temp1"));
      expect(state.templist).toEqual({});
    });
  });

  describe("extraReducers (thunks)", () => {
    const storeFactory = () =>
      configureStore({
        reducer: { calendars: reducer },
      });

    it("getCalendarsListAsync.fulfilled replaces list", async () => {
      (userAPI.getOpenPaasUser as jest.Mock).mockResolvedValue({ id: "u1" });
      (calAPI.getCalendars as jest.Mock).mockResolvedValue({
        _embedded: { "dav:calendar": [] },
      });
      (userAPI.getUserDetails as jest.Mock).mockResolvedValue({
        firstname: "Alice",
        lastname: "Smith",
        emails: ["a@b.com"],
      });

      const store = storeFactory();
      await store.dispatch(getCalendarsListAsync() as any);
      const state = store.getState().calendars;
      expect(state.list).toEqual({});
    });

    it("patchCalendarAsync.fulfilled updates calendar fields", () => {
      const calId = "c1";
      const prev = {
        ...initialState,
        list: { c1: { id: calId, events: { e1: { uid: "e1" } } } as any },
      };
      const patch = { name: "N", desc: "D", color: "#00f" };
      const state = reducer(
        prev,
        patchCalendarAsync.fulfilled(
          { calId, calLink: "link", patch },
          "req1",
          {
            calId,
            calLink: "link",
            patch,
          }
        )
      );
      expect(state.list[calId].name).toBe("N");
      expect(state.list[calId].description).toBe("D");
      expect(state.list[calId].color).toBe("#00f");
    });

    it("removeCalendarAsync.fulfilled deletes calendar", () => {
      const prev = {
        ...initialState,
        list: { c1: { id: "c1" } as any },
      };
      const state = reducer(
        prev,
        removeCalendarAsync.fulfilled({ calId: "c1" }, "req2", {
          calId: "c1",
          calLink: "l",
        })
      );
      expect(state.list).toEqual({});
    });

    it("patchACLCalendarAsync.fulfilled sets visibility", () => {
      const prev = {
        ...initialState,
        list: { c1: { id: "c1", visibility: "public" } as any },
      };
      const state = reducer(
        prev,
        patchACLCalendarAsync.fulfilled(
          { calId: "c1", calLink: "l", request: "" },
          "req3",
          { calId: "c1", calLink: "l", request: "" }
        )
      );
      expect(state.list.c1.visibility).toBe("private");
    });

    it("createCalendarAsync.fulfilled adds a new calendar", () => {
      const payload = {
        userId: "u1",
        calId: "cal1",
        color: "#f00",
        name: "Test",
        desc: "Desc",
        owner: "Owner",
        ownerEmails: ["o@example.com"],
      };
      const state = reducer(
        initialState,
        createCalendarAsync.fulfilled(payload, "req4", payload)
      );
      expect(state.list["u1/cal1"].name).toBe("Test");
      expect(state.list["u1/cal1"].color).toBe("#f00");
    });

    it("addSharedCalendarAsync.fulfilled adds shared calendar", () => {
      const payload = {
        calId: "c1",
        color: "#0f0",
        link: "/calendars/u1/c1.json",
        name: "Shared",
        desc: "Shared Desc",
        owner: "O",
        ownerEmails: ["o@example.com"],
      };
      const mockCal = {
        cal: {
          _links: { self: { href: "/calendars/u1/c1.json" } },
          "apple:color": "#0f0",
          "caldav:description": "Shared Desc",
          "dav:name": "Shared",
        },
      };
      const state = reducer(
        initialState,
        addSharedCalendarAsync.fulfilled(payload, "req5", {
          userId: "u1",
          calId: "c1",
          cal: mockCal,
        })
      );
      expect(state.list["c1"].name).toBe("Shared");
    });

    it("deleteEventAsync.fulfilled removes event", () => {
      const calId = "c1";
      const prev = {
        ...initialState,
        list: {
          [calId]: {
            id: calId,
            events: { e1: { uid: "e1" } },
          } as unknown as Calendars,
        },
      };
      const state = reducer(
        prev,
        deleteEventAsync.fulfilled({ calId, eventId: "e1" }, "req6", {
          calId,
          eventId: "e1",
          eventURL: "e1.ics",
        })
      );
      expect(state.list[calId].events).toEqual({});
    });

    it("getTempCalendarsListAsync.fulfilled updates templist", () => {
      const payload = {
        t1: {
          id: "t1",
          name: "Temp",
          color: "#aaa",
          events: {},
          visibility: "public",
          owner: "O",
          ownerEmails: ["o@o.com"],
          link: "/calendars/t1.json",
          description: "desc",
        } as Calendars,
      };
      const state = reducer(
        initialState,
        getTempCalendarsListAsync.fulfilled(payload, "req7", {
          openpaasId: "u1",
          color: "#aaa",
          displayName: "test",
          avatarUrl: "",
          email: "test@test.com",
        })
      );
      expect(state.templist.t1.name).toBe("Temp");
    });

    it("putEventAsync.fulfilled updates calendar events", () => {
      const cal = { id: "c1", events: {} } as any;
      const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] };
      const state = reducer(
        initialState,
        putEventAsync.fulfilled(payload, "req8", {
          cal,
          newEvent: { uid: "e1" } as CalendarEvent,
        })
      );
      expect(state.list.c1.events.e1.uid).toBe("e1");
    });

    it("getEventAsync.fulfilled adds single event", () => {
      const payload = { calId: "c1", event: { uid: "e1" } as any };
      const state = reducer(
        initialState,
        getEventAsync.fulfilled(payload, "req9", { uid: "e1" } as any)
      );
      expect(state.list.c1.events.e1.uid).toBe("e1");
    });

    it("moveEventAsync.fulfilled updates events after move", () => {
      const cal = { id: "c1", events: {} } as any;
      const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] };
      const state = reducer(
        initialState,
        moveEventAsync.fulfilled(payload, "req10", {
          cal,
          newEvent: { uid: "e1" } as CalendarEvent,
          newURL: "e1.ics",
        })
      );
      expect(state.list.c1.events.e1.uid).toBe("e1");
    });

    it("getCalendarDetailAsync.fulfilled adds calendar events", () => {
      const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] };
      const state = reducer(
        initialState,
        getCalendarDetailAsync.fulfilled(payload, "req11", {
          calId: "c1",
          match: { start: "", end: "" },
        })
      );
      expect(state.list.c1.events.e1.uid).toBe("e1");
    });
  });
});
