import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import "./Calendar.styl";
import "./CustomCalendar.styl";
import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import EventPopover from "../../features/Events/EventModal";
import { CalendarEvent } from "../../features/Events/EventsTypes";
import CalendarSelection from "./CalendarSelection";
import {
  getCalendarDetailAsync,
  setTimeZone,
} from "../../features/Calendars/CalendarSlice";
import ImportAlert from "../../features/Events/ImportAlert";
import {
  formatDateToYYYYMMDDTHHMMSS,
  getCalendarRange,
} from "../../utils/dateUtils";
import { push } from "redux-first-history";
import EventPreviewModal from "../../features/Events/EventDisplayPreview";
import AddIcon from "@mui/icons-material/Add";
import { TempCalendarsInput } from "./TempCalendarsInput";
import Button from "@mui/material/Button";
import { Box } from "@mui/material";
import {
  updateSlotLabelVisibility,
  eventToFullCalendarFormat,
  extractEvents,
} from "./utils/calendarUtils";
import { useCalendarEventHandlers } from "./hooks/useCalendarEventHandlers";
import { useCalendarViewHandlers } from "./hooks/useCalendarViewHandlers";
import { EditModeDialog } from "../Event/EditModeDialog";
import { EventErrorHandler } from "../Error/EventErrorHandler";
import { EventErrorSnackbar } from "../Error/ErrorSnackbar";
import momentTimezonePlugin from "@fullcalendar/moment-timezone";
import { TimezoneSelector } from "./TimezoneSelector";
import { MiniCalendar } from "./MiniCalendar";
import { User } from "../Attendees/PeopleSearch";
import { useTheme } from "@mui/material/styles";
import { updateDarkColor } from "./utils/calendarColorsUtils";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";

interface CalendarAppProps {
  calendarRef: MutableRefObject<CalendarApi | null>;
  onDateChange?: (date: Date) => void;
  onViewChange?: (view: string) => void;
}

