import { useCallback, useEffect, useMemo, useRef } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { useMutation, useQuery, useQueryClient } from 'react-query';

import { isNil } from 'lodash-es';

import useHuSnackbar from '@material-hu/components/design-system/Snackbar';
import { useDialogLayer } from '@material-hu/components/layers/Dialogs';
import {
  useDrawerLayer,
  useDrawerLayerItem,
} from '@material-hu/components/layers/Drawers';

import { useLokaliseTranslation } from 'src/utils/i18n';

import { GOAL_WEIGHTS_DRAWER_ID } from '../../constants';
import { useGoalFeaturesConfig } from '../../hooks/useGoalFeaturesConfig';
import { goalsKeys } from '../../queries';
import { getGoalCycle, updateGoalWeights } from '../../services';
import { type Goal } from '../../types';
import { buildGoalWeightFieldDefaults, isGoalDraftStatus } from '../../utils';

import {
  type ContentData,
  GoalWeightsDrawerContent,
} from './GoalWeightsDrawerContent';
import { useGoalWeightsModals } from './useGoalWeightsModals';
import useGoalWeightsTexts from './useGoalWeightsTexts';

export type UseGoalWeightsDrawerArgs = {
  goals: Goal[];
  goalCycleId: number | null;
  ownerId: number;
  query: string;
  fromMyGoalsPage: boolean;
};

