import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  useInfiniteQuery,
  useMutation,
  useQueries,
  useQuery,
} from 'react-query';

import useHuSnackbar from '@material-hu/components/design-system/Snackbar';

import { logEvent } from 'src/config/logging';
import { queryClient } from 'src/config/react-query';
import { useAuth } from 'src/contexts/JWTContext';
import usePermissions from 'src/hooks/usePermissions';
import { orgChartKeys } from 'src/pages/dashboard/orgChart/queries';
import {
  updateNotifications,
  updateVacations,
  vacationsKeys,
} from 'src/pages/dashboard/timeOff/queries';
import { getUserSubordinates } from 'src/services/orgChart';
import {
  approveOrRejectRequest,
  cancelRequest,
  clearVacationsNotifications,
  downloadReportBalances,
  downloadReportRequests,
  editFilesInRequest,
  getBalanceConformationForAdjustments,
  getBalanceConformationOfPolicyTypes,
  getBalanceReport,
  getFutureBalance,
  getHolidaysByDate,
  getPolicyTypeDetail,
  getPolicyTypes,
  getPolicyTypesByUserId,
  getRequestedDays,
  getRequestToCalendarCollaborator,
  getRequestToCalendarManager,
  getVacationsNotifications,
  getVacationsRequestCollaborator,
  getVacationsRequestManager,
  massiveApproval,
  sendReminder,
} from 'src/services/vacations';
import { EventName } from 'src/types/amplitude';
import {
  type DateToRequest,
  type ExtraFieldsReport,
  ReportType,
  ResponseCode,
  type Role,
  type TabManagementType,
  type TimeOffAttachment,
  type VacationActionsSources,
  VacationRequestSources,
  VacationStatus,
  type VacationsFilterValues,
} from 'src/types/vacations';
import { formatDateInVacations } from 'src/utils/date';
import { getDownloadReportName } from 'src/utils/feed';
import { downloadBlob } from 'src/utils/files';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { UserPermissions } from 'src/utils/permissions';

import useFormatDate from '../useFormatDate';
import useGeneralError from '../useGeneralError';

const LIMIT = 20;

const { MANAGE_VACATIONS } = UserPermissions;

export const invalidateTimeOffNotifications = () => {
  queryClient.invalidateQueries(vacationsKeys.notifications.module());
};

export const useVacations = () => {
  const { t } = useLokaliseTranslation('time_off');
  const { user } = useAuth();
  const showGeneralError = useGeneralError();

  const policyTypesDataCollaborator = useQuery(
    vacationsKeys.policyTypes.listByUserId(user!.id),
    () => getPolicyTypesByUserId(user!.id),
    {
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_POLICY_TYPES_OTHER'));
      },
      enabled: !!user?.id,
    },
  );

  const policyTypesDataManager = useQuery(
    vacationsKeys.policyTypes.list(),
    () => getPolicyTypes(),
    {
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_POLICY_TYPES_OTHER'));
      },
    },
  );

  const managerData = useGetRequestsReportManager();

  const vacationsResults = {
    collaborator: {
      policyTypes: policyTypesDataCollaborator?.data?.data || [],
    },
    manager: {
      policyTypes: policyTypesDataManager?.data?.data || [],
      hasRequests: !!managerData.data?.length,
    },
    isLoadingViews:
      policyTypesDataCollaborator?.isLoading ||
      policyTypesDataManager?.isLoading ||
      managerData?.isLoading,
  };

  return vacationsResults;
};

export const useGetRequestsReportCollaborator = (policyTypeId: number) => {
  const showGeneralError = useGeneralError();

  const { t } = useLokaliseTranslation('time_off');

  const collaboratorData = useInfiniteQuery(
    vacationsKeys.collaborator.requestsReport.filters(policyTypeId),
    ({ pageParam = 1 }) =>
      getVacationsRequestCollaborator(policyTypeId, pageParam, LIMIT),
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_REQUEST_OTHER'));
      },
    },
  );

  return {
    data: collaboratorData.data?.pages.flatMap(page => page.data.items) || [],
    isLoading: collaboratorData?.isLoading,
    fetchNextPage: collaboratorData?.fetchNextPage,
    hasNextPage: !!collaboratorData?.hasNextPage,
    isFetchingNextPage: collaboratorData?.isFetchingNextPage,
    countRequest: collaboratorData.data?.pages[0]?.data?.count,
  };
};

