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

import { type AxiosError, type AxiosResponse } from 'axios';

import { Periods } from '@material-hu/components/composed-components/DatePeriodSelector/types';
import useSnackbar from '@material-hu/components/design-system/Snackbar';

import { logEvent } from 'src/config/amplitude';
import { queryClient } from 'src/config/react-query';
import { useAuth } from 'src/contexts/JWTContext';
import { LOG_DELAY_MS } from 'src/pages/dashboard/timeTracking/constants';
import {
  invalidateCategorizedHoursApprovalQueries,
  timeTrackingKeys,
} from 'src/pages/dashboard/timeTracking/queries';
import {
  getCategorizedHour,
  getDayDetail,
  getTimeControlRegisters,
  getTimeTrackingPolicies,
  getUserSites,
  type OverrideCategorizedHourStatusParams,
  type OverrideCategorizedHourStatusResult,
  overrideCategorizedHourStatus,
  type ReviewCategorizedHourApprovalStepParams,
  type ReviewCategorizedHourApprovalStepResult,
  reviewCategorizedHourApprovalStep,
  type UpdateCategorizedHourParams,
  updateCategorizedHour,
} from 'src/services/timeTracking';
import { EventName } from 'src/types/amplitude';
import { type ResponseError } from 'src/types/services';
import {
  type ApprovalRequest,
  type ControlRegister,
  type ControlRegisterParams,
  Incidence,
  type UserControlParams,
} from 'src/types/timeTracking';
import { insertIf } from 'src/utils/arrays';
import { formatDateInTimezone } from 'src/utils/date';
import { useLokaliseTranslation } from 'src/utils/i18n';
import {
  daySummaryWithAutoClosedEntry,
  getPeriodFromStringDates,
  parseIncidences,
} from 'src/utils/timeTracking';

import useGeneralError from '../useGeneralError';

const PARAMS_INDEX = 2;

// TODO: This is provisional until backend handles the sorting
const sortRegisters = (
  prev: ControlRegister,
  next: ControlRegister,
  order: 'ASC' | 'DESC',
) => {
  const prevName = prev.fullName.toLowerCase();
  const nextName = next.fullName.toLowerCase();
  if (order === 'ASC') {
    return prevName > nextName ? 1 : -1;
  }
  return prevName < nextName ? 1 : -1;
};

// TODO: Delete autoclose incidence mapping after backend is updated
export const parseRegisters = (
  registers: ControlRegister[],
  tz: string,
): ControlRegister[] =>
  registers?.length
    ? registers?.map(r => {
        const autoclosePresentOnRegister = r.daySummaries.some(ds =>
          daySummaryWithAutoClosedEntry(ds),
        );
        return {
          ...r,
          incidencesTagsPresent: [
            ...parseIncidences(r.incidencesTagsPresent),
            ...insertIf(autoclosePresentOnRegister, Incidence.AUTO_CLOSE),
          ],
          daySummaries: r.daySummaries.map(ds => ({
            ...ds,
            incidences: [
              ...parseIncidences(ds.incidences),
              ...insertIf(
                daySummaryWithAutoClosedEntry(ds),
                Incidence.AUTO_CLOSE,
              ),
            ],
            timeTrackingEntries: ds.timeTrackingEntries.map(entry => ({
              ...entry,
              time: formatDateInTimezone(entry.time, tz),
            })),
          })),
        };
      })
    : [];

const useLogDateSelection = (startDate: string, endDate: string) => {
  const periodRef = useRef(getPeriodFromStringDates(startDate, endDate));

  useEffect(() => {
    const newPeriod = getPeriodFromStringDates(startDate, endDate);
    if (newPeriod === Periods.CUSTOM || newPeriod !== periodRef.current) {
      periodRef.current = newPeriod;
      logEvent(EventName.TIME_TRACKING_CONTROL_MODULE_DATE_SELECTED, {
        type: newPeriod,
        startDate: startDate,
        endDate: endDate,
      });
    }
  }, [startDate, endDate]);
};

