import { useEffect } from 'react';
import {
  type FieldNamesMarkedBoolean,
  FormProvider,
  useForm,
} from 'react-hook-form';
import { useMutation, useQueryClient } from 'react-query';

import { isEmpty } from 'lodash-es';
import Stack from '@material-hu/mui/Stack';

import CardContainer from '@material-hu/components/design-system/CardContainer';
import { DrawerSize } from '@material-hu/components/design-system/Drawer/types';
import useHuSnackbar from '@material-hu/components/design-system/Snackbar';
import { useDrawerLayer } from '@material-hu/components/layers/Drawers';

import { useRequiredAuth } from 'src/contexts/JWTContext';
import useGeneralError from 'src/hooks/useGeneralError';
import {
  DEACTIVATION_OBSERVATION_FIELD,
  DEACTIVATION_REASON_FIELD,
} from 'src/pages/dashboard/Users/constants';
import useFieldComponents from 'src/pages/dashboard/Users/hooks/useFieldComponent';
import {
  updateUserKioskPinQuery,
  usersKeys,
} from 'src/pages/dashboard/Users/queries';
import * as organizationChartsService from 'src/services/organizationChartsService';
import * as usersService from 'src/services/usersService';
import { type RequestError } from 'src/types/services';
import { ErrorCode, type User } from 'src/types/user';
import { getDirtyValues } from 'src/utils/formUtils';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { LogEvents, logEvent } from 'src/utils/logging';

import {
  ProfileFieldTypes,
  type ProfileSectionField,
} from '../User/components/InformationTab/types';
import {
  mapProfileDataFormValuesForPatch,
  profileSectionFieldsToDefaultValues,
} from '../User/components/InformationTab/utils';

export type ProfileDataSectionId =
  | 'basic_info'
  | 'work_info'
  | 'access_data'
  | 'social_networks'
  | 'custom_fields';

export type ProfileDataSectionEditDrawerArgs = {
  user: User;
  sectionId: ProfileDataSectionId;
  sectionTitle: string;
  items: ProfileSectionField[];
};

const PROFILE_SECTION_EDIT_DRAWER_ID = 'profile-section-edit-drawer';

type ProfileSectionEditBodyProps = ProfileDataSectionEditDrawerArgs;