export const useGetRequestsReportManager = (params?: VacationsFilterValues) => {
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();

  const managerData = useInfiniteQuery(
    vacationsKeys.manager.requestsReport.filters(params),
    ({ pageParam = 1 }) => {
      return getVacationsRequestManager(params!, pageParam, LIMIT);
    },
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_REQUEST_OTHER'));
      },
    },
  );

  return {
    data: managerData.data?.pages.flatMap(page => page.data.items) || [],
    isLoading: managerData?.isLoading,
    fetchNextPage: managerData?.fetchNextPage,
    hasNextPage: managerData?.hasNextPage,
    isFetchingNextPage: managerData?.isFetchingNextPage,
    countRequest: managerData.data?.pages[0]?.data?.count,
  };
};

export const useGetBalanceConformation = (
  policyTypeId: number,
  userCollaboratorId: number,
) => {
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();

  const balanceConformationData = useInfiniteQuery(
    vacationsKeys.policyTypes.conformation(policyTypeId, userCollaboratorId),
    ({ pageParam = 1 }) =>
      getBalanceConformationOfPolicyTypes(
        policyTypeId,
        pageParam,
        LIMIT,
        userCollaboratorId,
      ),
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_BALANCE_CONFORMATION'));
      },
    },
  );

  return {
    data: balanceConformationData.data?.pages.flatMap(page => page.data.items),
    isLoading: balanceConformationData?.isLoading,
    fetchNextPage: balanceConformationData?.fetchNextPage,
    hasNextPage: balanceConformationData?.hasNextPage,
    isFetchingNextPage: balanceConformationData?.isFetchingNextPage,
  };
};

export const useGetBalanceConformationForAdjustments = (
  policyTypeId: number,
  userCollaboratorId: number,
) => {
  return useQuery(
    vacationsKeys.policyTypes.conformation(
      policyTypeId,
      userCollaboratorId,
      false,
    ),
    () =>
      getBalanceConformationForAdjustments(policyTypeId, userCollaboratorId),
    { select: r => r.data.items },
  );
};

export const useFutureBalancesConformation = (
  policyTypeId: number,
  userCollaboratorId: number,
  options = {},
) =>
  useQuery(
    vacationsKeys.policyTypes.futureBalance(policyTypeId, userCollaboratorId),
    () => getFutureBalance(policyTypeId, userCollaboratorId),
    { select: r => r.data, ...options },
  );

export const usePolicyTypeDetail = ({
  id,
  userCollaboratorId,
  enabled,
}: {
  id: number;
  userCollaboratorId?: number;
  enabled: boolean;
}) => {
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();

  const policyTypesDataDetail = useQuery(
    vacationsKeys.policyTypes.detail(id, userCollaboratorId!),
    () => getPolicyTypeDetail(id, userCollaboratorId),
    {
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_POLICY_TYPES_ONE'));
      },
      enabled,
    },
  );

  const policyTypeDataResults = {
    detail: policyTypesDataDetail?.data?.data,
    isLoading: policyTypesDataDetail?.isLoading,
  };

  return policyTypeDataResults;
};

export const useGetNotificationsVacations = () => {
  const showGeneralError = useGeneralError();
  const { t } = useLokaliseTranslation('time_off');

  const {
    data = {
      vacationRequestsAsManagerNotifications: 0,
      vacationRequestsAsCollaboratorNotifications: 0,
    },
  } = useQuery(
    vacationsKeys.notifications.module(),
    () => getVacationsNotifications(),
    {
      onError: err => {
        showGeneralError(err, t('general:error_loading_home_notifications'));
      },
    },
  );

  return data;
};

export const useClearNotificationsVacations = (role: Role) => {
  const { mutate: clearNotificationsMutate } = useMutation(
    (body: { role: string }) => clearVacationsNotifications(body),
    {
      onSettled: () => updateNotifications(role),
    },
  );

  useEffect(() => {
    clearNotificationsMutate({ role });
  }, []);
};

export type GetManagerTypesReturn = {
  isApprover: boolean;
  isManager: boolean;
  isAdmin: boolean;
};