export const useGetDaySummaries = (
  queryParams: ControlRegisterParams,
  order: 'ASC' | 'DESC' = 'ASC',
  queryLimit = 10,
  keepPreviousData = false,
) => {
  const showGeneralError = useGeneralError();
  const { t } = useLokaliseTranslation('time_tracker');
  const { instance } = useAuth();
  const [totalUsers, setTotalUsers] = useState<number | null>(null);
  const timerRef = useRef<NodeJS.Timeout | null>(null);
  useLogDateSelection(queryParams.startDate, queryParams.endDate);

  const hasNoFilters = useMemo(() => {
    // dates are not considered as filters because different periods may return different user counts
    const { startDate, endDate, ...filterParams } = queryParams;
    return !Object.values(filterParams).some(Boolean);
  }, [queryParams]);

  const {
    data: infiniteRegisters,
    isLoading,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
  } = useInfiniteQuery(
    timeTrackingKeys.controlRegisters(queryParams),
    ({ pageParam = 1, signal }) =>
      getTimeControlRegisters(
        {
          ...queryParams,
          page: pageParam,
          limit: queryLimit,
        },
        signal!,
      ),
    {
      getNextPageParam: (_, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};
        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => {
        showGeneralError(err, t('error_day_summaries'));
      },
      keepPreviousData,
    },
  );

  // This effect will run only once to store the total users count
  useEffect(() => {
    const count = infiniteRegisters?.pages[0]?.data.count;
    if (hasNoFilters) {
      setTotalUsers(count ?? null);
    }
  }, [infiniteRegisters, hasNoFilters]);

  const needBaseline =
    totalUsers === null &&
    !hasNoFilters &&
    !!queryParams.startDate &&
    !!queryParams.endDate;

  const { data: baselineResp } = useQuery(
    ['tt:baselineCount', queryParams.startDate, queryParams.endDate],
    ({ signal }) =>
      getTimeControlRegisters(
        {
          startDate: queryParams.startDate,
          endDate: queryParams.endDate,
          page: 1,
          limit: 1,
          search: '',
          itemIds: '',
          status: '',
          incidences: '',
          siteIds: '',
          userIds: '',
        },
        signal!,
      ),
    {
      enabled: needBaseline,
      staleTime: 5 * 60 * 1000,
    },
  );

  useEffect(() => {
    const count = baselineResp?.data?.count;
    if (needBaseline && typeof count === 'number') {
      setTotalUsers(count);
    }
  }, [baselineResp, needBaseline]);
  /*
   This effect will log the drawer filters applied (sites and segmentations), however it wont take into account empty filters
   and it will check the previous executed query params and validate that the filters have changed since then.
   This is to avoid logging the same filters when the user is just changing the date period or quick filters
  */
  useEffect(() => {
    // Obtain last executed query params
    const baseQuery = timeTrackingKeys.controlRegisters(undefined).slice(0, -1);
    const lastQueries = queryClient.getQueriesData(baseQuery);
    // If its the first query, there is no previous one
    if (lastQueries.length > 1) {
      const previouslyExecutedQueryParams = lastQueries[
        lastQueries.length - 2
      ][0][PARAMS_INDEX] as ControlRegisterParams;
      const changedParams =
        queryParams.itemIds !== previouslyExecutedQueryParams.itemIds ||
        queryParams.siteIds !== previouslyExecutedQueryParams.siteIds;
      const appliedDrawerFilters = queryParams.itemIds || queryParams.siteIds;
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }
      if (appliedDrawerFilters && changedParams) {
        timerRef.current = setTimeout(() => {
          logEvent(EventName.TIME_TRACKING_CONTROL_MODULE_FILTERED, {
            userCount: infiniteRegisters?.pages[0]?.data.count,
            sitesFilters: queryParams.siteIds,
            segmentsFilters: queryParams.itemIds,
          });
        }, LOG_DELAY_MS);
      }
    }
  }, [infiniteRegisters]);

  const registers = infiniteRegisters?.pages
    .flatMap(page => page.data.items)
    .sort((prev, next) => sortRegisters(prev, next, order));

  const registersTotal = registers?.length || 0;

  return {
    registers: parseRegisters(registers ?? [], instance?.timezone ?? 'UTC'),
    totalRegisters: registersTotal,
    currentUserCount: infiniteRegisters?.pages[0].data.count || 0,
    totalUserCount: totalUsers || 0,
    totalPages: infiniteRegisters?.pages[0].data.totalPages || undefined,
    isLoading,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
  };
};

export const useGetUserDaySummaries = (
  queryParams: UserControlParams,
  options?: { enabled?: boolean },
) => {
  const showGeneralError = useGeneralError();
  const { t } = useLokaliseTranslation('time_tracker');
  const { instance } = useAuth();
  useLogDateSelection(queryParams.startDate, queryParams.endDate);

  const completedParams = {
    ...queryParams,
    search: '',
    itemIds: '',
    status: '',
    incidences: '',
    siteIds: '',
  };

  const { data: userRegisters, isFetching } = useQuery(
    timeTrackingKeys.userControlRegisters(queryParams),
    ({ signal }) =>
      getTimeControlRegisters(
        {
          ...completedParams,
          page: 1,
          limit: 31, // We need 31 days max
        },
        signal!,
      ),
    {
      enabled: options?.enabled ?? true,
      onError: err => {
        showGeneralError(err, t('error_day_summaries'));
      },
    },
  );

  const registers = userRegisters?.data.items.sort((prev, next) =>
    sortRegisters(prev, next, 'ASC'),
  );

  const registersTotal = registers?.length || 0;

  return {
    registers: parseRegisters(registers ?? [], instance?.timezone ?? 'UTC'),
    totalRegisters: registersTotal,
    isFetching,
  };
};

export const useGetDayDetail = (
  userId: number,
  date: string,
  options?: { enabled?: boolean },
) => {
  const { t } = useLokaliseTranslation('time_tracker');
  const showGeneralError = useGeneralError();

  const { data, isLoading, isFetching } = useQuery(
    timeTrackingKeys.dayDetail(userId, date),
    () => getDayDetail(userId, date),
    {
      enabled: (options?.enabled ?? true) && !!userId && !!date,
      select: response => response.data,
      onError: err => {
        showGeneralError(err, t('error_day_summaries'));
      },
    },
  );

  return { dayDetail: data, isLoading, isFetching };
};