const ProfileSectionEditBody = ({
  items,
  user,
  sectionId,
}: ProfileSectionEditBodyProps) => {
  const { t } = useLokaliseTranslation('users');
  const { closeDrawer, updateDrawerConfig } = useDrawerLayer();
  const queryClient = useQueryClient();
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();
  const { user: loggedUser } = useRequiredAuth();

  const form = useForm({
    defaultValues: profileSectionFieldsToDefaultValues(items),
  });

  const {
    handleSubmit,
    formState: { isDirty, isValid, isSubmitted, dirtyFields },
  } = form;

  const { mutateAsync, isLoading: isMutationLoading } = useMutation(
    async (dirtyValues: Record<string, unknown>) => {
      const {
        boss,
        kioskPin,
        [DEACTIVATION_REASON_FIELD]: deactivationReason,
        [DEACTIVATION_OBSERVATION_FIELD]: deactivationObservation,
        ...patchBody
      } = dirtyValues;

      const mappedValues = mapProfileDataFormValuesForPatch(
        patchBody as Record<string, unknown>,
        items,
      );

      const { isCustomField } = items[0];

      if (!isEmpty(patchBody)) {
        if (isCustomField) {
          await usersService.patchUserProfileData(user.id, mappedValues);
        } else {
          await usersService.patchUser(user.id, mappedValues);
        }
      }

      if ('kioskPin' in dirtyValues) {
        const pinValue = (kioskPin as string) || '';
        await usersService.updateUserKioskPin(user.id, pinValue);
        updateUserKioskPinQuery(user.id, pinValue);
      }

      if ('boss' in dirtyValues) {
        const bossFromForm = boss as User | null;
        const initialBossUserId = (
          items.find(i => i.type === ProfileFieldTypes.BOSS)?.value as User
        )?.id;
        if (bossFromForm?.id) {
          // update new boss
          await organizationChartsService.updateBoss(user.id, bossFromForm.id);
        } else if (initialBossUserId) {
          // remove old boss
          await organizationChartsService.removeBoss(
            user.id,
            initialBossUserId,
          );
        }
      }

      if (
        DEACTIVATION_REASON_FIELD in dirtyValues ||
        DEACTIVATION_OBSERVATION_FIELD in dirtyValues
      ) {
        await usersService.patchUserDeactivation(user.id, {
          ...(DEACTIVATION_REASON_FIELD in dirtyValues
            ? {
                deactivationReason:
                  (deactivationReason as { value: string })?.value ?? null,
              }
            : {}),
          ...(DEACTIVATION_OBSERVATION_FIELD in dirtyValues
            ? {
                deactivationObservation:
                  (deactivationObservation as string)?.trim() || null,
              }
            : {}),
        });
      }
    },
    {
      onSuccess: async () => {
        logEvent(LogEvents.USER_UPDATED, {
          actorId: loggedUser.id,
          userId: user.employeeInternalId,
          section: 'profile',
        });
        await queryClient.invalidateQueries(usersKeys.user(user.id));
        await queryClient.invalidateQueries(usersKeys.userEditBosses(user.id));
        enqueueSnackbar({
          title: t('success_edit'),
          variant: 'success',
        });
        closeDrawer(PROFILE_SECTION_EDIT_DRAWER_ID);
      },
      onError: (err: RequestError) => {
        if (
          err?.response?.data?.code ===
          ErrorCode.TIME_TRACKING_KIOSK_PIN_ALREADY_EXISTS
        ) {
          form.setError('kioskPin', {
            message: t('error_pin_code_duplicated'),
          });
          return;
        }
        if (err?.response?.data?.code === ErrorCode.ALREADY_EXIST_USER) {
          form.setError('employeeInternalId', {
            message: t('ERROR_EXIST'),
          });
          return;
        }
        if (
          err?.response?.data?.code ===
          ErrorCode.BOSS_ASSIGNATION_GENERATE_CYCLE
        ) {
          enqueueSnackbar({ title: t('cycle_error'), variant: 'error' });
          return;
        }
        showGeneralError(err);
      },
    },
  );

  const { renderField } = useFieldComponents();

  const onSubmit = handleSubmit(async values => {
    const dirtyValues = getDirtyValues(
      dirtyFields as FieldNamesMarkedBoolean<Record<string, unknown>>,
      values as Record<string, unknown>,
    );
    if (isEmpty(dirtyValues)) {
      return;
    }
    const dirtyFieldNames = Object.keys(dirtyValues);
    if (dirtyFieldNames.length) {
      logEvent(LogEvents.USER_INFO_UPDATE, {
        userId: user.id,
        fields: dirtyFieldNames,
        employeeInternalId: user.employeeInternalId,
      });
    }
    await mutateAsync(dirtyValues as Record<string, unknown>);
    // fired only on successful save — mutateAsync throws on error
    logEvent(LogEvents.USER_PROFILE_EDIT_SAVED, {
      targetUserId: user.id,
      section: sectionId,
      fieldsEdited: dirtyFieldNames,
    });
  });

  const saveButtonDisabled = !isDirty || (isSubmitted && !isValid);

  useEffect(() => {
    updateDrawerConfig(PROFILE_SECTION_EDIT_DRAWER_ID, current => {
      if ('content' in current) {
        return current;
      }
      return {
        ...current,
        primaryButtonProps: {
          ...current.primaryButtonProps,
          disabled: saveButtonDisabled,
          loading: isMutationLoading,
        },
      };
    });
  }, [saveButtonDisabled, isMutationLoading, updateDrawerConfig]);

  return (
    <FormProvider {...form}>
      <form
        id={PROFILE_SECTION_EDIT_DRAWER_ID}
        onSubmit={onSubmit}
      >
        <Stack sx={{ gap: 2 }}>
          {items.map(field => {
            const rendered = renderField(field, user);
            if (!rendered) return null;
            return (
              <CardContainer
                fullWidth
                key={field.id ?? `${field.type}-${field.name}`}
                sx={{
                  backgroundColor: ({ palette }) =>
                    palette.new.background.elements.grey,
                }}
              >
                {rendered}
              </CardContainer>
            );
          })}
        </Stack>
      </form>
    </FormProvider>
  );
};

export const useProfileDataSectionEditDrawer = () => {
  const { t } = useLokaliseTranslation(['general']);
  const { openDrawer, closeDrawer } = useDrawerLayer();

  const openProfileSectionEditDrawer = (
    args: ProfileDataSectionEditDrawerArgs,
  ) => {
    openDrawer(
      {
        title: `${t('edit')} ${args.sectionTitle}`,
        size: DrawerSize.MEDIUM,
        children: <ProfileSectionEditBody {...args} />,
        primaryButtonProps: {
          children: t('save'),
          type: 'submit',
          form: PROFILE_SECTION_EDIT_DRAWER_ID,
        },
        secondaryButtonProps: {
          children: t('cancel'),
          onClick: () => closeDrawer(),
        },
      },
      PROFILE_SECTION_EDIT_DRAWER_ID,
    );
  };

  return { openProfileSectionEditDrawer, closeDrawer };
};