export const useGetManagerTypes = (
  isUserManager = true,
): GetManagerTypesReturn => {
  const { user } = useAuth();
  const { hasAll: isAdmin } = usePermissions([MANAGE_VACATIONS]);

  const { data: subordinates = { data: [] } } = useQuery(
    orgChartKeys.detailSubordinates(user!.id),
    () => getUserSubordinates(user!.id),
    {
      enabled: !!user?.id && isUserManager,
    },
  );

  if (!isUserManager)
    return { isApprover: false, isManager: false, isAdmin: false };

  const isManager = subordinates?.data.length > 0 || false;
  const isApprover = !isManager && !isAdmin;
  return {
    isApprover,
    isManager,
    isAdmin,
  };
};

export const useGetBalanceReport = (params: VacationsFilterValues) => {
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();

  const reportData = useInfiniteQuery(
    vacationsKeys.manager.balanceReport.filters(params),
    ({ pageParam = 1 }) => {
      return getBalanceReport(params, pageParam, LIMIT);
    },
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_BALANCES'));
      },
    },
  );

  return {
    data: reportData.data?.pages.length
      ? reportData.data?.pages.flatMap(page => page?.data.items)
      : null,
    count: reportData.data?.pages[0]
      ? reportData.data?.pages[0].data.count
      : null,
    isLoading: reportData?.isLoading,
    fetchNextPage: reportData?.fetchNextPage,
    hasNextPage: reportData?.hasNextPage,
    isFetchingNextPage: reportData?.isFetchingNextPage,
  };
};

type ParamMutationDowloadReport = {
  fileExtension: string;
  extraFields?: ExtraFieldsReport;
  countRequestManager?: number;
};

export const useDownloadReport = (
  filters: VacationsFilterValues,
  reportType: string,
  onClose: () => void,
  source: TabManagementType,
  showSendedByEmailModal?: () => void,
) => {
  const { t } = useLokaliseTranslation('time_off');
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();

  const downloadReportMutation = useMutation(
    ({
      fileExtension,
      extraFields = {},
      countRequestManager = 0,
    }: ParamMutationDowloadReport) => {
      if (reportType === ReportType.REQUESTS) {
        return downloadReportRequests(
          filters,
          fileExtension,
          extraFields,
          countRequestManager,
        );
      }
      return downloadReportBalances(
        filters,
        fileExtension,
        extraFields,
        countRequestManager,
      );
    },
    {
      onSuccess: ({ data }, { fileExtension }) => {
        if (data.sendByEmail) {
          showSendedByEmailModal?.();
          return;
        }
        downloadBlob(
          data,
          getDownloadReportName(
            t(
              reportType === ReportType.REQUESTS ? 'REQUESTS' : 'BALANCES',
            ).toLowerCase(),
            fileExtension,
          ),
        );

        enqueueSnackbar({
          title: t('SUCCESS_DOWNLOAD_REPORT'),
          variant: 'success',
        });

        logEvent(EventName.TIME_OFF_REPORT_DOWNLOAD, {
          report: reportType,
          fileType: fileExtension,
          source,
        });
      },
      onError: err => {
        showGeneralError(err, t('ERROR_DOWNLOAD_REPORT'));
      },
      onSettled: () => onClose(),
    },
  );

  return downloadReportMutation;
};

type UpdateFilesParams = {
  requestId: number;
  requestState: VacationStatus;
  requestAttachments: TimeOffAttachment[];
};

export const useUpdateFilesInRequest = ({
  requestId,
  requestState,
  requestAttachments,
}: UpdateFilesParams) => {
  const { t } = useLokaliseTranslation('time_off');
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();

  const updateFilesMutations = useMutation(
    async (nextAttachments: TimeOffAttachment[]) => {
      const attachmentsMapped = nextAttachments.map(attachment => ({
        ...attachment,
        url: attachment.url?.split('?')[0],
      }));
      return editFilesInRequest({
        id: requestId,
        attachments: attachmentsMapped,
      });
    },
    {
      onSuccess: (_, context) => {
        updateVacations(requestId);
        enqueueSnackbar({
          title: t('REQUEST_SUCCESSFULLY_UPDATED'),
          variant: 'success',
        });

        if (context.length > (requestAttachments?.length ?? 0)) {
          logEvent(EventName.TIME_OFF_DOCUMENTATION_ATTACHED, {
            source: VacationRequestSources.REQUEST_DETAIL,
            requestState,
            requestId,
          });
        }
      },
      onError: (err: any) => {
        if (err.message === ResponseCode.ERROR_MAX_ATTACHMENTS_SIZE_VACATIONS) {
          enqueueSnackbar({
            title: t('ERROR_MAX_ATTACHMENTS_SIZE_VACATIONS'),
            variant: 'error',
          });
        } else {
          showGeneralError(err, t('ERROR_UPDATING_REQUEST'));
        }
      },
    },
  );

  return updateFilesMutations;
};

