import { FormProvider, useForm } from 'react-hook-form';
import { useMutation } from 'react-query';
import { useNavigate, useParams } from 'react-router';

import { type AxiosResponse } from 'axios';
import { isEqual, pickBy } from 'lodash-es';
import { useModal } from '@material-hu/hooks/useModal';
import Stack from '@material-hu/mui/Stack';
import Typography from '@material-hu/mui/Typography';

import Button from '@material-hu/components/design-system/Buttons/Button';
import HuDialog from '@material-hu/components/design-system/Dialog';
import HuSkeleton from '@material-hu/components/design-system/Skeleton';
import useHuSnackbar from '@material-hu/components/design-system/Snackbar';

import { queryClient } from 'src/config/react-query';
import useWorkSchedule from 'src/hooks/queryHooks/useWorkSchedule';
import useFormatDate from 'src/hooks/useFormatDate';
import useGeneralError from 'src/hooks/useGeneralError';
import useHuGoTheme from 'src/hooks/useHuGoTheme';
import useHugoUnsavedWarning from 'src/hooks/useUnsavedWarning/altHugo';
import * as timeTrackingService from 'src/services/timeTrackingService';
import {
  type FormScheduledDay,
  type FormTimeSlot,
  NightShiftAssignmentMode,
  type Schedule,
} from 'src/types/timeTracking';
import { getDirtyValues } from 'src/utils/formUtils';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { LogEvents, logEvent } from 'src/utils/logging';
import { type PaginatedResponse } from 'src/utils/tableUtils';
import { formatUTCDate, sortDays, sToMs } from 'src/utils/timeUtils';

import useTimeTrackingSettings from '../../hooks/useTimeTrackingSettings';
import { invalidateScheduleAssignments, timeTrackingKeys } from '../../queries';
import { timeTrackingRoutes } from '../../routes';
import SectionHeader from '../../SectionHeader';
import GeneralDataStep from '../components/GeneralDataStep';
import WorkdayStep from '../components/WorkdayStep';
import { ScheduleMode, type TimeSlotsParams } from '../types';
import {
  checkScheduleMode,
  generalTimeSlotsWithPlaceholder,
  parseDaysTimeSlots,
  parseStringTimeSlot,
  parseTimeSlot,
  scheduledDaysWithPlaceholder,
  timeSlotsAreValid,
} from '../utils';

const EditScheduleSkeleton = () => {
  return (
    <Stack sx={{ gap: 4, maxWidth: '966px', width: '100%' }}>
      <Stack sx={{ gap: 4 }}>
        <Stack sx={{ gap: 1 }}>
          <HuSkeleton
            height={34}
            width={177}
          />
          <HuSkeleton
            height={23}
            width={600}
          />
        </Stack>
        <Stack sx={{ gap: 2 }}>
          <HuSkeleton height={273} />
          <HuSkeleton height={125} />
          <HuSkeleton height={76} />
          <HuSkeleton height={76} />
          <HuSkeleton
            height={40}
            width={120}
            sx={{ alignSelf: 'flex-end' }}
          />
        </Stack>
      </Stack>
    </Stack>
  );
};

type FieldTypes = {
  name: string;
  description: string;
  scheduledDays: FormScheduledDay[];
  generalTimeSlots: FormTimeSlot[];
  scheduleMode: ScheduleMode;
};

