import React, { useCallback, useMemo, useState } from "react";

import { HolidaysResult } from "@/app/api/holidays";
import { LeaveType } from "@/app/api/timesheets";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import { Box, useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import {
  getHolidayForDate,
  getPublicHolidaysForNYears,
} from "@/app/utils/holidayUtils";
import {
  INPUT_DATE_FORMAT,
  convertDateArrayToString,
} from "@/app/utils/formatUtil";
import StyledFullCalendar from "../StyledFullCalendar";
import { ProjectWithTasks } from "@/app/api/projects";
import {
  LeaveEntry,
  RecordLeaveInput,
  RecordTimesheetInput,
  saveLeave,
} from "@/app/api/timesheets/actions";
import { Props as LeaveEditModalProps } from "../LeaveTable/LeaveEditModal";
import LeaveEditModal from "../LeaveTable/LeaveEditModal";
import dayjs, { Dayjs } from "dayjs";
import { checkTotalHours } from "@/app/api/timesheets/utils";
import unionBy from "lodash/unionBy";

export interface Props {
  leaveTypes: LeaveType[];
  companyHolidays: HolidaysResult[];
  allProjects: ProjectWithTasks[];
  leaveRecords: RecordLeaveInput;
  timesheetRecords: RecordTimesheetInput;
  isFullTime: boolean;
  joinDate: Dayjs;
  isSaturdayWorker: boolean
}

interface EventClickArg {
  event: {
    start: Date | null;
    startStr: string;
    extendedProps: {
      calendar?: string;
      entry?: LeaveEntry;
    };
  };
}

const LeaveCalendar: React.FC<Props> = ({
  companyHolidays,
  allProjects,
  leaveTypes,
  timesheetRecords,
  leaveRecords,
  isFullTime,
  joinDate,
  isSaturdayWorker
}) => {
  const {
    t,
    i18n: { language },
  } = useTranslation(["home", "common"]);
  const locale = language === "zh" ? "zh-tw" : "en";
  const theme = useTheme();

  const projectMap = useMemo(() => {
    return allProjects.reduce<{
      [id: ProjectWithTasks["id"]]: ProjectWithTasks;
    }>((acc, project) => {
      return { ...acc, [project.id]: project };
    }, {});
  }, [allProjects]);

  const leaveMap = useMemo(() => {
    return leaveTypes.reduce<{ [id: LeaveType["id"]]: string }>(
      (acc, leaveType) => ({ ...acc, [leaveType.id]: leaveType.name }),
      {},
    );
  }, [leaveTypes]);

  const [localLeaveRecords, setLocalLeaveEntries] = useState(leaveRecords);

  // leave edit modal related
  const [leaveEditModalProps, setLeaveEditModalProps] = useState<
    Partial<LeaveEditModalProps>
  >({});
  const [leaveEditModalOpen, setLeaveEditModalOpen] = useState(false);

  const openLeaveEditModal = useCallback(
    (defaultValues?: LeaveEntry, recordDate?: string, isHoliday?: boolean) => {
      setLeaveEditModalProps({
        defaultValues: defaultValues ? { ...defaultValues } : undefined,
        recordDate,
        isHoliday,
        onDelete: defaultValues
          ? async () => {
              if (!recordDate || !leaveRecords[recordDate]) {
                return;
              }
              const leaveEntriesAtDate = leaveRecords[recordDate];
              const newLeaveRecords = {
                ...leaveRecords,
                [recordDate!]: leaveEntriesAtDate.filter(
                  (e) => e.id !== defaultValues.id,
                ),
              };
              const savedLeaveRecords = await saveLeave(newLeaveRecords);
              setLocalLeaveEntries(savedLeaveRecords);
              setLeaveEditModalOpen(false);
            }
          : undefined,
      });
      setLeaveEditModalOpen(true);
    },
    [leaveRecords],
  );

  const closeLeaveEditModal = useCallback(() => {
    setLeaveEditModalOpen(false);
  }, []);

  // calendar related
  const holidays = useMemo(() => {
    return [
      ...getPublicHolidaysForNYears(2),
      ...companyHolidays.map((h) => ({
        title: h.name,
        date: convertDateArrayToString(h.date, INPUT_DATE_FORMAT),
        extendedProps: {
          calender: "holiday",
        },
      })),
    ].map((e) => ({
      ...e,
      backgroundColor: theme.palette.error.main,
      borderColor: theme.palette.error.main,
    }));
  }, [companyHolidays, theme.palette.error.main]);

  const leaveEntries = useMemo(
    () =>
      Object.keys(localLeaveRecords).flatMap((date, index) => {
        return localLeaveRecords[date].map((entry) => ({
          id: `${date}-${index}-leave-${entry.id}`,
          date,
          title: `${t("{{count}} hour", {
            ns: "common",
            count: entry.inputHours || 0,
          })} (${t(leaveMap[entry.leaveTypeId])})`,
          backgroundColor: theme.palette.warning.light,
          borderColor: theme.palette.warning.light,
          textColor: theme.palette.text.primary,
          extendedProps: {
            calendar: "leaveEntry",
            entry,
          },
        }));
      }),
    [leaveMap, localLeaveRecords, t, theme],
  );

  const timeEntries = useMemo(
    () =>
      Object.keys(timesheetRecords).flatMap((date, index) => {
        return timesheetRecords[date].map((entry) => ({
          id: `${date}-${index}-time-${entry.id}`,
          date,
          title: `${t("{{count}} hour", {
            ns: "common",
            count: (entry.inputHours || 0) + (entry.otHours || 0),
          })} (${
            entry.projectId
              ? projectMap[entry.projectId].code
              : t("Non-billable task")
          })`,
          backgroundColor: theme.palette.info.main,
          borderColor: theme.palette.info.main,
          extendedProps: {
            calendar: "timeEntry",
            entry,
          },
        }));
      }),
    [projectMap, timesheetRecords, t, theme],
  );

  const handleEventClick = useCallback(
    ({ event }: EventClickArg) => {
      const dayJsObj = dayjs(event.startStr);
      const holiday = getHolidayForDate(event.startStr, companyHolidays);
      const isWeekend = !isSaturdayWorker ? dayJsObj.day() === 0 || dayJsObj.day() === 6 : dayJsObj.day() === 0;
      const isHoliday = holiday || isWeekend;

      if (
        event.extendedProps.calendar === "leaveEntry" &&
        event.extendedProps.entry
      ) {
        openLeaveEditModal(
          event.extendedProps.entry as LeaveEntry,
          event.startStr,
          Boolean(isHoliday),
        );
      }
    },
    [companyHolidays, openLeaveEditModal],
  );

  const handleDateClick = useCallback(
    (e: { dateStr: string; dayEl: HTMLElement }) => {
      const dayJsObj = dayjs(e.dateStr);
      const holiday = getHolidayForDate(e.dateStr, companyHolidays);
      const isWeekend = !isSaturdayWorker ? dayJsObj.day() === 0 || dayJsObj.day() === 6 : dayJsObj.day() === 0;
      const isHoliday = holiday || isWeekend;

      openLeaveEditModal(undefined, e.dateStr, Boolean(isHoliday));
    },
    [companyHolidays, openLeaveEditModal],
  );

  const checkTotalHoursForDate = useCallback(
    (newEntry: LeaveEntry, date?: string) => {
      if (!date) {
        throw Error("Invalid date");
      }
      const dayJsObj = dayjs(date);
      const holiday = getHolidayForDate(date, companyHolidays);
      const isWeekend = !isSaturdayWorker ? dayJsObj.day() === 0 || dayJsObj.day() === 6 : dayJsObj.day() === 0;
      const isHoliday = holiday || isWeekend;

      const leaves = localLeaveRecords[date] || [];
      const timesheets = timesheetRecords[date] || [];

      const leavesWithNewEntry = unionBy(
        [newEntry as LeaveEntry],
        leaves,
        "id",
      );

      const totalHourError = checkTotalHours(
        dayJsObj,
        timesheets,
        leavesWithNewEntry,
        Boolean(isHoliday),
        isFullTime,
        joinDate,
      );

      if (totalHourError) throw Error(totalHourError);
    },
    [
      companyHolidays,
      isFullTime,
      joinDate,
      localLeaveRecords,
      timesheetRecords,
    ],
  );

  const handleSaveLeave = useCallback(
    async (leaveEntry: LeaveEntry, recordDate?: string) => {
      checkTotalHoursForDate(leaveEntry, recordDate);
      const leaveEntriesAtDate = leaveRecords[recordDate!] || [];
      const newLeaveRecords = {
        ...leaveRecords,
        [recordDate!]: [
          ...leaveEntriesAtDate.filter((e) => e.id !== leaveEntry.id),
          leaveEntry,
        ],
      };
      const savedLeaveRecords = await saveLeave(newLeaveRecords);
      setLocalLeaveEntries(savedLeaveRecords);
      setLeaveEditModalOpen(false);
    },
    [checkTotalHoursForDate, leaveRecords],
  );

  return (
    <Box>
      <StyledFullCalendar
        plugins={[dayGridPlugin, interactionPlugin]}
        initialView="dayGridMonth"
        buttonText={{ today: t("Today") }}
        events={[...holidays, ...timeEntries, ...leaveEntries]}
        eventClick={handleEventClick}
        dateClick={handleDateClick}
        locale={locale}
      />
      <LeaveEditModal
        modalSx={{ maxWidth: 400 }}
        leaveTypes={leaveTypes}
        open={leaveEditModalOpen}
        onClose={closeLeaveEditModal}
        onSave={handleSaveLeave}
        {...leaveEditModalProps}
      />
    </Box>
  );
};

export default LeaveCalendar;