export type MassiveApprovalProps = {
  ids: number[];
  state: VacationStatus;
  source?: VacationActionsSources;
  massiveApprovalCount: number;
  onSuccess: () => void;
  onError: () => void;
};

export const useMassiveApproval = (props: MassiveApprovalProps) => {
  const { ids, state, source, massiveApprovalCount, onSuccess, onError } =
    props;

  const { t } = useLokaliseTranslation('time_off');
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();

  const vacationMassiveApprovallMutation = useMutation(
    () => massiveApproval(ids, state),
    {
      onSuccess: () => {
        enqueueSnackbar({
          title: t(`SUCCESS_${state}_VACATION`, {
            count: massiveApprovalCount,
          }),
          variant: 'success',
        });
        logEvent(EventName.TIME_OFF_APPROVAL_RESOLVED, {
          source,
          state,
          count: massiveApprovalCount,
        });
        onSuccess();
      },

      onError: (err: any) => {
        if (
          err?.response?.data?.code ===
          ResponseCode.VACATION_REQUEST_NOT_PENDING
        ) {
          showGeneralError(err, t('VACATION_REQUEST_NOT_PENDING'));
        } else {
          showGeneralError(
            err,
            t(`ERROR_${state}_VACATION`, { count: massiveApprovalCount }),
          );
        }
        onError();
      },
    },
  );

  return vacationMassiveApprovallMutation;
};

export type VacationChangeStateMutationProps = {
  requestId: number;
  stepId?: number;
  policyTypeId?: number;
  state: VacationStatus;
  source?: VacationActionsSources;
  onSuccess: () => void;
  onError: () => void;
  resolutionReason?: string;
  comment?: string;
  isSingleApprovalStep?: boolean;
};

export const useVacationApprovalMutation = (
  props: VacationChangeStateMutationProps,
) => {
  const {
    requestId,
    stepId,
    policyTypeId,
    state,
    source,
    resolutionReason = '',
    comment = '',
    onSuccess = () => null,
    onError = () => null,
  } = props;

  const { t } = useLokaliseTranslation('time_off');
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();

  const vacationApprovalMutation = useMutation(
    () => {
      if (state === VacationStatus.CANCELLED) {
        return cancelRequest(requestId, resolutionReason);
      }
      return approveOrRejectRequest({
        requestId,
        state,
        stepId: stepId!,
        resolutionReason,
        comment,
      });
    },
    {
      onSuccess: () => {
        onSuccess();
        invalidateTimeOffNotifications();

        enqueueSnackbar({
          title: t(`SUCCESS_${state}_VACATION`, { count: 1 }),
          variant: 'info',
        });
        logEvent(EventName.TIME_OFF_APPROVAL_RESOLVED, {
          source,
          requestId,
          policyTypeId,
          state,
          resolutionReason,
          comment,
        });
      },

      onError: (err: any) => {
        onError();
        if (
          err?.response?.data?.code ===
          ResponseCode.VACATION_REQUEST_NOT_PENDING
        ) {
          showGeneralError(err, t('VACATION_REQUEST_NOT_PENDING'));
        } else {
          showGeneralError(err, t(`ERROR_${state}_VACATION`, { count: 1 }));
        }
      },
    },
  );

  return vacationApprovalMutation;
};

export default useVacationApprovalMutation;

export type GetRequestedDaysParam = {
  to?: DateToRequest;
  from?: DateToRequest;
  policyTypeId: number;
  enabled?: boolean;
  issuerId: number | null;
  open?: boolean;
  amountInMoney?: number;
};