export default function CalendarApp({
  calendarRef,
  onDateChange,
  onViewChange,
}: CalendarAppProps) {
  const [selectedDate, setSelectedDate] = useState(new Date());
  const [selectedMiniDate, setSelectedMiniDate] = useState(new Date());
  const tokens = useAppSelector((state) => state.user.tokens);
  const userId =
    useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
  const dispatch = useAppDispatch();
  const theme = useTheme();
  useEffect(() => {
    if (!tokens || !userId) {
      dispatch(push("/"));
    }
  }, [dispatch, tokens, userId]);

  const calendars = useAppSelector((state) => state.calendars.list);
  const tempcalendars = useAppSelector((state) => state.calendars.templist);
  const [selectedCalendars, setSelectedCalendars] = useState<string[]>([]);

  const calendarLightSignature = useMemo(() => {
    return Object.values(calendars || {})
      .map((cal) => `${cal.id}:${cal.color?.light ?? ""}`)
      .sort()
      .join("|");
  }, [calendars]);

  const calendarIdsString = useMemo(
    () =>
      Object.keys(calendars || {})
        .sort()
        .join(","),
    [calendars]
  );
  const calendarIds = useMemo(
    () => (calendarIdsString ? calendarIdsString.split(",") : []),
    [calendarIdsString]
  );

  const dottedEvents: CalendarEvent[] = selectedCalendars.flatMap((calId) => {
    const calendar = calendars[calId];
    if (!calendar?.events) return [];
    return Object.values(calendar.events);
  });

  const [currentView, setCurrentView] = useState("timeGridWeek");
  const timezone = useAppSelector((state) => state.calendars.timeZone);

  const fetchedRangesRef = useRef<Record<string, string>>({});

  // Auto-select personal calendars when first loaded
  const initialLoadRef = useRef(true);
  const [eventErrors, setEventErrors] = useState<string[]>([]);
  const errorHandler = useRef(new EventErrorHandler());

  useEffect(() => {
    const handler = errorHandler.current;
    handler.setErrorCallback(setEventErrors);
    return () => handler.setErrorCallback(() => {});
  }, []);

  const handleErrorClose = () => {
    setEventErrors([]);
    errorHandler.current.clearAll();
  };

  useEffect(() => {
    if (initialLoadRef.current && calendarIds.length > 0 && userId) {
      const cached = localStorage.getItem("selectedCalendars");
      if (cached && cached.length > 0) {
        const parsed = JSON.parse(cached) as string[];
        const valid = parsed.filter((id) => calendars[id]);
        setSelectedCalendars(valid);
      } else {
        const personalCalendarIds = calendarIds.filter(
          (id) => id.split("/")[0] === userId
        );
        setSelectedCalendars(personalCalendarIds);
      }
      initialLoadRef.current = false;
    }
  }, [calendarIds, calendars, userId]);

  // Save selected cals to cache
  useEffect(() => {
    if (calendarIds.length > 0) {
      localStorage.setItem(
        "selectedCalendars",
        JSON.stringify(selectedCalendars)
      );
    }
  }, [selectedCalendars, calendarIds.length]);

  const prevCalendarLightSignature = useRef<string | null>(null);

  useEffect(() => {
    if (!calendarLightSignature) {
      prevCalendarLightSignature.current = calendarLightSignature;
      return;
    }

    if (prevCalendarLightSignature.current === calendarLightSignature) {
      return;
    }

    prevCalendarLightSignature.current = calendarLightSignature;
    updateDarkColor(calendars || {}, theme, dispatch);
  }, [calendarLightSignature, calendars, theme, dispatch]);

  useEffect(() => {
    if (calendarIds.length === 0) return;
    const validCalendarIds = new Set(calendarIds);
    setSelectedCalendars((prev) => {
      const filtered = prev.filter((calId) => validCalendarIds.has(calId));
      if (filtered.length === prev.length) {
        const unchanged = filtered.every((id, index) => id === prev[index]);
        if (unchanged) {
          return prev;
        }
      }
      return filtered;
    });
  }, [calendarIds]);

  const calendarRange = useMemo(
    () => getCalendarRange(selectedDate),
    [selectedDate]
  );

  const calendarRangeStart = calendarRange.start.getTime();
  const calendarRangeEnd = calendarRange.end.getTime();

  const rangeStart = useMemo(
    () => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeStart)),
    [calendarRangeStart]
  );

  const rangeEnd = useMemo(
    () => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeEnd)),
    [calendarRangeEnd]
  );

  // Create a stable string key for the range
  const rangeKey = useMemo(
    () => `${rangeStart}_${rangeEnd}`,
    [rangeStart, rangeEnd]
  );

  let filteredEvents: CalendarEvent[] = extractEvents(
    selectedCalendars,
    calendars || {}
  );

  const tempCalendarIds = useMemo(
    () => Object.keys(tempcalendars || {}).sort(),
    [tempcalendars]
  );

  let filteredTempEvents: CalendarEvent[] = extractEvents(
    tempCalendarIds,
    tempcalendars || {}
  );

  const sortedSelectedCalendars = useMemo(
    () => [...selectedCalendars].sort(),
    [selectedCalendars]
  );

  const prefetchedCalendarsRef = useRef<Record<string, string>>({});
  const tempFetchedRangesRef = useRef<Record<string, string>>({});
  const activeLoadCompletedRef = useRef<boolean>(false);
  const [activeLoadCompleted, setActiveLoadCompleted] =
    useState<boolean>(false);

  useEffect(() => {
    activeLoadCompletedRef.current = false;
    setActiveLoadCompleted(false);
  }, [rangeKey]);

  useEffect(() => {
    if (!rangeKey || sortedSelectedCalendars.length === 0) {
      activeLoadCompletedRef.current = true;
      setActiveLoadCompleted(true);
      return;
    }

    let cancelled = false;
    const ACTIVE_BATCH_SIZE = 5;

    const loadCalendars = async () => {
      try {
        const pendingIds = sortedSelectedCalendars.filter(
          (id) => fetchedRangesRef.current[id] !== rangeKey
        );

        if (pendingIds.length === 0) {
          activeLoadCompletedRef.current = true;
          setActiveLoadCompleted(true);
          return;
        }

        for (
          let i = 0;
          i < pendingIds.length && !cancelled;
          i += ACTIVE_BATCH_SIZE
        ) {
          const chunk = pendingIds.slice(i, i + ACTIVE_BATCH_SIZE);

          chunk.forEach((id) => {
            fetchedRangesRef.current[id] = rangeKey;
            prefetchedCalendarsRef.current[id] = "active";
          });

          const requests = chunk.map(async (id) => {
            try {
              await dispatch(
                getCalendarDetailAsync({
                  calId: id,
                  match: {
                    start: rangeStart,
                    end: rangeEnd,
                  },
                })
              ).unwrap();
            } catch (error) {
              console.error(`Failed to load calendar ${id}:`, error);
              fetchedRangesRef.current[id] = "";
            }
          });

          await Promise.all(requests);
        }
      } finally {
        if (!cancelled) {
          activeLoadCompletedRef.current = true;
          setActiveLoadCompleted(true);
        }
      }
    };

    loadCalendars();

    return () => {
      cancelled = true;
    };
  }, [dispatch, rangeKey, sortedSelectedCalendars, rangeStart, rangeEnd]);

  useEffect(() => {
    if (!rangeKey || !activeLoadCompleted) return;

    const hiddenCalendars = calendarIds
      .filter((id) => !selectedCalendars.includes(id))
      .filter((id) => {
        const prefetched = prefetchedCalendarsRef.current[id];
        return prefetched !== rangeKey && prefetched !== "active";
      });

    if (hiddenCalendars.length === 0) return;

    hiddenCalendars.forEach((id) => {
      prefetchedCalendarsRef.current[id] = rangeKey;
      dispatch(
        getCalendarDetailAsync({
          calId: id,
          match: {
            start: rangeStart,
            end: rangeEnd,
          },
        })
      )
        .unwrap()
        .catch((error) => {
          console.error(`Prefetch calendar ${id} failed:`, error);
          prefetchedCalendarsRef.current[id] = "";
        });
    });
  }, [
    calendarIdsString,
    selectedCalendars,
    rangeKey,
    dispatch,
    rangeStart,
    rangeEnd,
    activeLoadCompleted,
  ]);

  const calendarsWithClearedCache = useMemo(() => {
    return selectedCalendars
      .map((id) => {
        const cleared = calendars[id]?.lastCacheCleared;
        return cleared ? { id, cleared } : null;
      })
      .filter(Boolean) as { id: string; cleared: number }[];
  }, [selectedCalendars, calendars]);

  const processedCacheClearRef = useRef<Record<string, number>>({});

  useEffect(() => {
    calendarsWithClearedCache.forEach(({ id, cleared }) => {
      if (processedCacheClearRef.current[id] === cleared) {
        return;
      }

      processedCacheClearRef.current[id] = cleared;
      delete fetchedRangesRef.current[id];
      prefetchedCalendarsRef.current[id] = "";

      dispatch(
        getCalendarDetailAsync({
          calId: id,
          match: {
            start: rangeStart,
            end: rangeEnd,
          },
        })
      )
        .unwrap()
        .then(() => {
          fetchedRangesRef.current[id] = rangeKey;
          prefetchedCalendarsRef.current[id] = rangeKey;
        })
        .catch(() => {
          fetchedRangesRef.current[id] = "";
          prefetchedCalendarsRef.current[id] = "";
        });
    });
  }, [calendarsWithClearedCache, dispatch, rangeKey, rangeStart, rangeEnd]);

  const tempCalendarControllersRef = useRef<Map<string, AbortController>>(
    new Map()
  );

  useEffect(() => {
    const currentIds = new Set(tempCalendarIds);
    Object.keys(tempFetchedRangesRef.current).forEach((id) => {
      if (!currentIds.has(id)) {
        delete tempFetchedRangesRef.current[id];
      }
    });
  }, [tempCalendarIds]);

  useEffect(() => {
    if (!rangeKey || tempCalendarIds.length === 0) return;

    let cancelled = false;
    const TEMP_BATCH_SIZE = 5;
    const loadTempCalendars = async () => {
      const pendingIds = tempCalendarIds.filter(
        (id) => tempFetchedRangesRef.current[id] !== rangeKey
      );

      if (pendingIds.length === 0) {
        return;
      }

      for (
        let i = 0;
        i < pendingIds.length && !cancelled;
        i += TEMP_BATCH_SIZE
      ) {
        const chunk = pendingIds.slice(i, i + TEMP_BATCH_SIZE);
        chunk.forEach((id) => {
          tempFetchedRangesRef.current[id] = rangeKey;
        });

        const requests = chunk.map(async (id) => {
          try {
            await dispatch(
              getCalendarDetailAsync({
                calId: id,
                match: {
                  start: rangeStart,
                  end: rangeEnd,
                },
                calType: "temp",
              })
            ).unwrap();
          } catch (error) {
            console.error(`Failed to load temp calendar ${id}:`, error);
            tempFetchedRangesRef.current[id] = "";
          }
        });

        await Promise.all(requests);
      }
    };

    loadTempCalendars();

    return () => {
      cancelled = true;
    };
  }, [dispatch, rangeKey, tempCalendarIds, rangeStart, rangeEnd]);

  const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);

  const [openEventDisplay, setOpenEventDisplay] = useState(false);
  const [eventDisplayedId, setEventDisplayedId] = useState("");
  const [eventDisplayedTemp, setEventDisplayedTemp] = useState(false);
  const [eventDisplayedCalId, setEventDisplayedCalId] = useState("");
  const [openEditModePopup, setOpenEditModePopup] = useState<string | null>(
    null
  );
  const [, setTypeOfAction] = useState<"solo" | "all" | undefined>(undefined);
  const [afterChoiceFunc, setAfterChoiceFunc] = useState<Function>();
  const [selectedEvent, setSelectedEvent] = useState<CalendarEvent>(
    {} as CalendarEvent
  );
  const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>(
    null
  );

  const [tempUsers, setTempUsers] = useState<User[]>([]);
  const [tempEvent, setTempEvent] = useState<CalendarEvent>(
    {} as CalendarEvent
  );

  // Event handlers
  const eventHandlers = useCalendarEventHandlers({
    setSelectedRange,
    setAnchorEl,
    calendarRef,
    selectedCalendars,
    tempcalendars,
    calendarRange,
    dispatch,
    setOpenEventDisplay,
    setEventDisplayedId,
    setEventDisplayedCalId,
    setEventDisplayedTemp,
    calendars,
    setSelectedEvent,
    setAfterChoiceFunc,
    setOpenEditModePopup,
    tempUsers,
    setTempEvent,
    timezone,
  });

  // View handlers
  const viewHandlers = useCalendarViewHandlers({
    calendarRef,
    setSelectedDate,
    setSelectedMiniDate,
    onViewChange,
    calendars,
    tempcalendars,
    errorHandler: errorHandler.current,
  });

  if (process.env.NODE_ENV === "test") {
    (window as any).__calendarRef = calendarRef;
  }

  const { t, lang } = useI18n();

  return (
    <main className="main-layout">
      <Box
        className="sidebar"
        sx={{
          padding: "0 15px 16px 25px",
          position: "relative",
        }}
      >
        <Box
          sx={{
            position: "sticky",
            top: 0,
            zIndex: 10,
            backgroundColor: "#fff",
            paddingTop: "16px",
          }}
        >
          <Button
            size="large"
            variant="contained"
            fullWidth
            onClick={() =>
              eventHandlers.handleDateSelect(null as unknown as DateSelectArg)
            }
          >
            <AddIcon /> <p>{t("event.createEvent")}</p>
          </Button>
        </Box>

        <MiniCalendar
          calendarRef={calendarRef}
          selectedDate={selectedMiniDate}
          setSelectedDate={setSelectedDate}
          setSelectedMiniDate={setSelectedMiniDate}
          dottedEvents={dottedEvents}
        />
        <TempCalendarsInput
          tempUsers={tempUsers}
          setTempUsers={setTempUsers}
          handleToggleEventPreview={() => {
            eventHandlers.handleDateSelect(null as unknown as DateSelectArg);
          }}
          selectedCalendars={selectedCalendars}
          setSelectedCalendars={setSelectedCalendars}
        />
        <div className="calendarList">
          <CalendarSelection
            selectedCalendars={selectedCalendars}
            setSelectedCalendars={setSelectedCalendars}
          />
        </div>
      </Box>
      <div className="calendar">
        <ImportAlert />
        <FullCalendar
          ref={(ref) => {
            if (ref) {
              calendarRef.current = ref.getApi();
            }
          }}
          plugins={[
            dayGridPlugin,
            timeGridPlugin,
            interactionPlugin,
            momentTimezonePlugin,
          ]}
          initialView="timeGridWeek"
          firstDay={1}
          editable={true}
          locale={lang}
          selectable={true}
          timeZone={timezone}
          height={"100%"}
          select={eventHandlers.handleDateSelect}
          nowIndicator
          slotLabelClassNames={(arg) => [
            updateSlotLabelVisibility(new Date(), arg, timezone),
          ]}
          nowIndicatorContent={viewHandlers.handleNowIndicatorContent}
          headerToolbar={false}
          views={{
            timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
          }}
          dayMaxEvents={true}
          events={eventToFullCalendarFormat(
            filteredEvents,
            filteredTempEvents,
            userId
          )}
          weekNumbers={
            currentView === "timeGridWeek" || currentView === "timeGridDay"
          }
          weekNumberFormat={{ week: "long" }}
          weekNumberContent={(arg) => {
            return (
              <div className="weekSelector">
                <div>
                  {t("menubar.views.week")} {arg.num}
                </div>
                <TimezoneSelector
                  value={timezone}
                  referenceDate={calendarRef.current?.getDate() ?? new Date()}
                  onChange={(newTimezone: string) =>
                    dispatch(setTimeZone(newTimezone))
                  }
                />
              </div>
            );
          }}
          dayCellContent={(arg) => {
            const month = arg.date.toLocaleDateString(t("locale"), {
              month: "short",
            });
            if (arg.view.type === "dayGridMonth") {
              return (
                <span
                  className={`fc-daygrid-day-number ${
                    arg.isToday ? "current-date" : ""
                  }`}
                >
                  {arg.dayNumberText === "1" ? month : ""} {arg.dayNumberText}
                </span>
              );
            }
          }}
          slotDuration={"00:30:00"}
          slotLabelInterval={"01:00:00"}
          scrollTime="12:00:00"
          unselectAuto={false}
          allDayText=""
          slotLabelFormat={{
            hour: "2-digit",
            minute: "2-digit",
            hour12: false,
          }}
          datesSet={(arg) => {
            setCurrentView(arg.view.type);
            // Get the current date from calendar API to ensure consistency
            const calendarCurrentDate =
              calendarRef.current?.getDate() || new Date(arg.start);

            if (arg.view.type === "dayGridMonth") {
              const start = new Date(arg.start).getTime();
              const end = new Date(arg.end).getTime();
              const middle = start + (end - start) / 2;
              setSelectedDate(new Date(middle));
              setSelectedMiniDate(calendarCurrentDate);
            } else {
              setSelectedDate(new Date(arg.start));
              setSelectedMiniDate(new Date(arg.start));
            }

            // Always use the calendar's current date for consistency
            if (onDateChange) {
              onDateChange(calendarCurrentDate);
            }

            // Notify parent about view change
            if (onViewChange) {
              onViewChange(arg.view.type);
            }

            // Update slot label visibility when view changes
            setTimeout(() => {
              updateSlotLabelVisibility(new Date());
            }, 100);
          }}
          dayHeaderContent={(arg) => {
            const date = arg.date.getDate();
            const weekDay = arg.date
              .toLocaleDateString(t("locale"), { weekday: "short" })
              .toUpperCase();
            return (
              <div className="fc-daygrid-day-top">
                <small>{weekDay}</small>
                {arg.view.type !== "dayGridMonth" && (
                  <span
                    className={`fc-daygrid-day-number ${
                      arg.isToday ? "current-date" : ""
                    }`}
                  >
                    {date}
                  </span>
                )}
              </div>
            );
          }}
          dayHeaderDidMount={viewHandlers.handleDayHeaderDidMount}
          dayHeaderWillUnmount={viewHandlers.handleDayHeaderWillUnmount}
          viewDidMount={viewHandlers.handleViewDidMount}
          viewWillUnmount={viewHandlers.handleViewWillUnmount}
          eventClick={eventHandlers.handleEventClick}
          eventAllow={eventHandlers.handleEventAllow}
          eventDrop={eventHandlers.handleEventDrop}
          eventResize={eventHandlers.handleEventResize}
          eventContent={viewHandlers.handleEventContent}
          eventDidMount={viewHandlers.handleEventDidMount}
        />
        <EventPopover
          anchorEl={anchorEl}
          open={Boolean(anchorEl)}
          onClose={eventHandlers.handleClosePopover}
          selectedRange={selectedRange}
          setSelectedRange={setSelectedRange}
          calendarRef={calendarRef}
          event={tempEvent}
        />
        <EditModeDialog
          type={openEditModePopup}
          setOpen={setOpenEditModePopup}
          event={selectedEvent}
          eventAction={(type: "solo" | "all" | undefined) => {
            setTypeOfAction(type);
            afterChoiceFunc && afterChoiceFunc(type);
          }}
        />
        {openEventDisplay && eventDisplayedId && eventDisplayedCalId && (
          <EventPreviewModal
            eventId={eventDisplayedId}
            calId={eventDisplayedCalId}
            tempEvent={eventDisplayedTemp}
            open={openEventDisplay}
            onClose={eventHandlers.handleCloseEventDisplay}
          />
        )}
        <EventErrorSnackbar messages={eventErrors} onClose={handleErrorClose} />
      </div>
    </main>
  );
}