export const useGetUserPolicies = (userId: number) => {
  const { data: policies, isFetching } = useQuery(
    timeTrackingKeys.userPolicies(userId),
    () => getTimeTrackingPolicies(userId),
    {
      select: response => response.data,
      enabled: !!userId,
    },
  );

  return { policies, isFetching };
};

export const useGetUserSites = (userId: number) => {
  const { t } = useLokaliseTranslation('time_tracker');
  const showGeneralError = useGeneralError();

  const { data: sites, isFetching } = useQuery(
    timeTrackingKeys.userSites(userId),
    () => getUserSites(userId),
    {
      onError: err => {
        showGeneralError(err, t('error_user_sites'));
      },
      select: response => response.data,
      enabled: !!userId,
    },
  );

  return { sites, isFetching };
};

export const useGetCategorizedHour = (id: string | null) => {
  const { t } = useLokaliseTranslation('time_tracker');
  const showGeneralError = useGeneralError();

  const { data, isLoading, isFetching } = useQuery(
    timeTrackingKeys.categorizedHours.detail(id!),
    () => getCategorizedHour(id!),
    {
      enabled: !!id,
      select: response => response.data,
      onError: err => {
        showGeneralError(err, t('error_request_detail'));
      },
    },
  );

  return { categorizedHour: data, isLoading, isFetching };
};

export const useUpdateCategorizedHour = () => {
  const { t } = useLokaliseTranslation(['time_tracker', 'approval_requests']);
  const showGeneralError = useGeneralError();

  return useMutation(
    (params: UpdateCategorizedHourParams) => updateCategorizedHour(params),
    {
      onSuccess: (_, variables) => {
        queryClient.invalidateQueries(
          timeTrackingKeys.categorizedHours.detail(variables.id),
        );
        queryClient.invalidateQueries(timeTrackingKeys.categorizedHours.all());
        queryClient.invalidateQueries(
          timeTrackingKeys.categorizedHoursPending(),
        );
        queryClient.invalidateQueries({
          predicate: query =>
            query.queryKey.includes('categorized-hours-requests'),
        });
      },
      onError: err => {
        showGeneralError(err, t('approval_requests:error_updating_request'));
      },
    },
  );
};

export const useReviewCategorizedHourApprovalStep = () => {
  const { t } = useLokaliseTranslation(['time_tracker', 'approval_requests']);
  const showGeneralError = useGeneralError();
  const { enqueueSnackbar } = useSnackbar();

  return useMutation(
    (params: ReviewCategorizedHourApprovalStepParams) =>
      reviewCategorizedHourApprovalStep(params),
    {
      onSuccess: (
        response: AxiosResponse<ReviewCategorizedHourApprovalStepResult>,
        variables,
      ) => {
        const { approvalSteps, status } = response.data;
        // Update the detail cache directly with the new steps and status,
        // avoiding a full refetch since the response already contains the updated state.
        queryClient.setQueryData<AxiosResponse<ApprovalRequest> | undefined>(
          timeTrackingKeys.categorizedHours.detail(variables.categorizedHourId),
          old =>
            old
              ? { ...old, data: { ...old.data, approvalSteps, status } }
              : old,
        );
        // Still invalidate list queries since the item may change tabs.
        invalidateCategorizedHoursApprovalQueries();
      },
      onError: (err: AxiosError<ResponseError>, variables) => {
        if (err?.response?.data?.code === 'ALREADY_REVIEWED_APPROVAL_STEP') {
          enqueueSnackbar({
            title: t('approval_requests:already_reviewed_title'),
            description: t('approval_requests:already_reviewed_description'),
            variant: 'error',
          });
          queryClient.invalidateQueries(
            timeTrackingKeys.categorizedHours.detail(
              variables.categorizedHourId,
            ),
          );
          invalidateCategorizedHoursApprovalQueries();
          return;
        }
        showGeneralError(
          err,
          t('approval_requests:feedback.request_modified_error'),
          true,
          {
            description: t(
              'approval_requests:feedback.request_modified_error_description',
            ),
          },
        );
      },
    },
  );
};

export const useOverrideCategorizedHourStatus = () => {
  const showGeneralError = useGeneralError();

  return useMutation(overrideCategorizedHourStatus, {
    onSuccess: (
      response: AxiosResponse<OverrideCategorizedHourStatusResult>,
      variables,
    ) => {
      const { approvalSteps, status, statusOverride } = response.data;
      queryClient.setQueryData<AxiosResponse<ApprovalRequest> | undefined>(
        timeTrackingKeys.categorizedHours.detail(variables.id),
        old =>
          old
            ? {
                ...old,
                data: { ...old.data, approvalSteps, status, statusOverride },
              }
            : old,
      );
      invalidateCategorizedHoursApprovalQueries();
    },
    onError: (err: AxiosError<ResponseError>) => {
      showGeneralError(err);
    },
  });
};