export const useGoalWeightsDrawer = ({
  goals,
  goalCycleId,
  ownerId,
  query,
  fromMyGoalsPage,
}: UseGoalWeightsDrawerArgs) => {
  const { getSaveButtonText, isManagerApprovalMode } = useGoalWeightsTexts({
    fromMyGoalsPage,
  });
  const { t } = useLokaliseTranslation('goals');
  const { isApprovalFlowEnabled } = useGoalFeaturesConfig();

  const defaultValues = useMemo(
    () => buildGoalWeightFieldDefaults(goals),
    [goals],
  );
  const localForm = useForm({ defaultValues, mode: 'onChange' });

  useEffect(() => {
    localForm.reset(defaultValues);
  }, [defaultValues, localForm]);

  const watchedValues = useWatch({ control: localForm.control });
  const { totalFromLocal, isValid } = useMemo(() => {
    const total = Object.values(watchedValues).reduce(
      (sum: number, weight: unknown) => sum + (Number(weight) || 0),
      0,
    );
    return { totalFromLocal: total, isValid: total === 100 };
  }, [watchedValues]);

  const hasNonDraftGoals = useMemo(
    () => goals?.some((goal: Goal) => !isGoalDraftStatus(goal.status)),
    [goals],
  );
  const allGoalsAreApproved = useMemo(
    () => goals?.every((goal: Goal) => !isGoalDraftStatus(goal.status)),
    [goals],
  );
  const shouldApplyApprovalFlowOnMyGoals = useMemo(
    () => isApprovalFlowEnabled && fromMyGoalsPage,
    [isApprovalFlowEnabled, fromMyGoalsPage],
  );

  const { enqueueSnackbar } = useHuSnackbar();
  const queryClient = useQueryClient();
  const { closeDrawer: closeDrawerLayer } = useDrawerLayer();
  const closeDrawer = useCallback(
    () => closeDrawerLayer(GOAL_WEIGHTS_DRAWER_ID),
    [closeDrawerLayer],
  );

  const cycleIdKey = goalCycleId ?? 0;

  const { data: goalCycle } = useQuery(
    goalsKeys.cycle(cycleIdKey),
    () => {
      if (goalCycleId == null) {
        throw new Error('goalCycleId is required');
      }
      return getGoalCycle(goalCycleId);
    },
    { select: response => response.data, enabled: !!goalCycleId },
  );

  const updateWeightsMutation = useMutation(
    (newWeights: { goalId: number; weight: number }[]) => {
      if (goalCycleId == null) {
        return Promise.reject(new Error('goalCycleId is required'));
      }
      return updateGoalWeights(
        goalCycleId,
        ownerId,
        newWeights,
        isManagerApprovalMode,
      );
    },
    {
      onSuccess: () => {
        enqueueSnackbar({
          title: t('weights.save_success'),
          variant: 'success',
        });
        const cycleId = goalCycleId;
        if (isNil(cycleId)) {
          closeDrawer();
          return;
        }
        if (fromMyGoalsPage) {
          queryClient.invalidateQueries(goalsKeys.myGoalsList(cycleId, query));
        } else {
          queryClient.invalidateQueries(
            goalsKeys.weights(cycleId, Number(ownerId)),
          );
        }
        closeDrawer();
      },
      onError: () => {
        enqueueSnackbar({
          title: t('weights.error_updating_weights'),
          variant: 'error',
        });
      },
    },
  );

  const { openDialog: openErrorDialog, closeDialog: closeErrorDialog } =
    useDialogLayer();

  const showErrorApproveModal = useCallback(() => {
    openErrorDialog({
      title: t('error_approve_modal_title'),
      textBody: t('error_approve_modal_description', {
        min: goalCycle?.minGoalsPerOwner,
        max: goalCycle?.maxGoalsPerOwner,
      }),
      onClose: () => closeErrorDialog(),
      primaryButtonProps: {
        children: t('general:accept'),
        onClick: () => closeErrorDialog(),
      },
    });
  }, [openErrorDialog, closeErrorDialog, t, goalCycle]);

  const handleSubmit = useCallback((): void => {
    if (!goalCycleId || !ownerId) return;
    const formValues = localForm.getValues();
    const formGoals = goals.map((goal: Goal) => ({
      goalId: goal.id,
      weight: Number(formValues[`weight-${goal.id}`]),
    }));
    updateWeightsMutation.mutate(formGoals);
  }, [localForm, goals, updateWeightsMutation, goalCycleId, ownerId]);

  const handleExit = useCallback(() => {
    localForm.reset(defaultValues);
    closeDrawer();
  }, [localForm, defaultValues, closeDrawer]);

  const { showSaveConfirm, showExitConfirm } = useGoalWeightsModals({
    onConfirmSave: handleSubmit,
    onConfirmExit: handleExit,
  });

  const onSubmit = useCallback(() => {
    const isOutOfBounds =
      !isNil(goalCycle) &&
      goalCycle.enforceGoalCountBounds &&
      (goals.length < (goalCycle.minGoalsPerOwner || 0) ||
        goals.length > (goalCycle.maxGoalsPerOwner || 0));
    if (isOutOfBounds) {
      showErrorApproveModal();
      return;
    }
    if (isManagerApprovalMode) {
      showSaveConfirm();
    } else {
      handleSubmit();
    }
  }, [
    isManagerApprovalMode,
    handleSubmit,
    goalCycle,
    goals.length,
    showErrorApproveModal,
    showSaveConfirm,
  ]);

  const handleCloseDrawer = useCallback(() => {
    const currentValues = localForm.getValues();
    const formHasChanges = Object.keys(currentValues).some(
      key => Number(currentValues[key]) !== Number(defaultValues[key]),
    );
    if (formHasChanges) {
      showExitConfirm();
    } else {
      handleExit();
    }
  }, [localForm, defaultValues, showExitConfirm, handleExit]);

  const contentDataRef = useRef<ContentData>({
    goals: [],
    shouldApplyApprovalFlowOnMyGoals: false,
    hasNonDraftGoals: false,
    isValid: false,
    totalFromLocal: 0,
  });
  contentDataRef.current = {
    goals: goals ?? [],
    shouldApplyApprovalFlowOnMyGoals,
    hasNonDraftGoals,
    isValid,
    totalFromLocal,
  };

  const drawerContent = (
    <FormProvider {...localForm}>
      <GoalWeightsDrawerContent contentDataRef={contentDataRef} />
    </FormProvider>
  );

  const { openDrawer } = useDrawerLayerItem(GOAL_WEIGHTS_DRAWER_ID, {
    title: isManagerApprovalMode
      ? t('my_team.edit_weight_approve')
      : t('my_team.edit_weight'),
    hasBackButton: true,
    onClose: handleCloseDrawer,
    primaryButtonProps: {
      children: getSaveButtonText(),
      fullWidth: true,
      onClick: onSubmit,
      disabled:
        !isValid ||
        updateWeightsMutation.isLoading ||
        (shouldApplyApprovalFlowOnMyGoals && allGoalsAreApproved),
    },
    children: drawerContent,
  });

  return {
    openDrawer: () => openDrawer(),
    hasGoals: Boolean(goals && goals.length > 0),
  };
};