const EditSchedule = () => {
  const navigate = useNavigate();
  const { t } = useLokaliseTranslation('work_schedules');
  const scheduleId = parseInt(useParams().id!);
  const HuGoThemeProvider = useHuGoTheme();
  const { formatDate } = useFormatDate();
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();

  const { data: timeTrackingSettings, isFetching: isFetchingSettings } =
    useTimeTrackingSettings();
  const enabledNightShifts =
    timeTrackingSettings?.nightShiftAssignment ===
    NightShiftAssignmentMode.SCHEDULE_INIT;

  const {
    data: workScheduleData,
    isLoading: isLoadingSchedule,
    isFetching: isFetchingSchedule,
  } = useWorkSchedule(scheduleId, {
    onSuccess: response => {
      const currentGeneralTimeSlots = generalTimeSlotsWithPlaceholder(response);
      form.reset({
        name: response.name || '',
        description: response.description || '',
        scheduledDays: scheduledDaysWithPlaceholder(response.scheduledDays).map(
          sd => ({
            ...sd,
            timeSlots: sd.timeSlots.map(parseStringTimeSlot),
          }),
        ),
        generalTimeSlots: currentGeneralTimeSlots.map(parseStringTimeSlot),
        scheduleMode: checkScheduleMode(
          currentGeneralTimeSlots,
          scheduledDaysWithPlaceholder(response.scheduledDays),
        ),
      });
    },
    staleTime: sToMs(120),
  });

  const originalSchedules = scheduledDaysWithPlaceholder(
    workScheduleData?.scheduledDays || [],
  );
  const originalGeneralTimeSlots =
    generalTimeSlotsWithPlaceholder(workScheduleData);
  const originalScheduleMode = checkScheduleMode(
    originalGeneralTimeSlots,
    originalSchedules,
  );

  const form = useForm<FieldTypes>({
    defaultValues: {
      name: workScheduleData?.name || '',
      description: workScheduleData?.description || '',
      scheduledDays: originalSchedules.map(sd => ({
        ...sd,
        timeSlots: sd.timeSlots.map(parseStringTimeSlot),
      })),
      generalTimeSlots: originalGeneralTimeSlots.map(parseStringTimeSlot),
      scheduleMode: originalScheduleMode,
    },
    mode: 'onChange',
  });
  const {
    formState: { isValid, isDirty, dirtyFields, isSubmitSuccessful },
    handleSubmit,
    watch,
  } = form;

  const { scheduledDays, generalTimeSlots, scheduleMode } = watch();

  const editScheduleMutation = useMutation(
    (updatedValues: Partial<timeTrackingService.CreateScheduleParams>) =>
      timeTrackingService.updateSchedule(scheduleId, { ...updatedValues }),
    {
      onMutate: variables => {
        logEvent(LogEvents.TIME_TRACKING_SCHEDULE_EDITED, {
          date: formatUTCDate(new Date()),
          editType: Object.keys(variables).join(','),
        });
      },
      onSuccess: async response => {
        const {
          name: responseName,
          description: responseDescription,
          scheduledDays: responseScheduledDays,
        } = response.data;
        enqueueSnackbar({
          title: t('GENERAL_EDIT_SUCCESS'),
          variant: 'success',
        });
        await invalidateScheduleAssignments(scheduleId);
        queryClient.setQueryData(
          timeTrackingKeys.schedule(response.data.id),
          response,
        );
        const staleScheduleData = queryClient.getQueryData(
          timeTrackingKeys.schedules(),
        );
        if (staleScheduleData) {
          queryClient.setQueryData<
            AxiosResponse<PaginatedResponse<Schedule>> | undefined
          >(timeTrackingKeys.schedules(), old => {
            if (!old) return old;
            // updates on scheduledDays / timeSlots creates a new schedule with different id
            // and disables the current id (remains as a previous version on DB)
            const updatedSchedulesList = old.data.items.filter(
              remainingSchedule => remainingSchedule.id !== scheduleId,
            );
            return {
              ...old,
              data: {
                ...old.data,
                items: [response.data, ...updatedSchedulesList],
              },
            };
          });
        }
        const responseGeneralSlots = generalTimeSlotsWithPlaceholder(
          response.data,
        );
        form.reset({
          name: responseName,
          description: responseDescription ?? '',
          scheduledDays: scheduledDaysWithPlaceholder(
            responseScheduledDays,
          ).map(sd => ({
            ...sd,
            timeSlots: sd.timeSlots.map(parseStringTimeSlot),
          })),
          generalTimeSlots: responseGeneralSlots.map(parseStringTimeSlot),
          scheduleMode: checkScheduleMode(
            responseGeneralSlots,
            scheduledDaysWithPlaceholder(responseScheduledDays),
          ),
        });
        confirmChangesModal.closeModal();
        navigate(timeTrackingRoutes.editSchedule(response.data.id), {
          replace: true,
        });
      },
      onError: err => {
        showGeneralError(err, t('GENERAL_EDIT_ERROR'));
      },
    },
  );

  const modifiedScheduledDays = !isEqual(
    originalSchedules,
    parseDaysTimeSlots({
      scheduledDays,
      usingGeneralSchedules: false,
      addEmptySlot: true,
    }),
  );

  const modifiedWorkdays = !isEqual(
    sortDays(scheduledDays.map(s => s.dayOfWeek)),
    sortDays(originalSchedules.map(cs => cs.dayOfWeek)),
  );

  const modifiedGeneralSlots =
    modifiedWorkdays ||
    (timeSlotsAreValid(generalTimeSlots) &&
      !isEqual(
        generalTimeSlots,
        generalTimeSlotsWithPlaceholder(workScheduleData).map(
          parseStringTimeSlot,
        ),
      ));

  const dirtiedSchedules =
    scheduleMode === ScheduleMode.custom
      ? modifiedScheduledDays
      : modifiedGeneralSlots;

  const confirmChangesModal = useModal(
    HuDialog,
    { maxWidth: 'sm' },
    {
      title: t('CONFIRM_SCHEDULE_CHANGES'),
      body: (
        <Stack sx={{ gap: 3 }}>
          <Typography variant="globalS">
            {t('CHANGED_SCHEDULES_DATA')}
          </Typography>
          <Stack
            sx={{
              backgroundColor: ({ palette }) =>
                palette.new.background.layout.default,
              borderRadius: '16px',
              gap: 1,
              p: 3,
            }}
          >
            <Typography
              variant="globalS"
              sx={{
                color: ({ palette }) => palette.new.text.neutral.disabled,
              }}
            >
              {t('WILL_MIGRATE_ASSIGNMENTS')}
            </Typography>
            <Typography
              variant="globalM"
              sx={{
                color: ({ palette }) => palette.new.text.neutral.disabled,
                fontWeight: 'semiBold',
              }}
            >
              {formatDate(new Date())}
            </Typography>
          </Stack>
        </Stack>
      ),
      primaryButtonProps: {
        children: t('GENERAL:CONFIRM'),
        loading: editScheduleMutation.isLoading,
        onClick: () => {
          handleSubmit(submit)();
        },
      },
      secondaryButtonProps: {
        children: t('GENERAL:CANCEL'),
        disabled: editScheduleMutation.isLoading,
        onClick: () => {
          confirmChangesModal.closeModal();
        },
      },
    },
  );

  const isFormDirty = isDirty || dirtiedSchedules;

  const { modal: UnsavedChangesModal, blocker } = useHugoUnsavedWarning({
    modalProps: {
      title: t('GENERAL:WISH_TO_EXIT_WITHOUT_SAVING'),
      body: t('GENERAL:UNSAVED_CHANGES_WILL_BE_LOST'),
      primaryButtonProps: {
        children: t('GENERAL:CANCEL'),
        onClick: () => {
          blocker.reset?.();
          UnsavedChangesModal.closeModal();
        },
      },
      secondaryButtonProps: {
        children: t('GENERAL:EXIT_WITHOUT_SAVING'),
        onClick: () => {
          UnsavedChangesModal.closeModal();
          blocker.proceed?.();
        },
      },
    },
    shouldBlock: isFormDirty && !isSubmitSuccessful,
  });

  const submit = (formValues: FieldTypes) => {
    const usingGeneralSchedules =
      formValues.scheduleMode === ScheduleMode.general;
    const parsedGeneralTimeSlots = parseTimeSlot(
      formValues.generalTimeSlots[0].startTime,
      formValues.generalTimeSlots[0].endTime,
    );
    const updatedGeneralSchedules = !isEqual(
      parsedGeneralTimeSlots,
      originalGeneralTimeSlots,
    );
    const parsedNewScheduledDays = parseDaysTimeSlots({
      scheduledDays: scheduledDays,
      usingGeneralSchedules: usingGeneralSchedules && updatedGeneralSchedules,
    });
    const updatedCustomSchedules = !isEqual(
      parsedNewScheduledDays,
      originalSchedules,
    );
    // Validate if general or custom timeSlots were modified
    const modifiedTimeSlots: TimeSlotsParams = {
      // For now we will only have only 1 general timeSlot
      generalTimeSlots:
        usingGeneralSchedules &&
        parsedGeneralTimeSlots.length > 0 &&
        updatedGeneralSchedules
          ? parsedGeneralTimeSlots
          : undefined,
      scheduledDays:
        updatedCustomSchedules || updatedGeneralSchedules
          ? parsedNewScheduledDays
          : undefined,
    };
    // dirtyFields will be populated with all the form fields if an array value is modified, we only use the values
    // that getDirtyValues can validate for certain (returned true)
    const modifiedFields = pickBy(dirtyFields, Boolean);
    const params = {
      ...getDirtyValues(modifiedFields, formValues),
      ...modifiedTimeSlots,
    };
    delete params.scheduleMode;
    editScheduleMutation.mutate(params);
  };

  const handleSubmitChanges = () => {
    if (modifiedGeneralSlots || modifiedScheduledDays) {
      confirmChangesModal.showModal();
    } else {
      handleSubmit(submit)();
    }
  };

  const isLoading =
    isLoadingSchedule || isFetchingSchedule || isFetchingSettings;

  return (
    <HuGoThemeProvider>
      {confirmChangesModal.modal}
      {UnsavedChangesModal.modal}
      <Stack
        sx={{
          backgroundColor: ({ palette }) =>
            palette.new.background.layout.default,
          minHeight: '100%',
        }}
      >
        <SectionHeader
          title={t('WORK_SCHEDULE_EDIT')}
          goBack={() => {
            navigate(timeTrackingRoutes.workSchedulesList());
          }}
          pill={
            workScheduleData?.version && workScheduleData.version > 1
              ? t('VERSION_NUMBER', { number: workScheduleData.version })
              : undefined
          }
        />
        <Stack sx={{ alignItems: 'center', px: 15, py: 4 }}>
          {isLoading && <EditScheduleSkeleton />}
          {!isLoading && (
            <FormProvider {...form}>
              <Stack sx={{ maxWidth: '966px', gap: 2 }}>
                <GeneralDataStep
                  title={t('GENERAL_DATA')}
                  subtitle={t('GENERAL_STEP_SUBTITLE')}
                  nameSubtitle={t('SCHEDULE_NAME')}
                  nameProps={{
                    label: t('GENERAL:NAME'),
                  }}
                  descriptionSubtitle={t('SCHEDULE_DESCRIPTION')}
                  descriptionProps={{
                    label: t('SCHEDULE_DESCRIPTION_LABEL'),
                  }}
                  isEdit
                />
                <WorkdayStep
                  enabledNightShifts={enabledNightShifts}
                  containerSx={{
                    borderRadius: 2,
                  }}
                />
              </Stack>
            </FormProvider>
          )}
        </Stack>
        <Stack
          sx={{
            alignItems: 'center',
            backgroundColor: theme =>
              theme.palette.new.background.layout.tertiary,
            boxShadow: theme => theme.shadows[2],
            py: 2,
            width: '100%',
          }}
        >
          <Stack
            sx={{
              flexDirection: 'row',
              justifyContent: 'flex-end',
              maxWidth: '966px',
              width: '100%',
            }}
          >
            <Button
              variant="primary"
              size="large"
              onClick={handleSubmitChanges}
              disabled={
                !isValid || !isFormDirty || editScheduleMutation.isLoading
              }
            >
              {t('GENERAL:SAVE')}
            </Button>
          </Stack>
        </Stack>
      </Stack>
    </HuGoThemeProvider>
  );
};

export default EditSchedule;