export const useGetRequestedDays = (params: GetRequestedDaysParam) => {
  const {
    to,
    from,
    amountInMoney,
    policyTypeId,
    enabled: enabledParam,
    issuerId,
    open,
  } = params;

  const enabledWithDates =
    !!from?.date && !!to?.date && !!policyTypeId && enabledParam;

  const enabledWithPolicyTypeAndWithoutDates = !!policyTypeId && !from && !to;

  const enabled =
    (enabledWithDates || enabledWithPolicyTypeAndWithoutDates) && open;

  const { data: requestedDays, isLoading } = useQuery(
    vacationsKeys.policyTypes.requestedDaysAmount(
      policyTypeId,
      issuerId,
      from,
      to,
      amountInMoney,
    ),
    () => getRequestedDays({ policyTypeId, from, to, issuerId, amountInMoney }),
    {
      enabled: !!enabled,
      keepPreviousData: true,
    },
  );

  return {
    requestedDays: requestedDays?.data?.amount || 0,
    amountInMoney: requestedDays?.data?.amountInMoney || 0,
    amountInTime: requestedDays?.data?.amountInTime || 0,
    errors: requestedDays?.data?.errors || {},
    warnings: requestedDays?.data?.warnings || {},
    isLoading,
  };
};

export const useHolidaysByDateRange = (
  userId: number,
  fromDate: string,
  toDate: string,
  enabled: boolean,
) => {
  const { data = [], isLoading } = useQuery(
    vacationsKeys.holidays.byDateRange(userId ?? 0, fromDate, toDate),
    () =>
      getHolidaysByDate({
        fromDate,
        toDate,
        userId,
      }),
    {
      enabled,
      staleTime: 60 * 60 * 1000,
      keepPreviousData: true,
      select: data => data?.data,
    },
  );

  return { holidays: data, isLoading };
};

export const useHolidaysByCalendarYears = (options: {
  userId: number;
  enabled: boolean;
  year: number;
}) => {
  const { userId, enabled, year } = options;
  const { formatDate } = useFormatDate();
  const fromDate = formatDate(new Date(year, 0, 1), 'yyyy-MM-dd');
  const toDate = formatDate(new Date(year, 11, 31), 'yyyy-MM-dd');

  return useHolidaysByDateRange(userId, fromDate, toDate, enabled);
};

export const useGetRequestToCalendar = (
  month: Date,
  filters: VacationsFilterValues,
  isManagerView = true,
) => {
  const { user } = useAuth();
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();

  const monthFormated = formatDateInVacations({
    date: month,
    user: user!,
    type: 'yyyy-MM',
  });

  const requestCalendarData = useInfiniteQuery(
    vacationsKeys.manager.requestCalendar.filters(
      month,
      filters,
      isManagerView,
    ),
    ({ pageParam = 1 }) => {
      const params = {
        page: pageParam,
        limit: LIMIT,
        month: monthFormated,
        filters,
      };
      return isManagerView
        ? getRequestToCalendarManager(params)
        : getRequestToCalendarCollaborator(params);
    },
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('ERROR_LOADING_CALENDAR'));
      },
    },
  );

  return {
    data: requestCalendarData.data?.pages.length
      ? requestCalendarData.data?.pages.flatMap(page => page?.data.items)
      : null,
    count: requestCalendarData.data?.pages[0]
      ? requestCalendarData.data?.pages[0].data.count
      : null,
    isLoading: requestCalendarData?.isLoading,
    fetchNextPage: requestCalendarData?.fetchNextPage,
    hasNextPage: requestCalendarData?.hasNextPage,
    isFetchingNextPage: requestCalendarData?.isFetchingNextPage,
  };
};

export type SendReminderParam = {
  requestId: number;
  onSettled: () => void;
};

export const useSendReminderRequest = (param: SendReminderParam) => {
  const { t } = useLokaliseTranslation('time_off');
  const showGeneralError = useGeneralError();
  const { enqueueSnackbar } = useHuSnackbar();

  const { requestId, onSettled } = param;

  const vacationMassiveApprovallMutation = useMutation(
    () => sendReminder(requestId),
    {
      onSuccess: () => {
        enqueueSnackbar({
          title: t('SUCCESS_SEND_REMINDER'),
          variant: 'info',
        });
      },
      onError: err => {
        showGeneralError(err, t(`ERROR_SEND_REMINDER`));
      },
      onSettled,
    },
  );

  return vacationMassiveApprovallMutation;
};
