import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { FormProvider, useForm } from 'react-hook-form';
import { useMutation, useQueryClient } from 'react-query';
import { useLocation, useNavigate, useParams } from 'react-router';

import { type AxiosError } from 'axios';
import { isEmpty, isNumber, mapValues, reduce, without } from 'lodash-es';
import Alert from '@material-hu/mui/Alert';
import Box from '@material-hu/mui/Box';
import Stack from '@material-hu/mui/Stack';
import Tab from '@material-hu/mui/Tab';
import Tabs from '@material-hu/mui/Tabs';
import Tooltip from '@material-hu/mui/Tooltip';
import Typography from '@material-hu/mui/Typography';

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

import { useRequiredAuth } from 'src/contexts/JWTContext';
import useFeatureFlag from 'src/hooks/useFeatureFlag';
import useGeneralError from 'src/hooks/useGeneralError';
import useHugoUnsavedWarning from 'src/hooks/useUnsavedWarning/altHugo';
import ArrowLeftIcon from 'src/icons/ArrowLeft';
import useUserEditPageData from 'src/pages/dashboard/Users/hooks/useUserEditPageData';
import {
  updateUserKioskPinQuery,
  usersKeys,
} from 'src/pages/dashboard/Users/queries';
import * as organizationChartsService from 'src/services/organizationChartsService';
import * as usersService from 'src/services/usersService';
import { FeatureFlags } from 'src/types/featureFlags';
import { type CustomField } from 'src/types/instance';
import { type ResponseError } from 'src/types/services';
import {
  ErrorCode,
  type TimeTrackingInfo,
  type User,
  type UserPermission,
  type UserSegmentation,
  UserStatus,
} from 'src/types/user';
import { insertIf } from 'src/utils/arrayUtils';
import {
  getCustomFieldDefaultValues,
  getDirtyValues,
  transformCustomFieldValues,
} from 'src/utils/formUtils';
import { getBEPermissionForRole } from 'src/utils/hardcodedPermissionsByRole';
import { formatTitle } from 'src/utils/helmetUtils';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { LogEvents, logEvent } from 'src/utils/logging';
import {
  INSTANCE_ONLY_PERMISSIONS,
  MODULES_PERMISSIONS,
  UserPermissions,
} from 'src/utils/permissions';
import { localDateToSimpleDateString } from 'src/utils/timeUtils';
import {
  getFullname,
  instanceHasRoles,
  permissionsObjectToArray,
} from 'src/utils/userUtils';

import CenteredCircularProgress from 'src/components/CircularProgress';

import useRolesPermissions from '../../RolesAndPermissions/hooks/useRolesPermissions';
import { type UserGrant } from '../grants';
import useUsersPermissions from '../hooks/useUsersPermissions';
import { usersRoutes } from '../routes';

import GeneralTab from './components/GeneralTab';
import PermissionsTab from './components/PermissionsTab';
import RolesTab from './components/RolesTab';
import SegmentationTab from './components/SegmentationTab';
import { type FieldValues } from './form';

type Props = {
  userSegmentation?: UserSegmentation[];
  userPermissions?: UserPermission;
  userBoss?: User;
  user: (User & { grants: string[] }) | null;
  instanceProfileFields: CustomField[];
  timeTrackingInfo: TimeTrackingInfo;
};

const UserEdit = ({
  user,
  userSegmentation,
  userPermissions,
  userBoss,
  instanceProfileFields,
  timeTrackingInfo,
}: Props) => {
  const { t } = useLokaliseTranslation('users');
  const queryClient = useQueryClient();
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();
  const {
    permissions: loggedUserPermissions,
    instance,
    user: loggedUser,
  } = useRequiredAuth();
  const { canManageUsers } = useUsersPermissions();
  const { canManageRoles, canUpdateRolesAssignment } = useRolesPermissions();
  const isCerberusEnabled = useFeatureFlag(FeatureFlags.CERBERUS_ENABLED);

  const navigate = useNavigate();

  const { state } = useLocation() as { state: { backLink: string } };
  const goBack = () => navigate(state?.backLink || usersRoutes.base());

  useEffect(() => {
    logEvent(LogEvents.USER_EDITION_ENTERED);
  }, []);

  const initialSegmentationForLog =
    userSegmentation?.flatMap(group => ({
      segmentationId: group.item?.groupId,
      id: group.item?.id,
      name: group.item?.name,
    })) || [];

  const form = useForm<FieldValues>({
    defaultValues: {
      hiringDate: user?.hiringDate || null,
      birthdate: user?.birthdate || null,
      phoneNumber: user?.phoneNumber || '',
      linkedIn: user?.socialNetworks?.linkedIn || '',
      instagram: user?.socialNetworks?.instagram || '',
      facebook: user?.socialNetworks?.facebook || '',
      twitter: user?.socialNetworks?.twitter || '',
      nickname: user?.nickname || '',
      firstName: user?.firstName || '',
      lastName: user?.lastName || '',
      employeeInternalId: user?.employeeInternalId || '',
      email: user?.email || '',
      permissions: userPermissions || {},
      initialPermissionRole: null,
      permissionRole: null,
      segmentation:
        userSegmentation?.flatMap(group => group.item)?.map(item => item.id) ||
        [],
      segmentationForLog: initialSegmentationForLog,
      profileData: getCustomFieldDefaultValues(
        instanceProfileFields,
        user?.profileData,
      ),
      boss: (userBoss as any) || null,
      reviewer: user?.reviewer || null,
      department: user?.department || null,
      position: user?.jobPosition || null,
      password: '',
      kioskPin: timeTrackingInfo?.userSettings?.kioskPin || '',
      deactivationReason: user?.deactivationReason || null,
      deactivationObservation: user?.deactivationObservation || '',
    },
    mode: 'onChange',
  });
  const {
    formState: { isSubmitting, dirtyFields, isDirty, isValid },
    handleSubmit,
    trigger,
  } = form;

  const { mutateAsync, status: mutationStatus } = useMutation(
    async (values: Partial<FieldValues>) => {
      const {
        permissions,
        segmentation,
        profileData,
        initialPermissionRole,
        permissionRole,
        department,
        position,
        reviewer,
        segmentationForLog,
        deactivationReason,
        deactivationObservation,
        ...basicInfo
      } = values;

      let currentUser: any = user;

      const commonBody = {
        ...basicInfo,
        reviewerId: reviewer === null ? null : reviewer?.id,
        departmentId: department === null ? null : department?.id,
        jobPositionId: position === null ? null : position?.id,
      };

      // In case a value is deleted it will be null, so we'll only be filtering pristine values
      const changedCommonBody = Object.values(commonBody).filter(
        v => v !== undefined,
      );

      // Only use this endpoint if the user pin code is being updated
      if (currentUser && 'kioskPin' in values) {
        await usersService.updateUserKioskPin(
          currentUser.id,
          values.kioskPin || '',
        );
        updateUserKioskPinQuery(currentUser.id, values.kioskPin || '');
      }

      if (changedCommonBody.length) {
        currentUser = (
          await (currentUser
            ? usersService.patchUser(currentUser.id, commonBody)
            : usersService.createUser({
                ...commonBody,
                segmentationItemIds: segmentation,
                profileData,
                kioskPin: loggedUserPermissions.includes(
                  UserPermissions.MANAGE_TIME_TRACKING,
                )
                  ? values.kioskPin || ''
                  : undefined, // Hide this param if the user does not have the manage permission
              }))
        ).data;
      }
      if (profileData && Object.keys(profileData).length) {
        await usersService.patchUserProfileData(currentUser.id, profileData);
      }
      if (
        'deactivationReason' in values ||
        'deactivationObservation' in values
      ) {
        await usersService.patchUserDeactivation(currentUser.id, {
          deactivationReason: form.getValues('deactivationReason') || null,
          deactivationObservation:
            form.getValues('deactivationObservation')?.trim() || null,
        });
      }

      // first we check whether boss is dirty
      if ('boss' in values) {
        const { boss } = values;
        if (boss) {
          await organizationChartsService.updateBoss(currentUser.id, boss.id);
        } else {
          await organizationChartsService.removeBoss(
            currentUser.id,
            userBoss?.id!,
          );
        }
      }
      if (instanceHasRoles(instance.id)) {
        if (
          loggedUserPermissions.includes(UserPermissions.MANAGE_CAPABILITIES)
        ) {
          await usersService.updateUserPermissions({
            capabilityNames: getBEPermissionForRole(permissionRole!)!,
            id: currentUser.id,
          });
          logEvent(LogEvents.USERS_ROLE_CHANGED, {
            userId: currentUser.id,
            employeeInternalId: currentUser.employeeInternalId,
            instanceId: instance.id,
            previousRole: initialPermissionRole,
            newRole: permissionRole,
          });
        }
      } else if (permissions) {
        const capabilityNames = without(
          permissionsObjectToArray(permissions),
          ...[
            ...MODULES_PERMISSIONS,
            ...INSTANCE_ONLY_PERMISSIONS,
            UserPermissions.DEVOPS,
            UserPermissions.VIEW_POST_STATS,
          ],
        );
        await usersService.updateUserPermissions({
          capabilityNames,
          id: currentUser.id,
        });
      }
      if (segmentation) {
        await usersService.updateUserSegmentation(segmentation, currentUser.id);
        logEvent(LogEvents.USERS_SEGMENTATION_UPDATED, {
          employeeInternalId: currentUser.employeeInternalId,
          oldValue: initialSegmentationForLog,
          newValue: segmentationForLog,
        });
      }
      return currentUser;
    },
    {
      onSuccess: async response => {
        if (!user) {
          logEvent(LogEvents.USER_CREATE, {
            userId: response.id,
            employeeInternalId: response.employeeInternalId,
          });
        } else {
          logEvent(LogEvents.USER_UPDATED, {
            actorId: loggedUser.id,
            userId: user.employeeInternalId,
            section: tabs[currentTabIndex].section,
          });
        }
        queryClient.invalidateQueries(usersKeys.all());
        enqueueSnackbar({
          title: t(`SUCCESS_${user ? 'EDIT' : 'CREATE'}`),
          variant: 'success',
        });
        goBack();
      },
      onError: (err: AxiosError<ResponseError>) => {
        if (
          err?.response?.data?.code ===
          ErrorCode.TIME_TRACKING_KIOSK_PIN_ALREADY_EXISTS
        ) {
          // Do not show snackbar, just show the error in the form
          form.setError('kioskPin', {
            message: t('ERROR_PIN_CODE_DUPLICATED'),
          });
          return;
        }
        if (err?.response?.data?.code === ErrorCode.ALREADY_EXIST_USER) {
          enqueueSnackbar({ title: t('ERROR_EXIST'), variant: 'error' });
        } else if (
          err?.response?.data?.code ===
          ErrorCode.BOSS_ASSIGNATION_GENERATE_CYCLE
        ) {
          enqueueSnackbar({ title: t('CYCLE_ERROR'), variant: 'error' });
        } else {
          showGeneralError(err);
        }
      },
    },
  );

  const handleSave = handleSubmit(async values => {
    let dirtyValues = getDirtyValues(dirtyFields, values);

    if (user) {
      const dirtyBasicInfo = Object.keys(dirtyValues).filter(
        value => !['permissions', 'segmentation'].includes(value),
      );
      if (dirtyBasicInfo.length) {
        logEvent(LogEvents.USER_INFO_UPDATE, {
          userId: user.id,
          fields: dirtyBasicInfo,
          employeeInternalId: user.employeeInternalId,
        });
      }

      if (dirtyValues.permissions) {
        const dirtyPermissions = reduce(
          dirtyValues.permissions,
          (result, value, key) =>
            value === userPermissions?.[key as keyof UserPermission]
              ? result
              : result.concat({ [key]: value }),
          [] as Record<string, boolean>[],
        );
        logEvent(LogEvents.USER_PERMISSIONS_UPDATE, {
          userId: user.id,
          capabilities: dirtyPermissions,
        });
      }
    }

    if (dirtyValues.hiringDate) {
      dirtyValues.hiringDate = localDateToSimpleDateString(
        dirtyValues.hiringDate,
      );
    }

    if (dirtyValues.birthdate) {
      dirtyValues.birthdate = localDateToSimpleDateString(
        dirtyValues.birthdate,
      );
    }

    if (dirtyValues.phoneNumber) {
      // the phone number component doesn't allow the remove the prefix +54
      // if the number is too short, it's probably just the country code so we remove it
      dirtyValues.phoneNumber =
        dirtyValues.phoneNumber.length > 4 ? dirtyValues.phoneNumber : null;
    }

    if (dirtyValues.profileData) {
      dirtyValues.profileData = transformCustomFieldValues(
        instanceProfileFields,
        dirtyValues.profileData,
      );
    }

    // replace empty strings and such with null
    const replaceEmptyFields = (value: object) =>
      isEmpty(value) && !isNumber(value) ? null : value;

    dirtyValues = {
      ...mapValues(dirtyValues, replaceEmptyFields),
      segmentation: dirtyValues.segmentation,
      profileData: mapValues(dirtyValues.profileData, replaceEmptyFields),
    };

    await mutateAsync(dirtyValues);
  });

  const { modal, blocker } = useHugoUnsavedWarning({
    modalProps: {
      title: t('general:unsaved_changes_title'),
      body: t('general:unsaved_changes_will_be_lost'),
      primaryButtonProps: {
        children: t('general:unsaved_changes.confirm'),
        onClick: () => {
          modal.closeModal();
          blocker.proceed!();
        },
      },
      secondaryButtonProps: {
        children: t('general:cancel'),
        onClick: () => {
          modal.closeModal();
          blocker.reset!();
        },
      },
    },
    shouldBlock: isDirty && mutationStatus === 'idle',
  });

  const tabs = [
    { label: t('GENERAL'), component: GeneralTab, section: 'general' },
    ...insertIf(!isCerberusEnabled || canManageUsers, {
      label: t('SEGMENTATION'),
      component: SegmentationTab,
      section: 'segmentation',
    }),
    ...insertIf(
      !isCerberusEnabled &&
        loggedUserPermissions.includes(UserPermissions.MANAGE_CAPABILITIES),
      {
        label: t('PERMISSIONS'),
        component: PermissionsTab,
        section: 'permissions',
      },
    ),
    ...insertIf(
      isCerberusEnabled && (canManageRoles || canUpdateRolesAssignment),
      {
        label: t('PERMISSIONS'),
        component: RolesTab,
        section: 'roles',
      },
    ),
  ];

  const [currentTabIndex, setCurrentTabIndex] = useState(0);
  const CurrentTabComponent = tabs[currentTabIndex].component;
  const handleChangeTab = async (_: React.SyntheticEvent, value: number) => {
    const formIsValid = await trigger();
    if (formIsValid) {
      setCurrentTabIndex(value);
    } else {
      // trigger() doesnt revalidate each input on change, but handleSubmit() does
      handleSave();
    }
  };

  const errorCount = Object.keys(form.formState.errors).length;
  const showAlert = !!errorCount && currentTabIndex === 0;
  const statusAlert =
    !!user &&
    [UserStatus.DEACTIVATED, UserStatus.UNCLAIMED].includes(user.status) &&
    ({
      severity: user.status === UserStatus.DEACTIVATED ? 'info' : 'warning',
      title: t(
        user.status === UserStatus.DEACTIVATED
          ? 'name_is_deactivated'
          : 'name_is_unclaimed',
        { name: getFullname(user) },
      ),
    } as const);

  return (
    <FormProvider {...form}>
      {modal.modal}
      <Helmet>
        <title>
          {formatTitle(
            user ? t('EDIT_USER', { name: getFullname(user) }) : t('NEW_USER'),
          )}
        </title>
      </Helmet>
      <Stack
        px={4}
        py={2}
      >
        <Button
          startIcon={<ArrowLeftIcon fontSize="small" />}
          onClick={goBack}
          variant="outlined"
          sx={{ width: '100px' }}
        >
          {t('BACK')}
        </Button>
        <Stack
          sx={{
            mt: 2,
            mb: 3,
            p: 2,
            pb: 1,
            backgroundColor: 'background.paper',
            borderRadius: '15px',
          }}
        >
          <Typography variant="h6">
            {user ? t('EDIT_USER', { name: getFullname(user) }) : t('NEW_USER')}
          </Typography>
          {statusAlert && (
            <Alert
              severity={statusAlert.severity}
              sx={{ mt: 1 }}
            >
              {statusAlert.title}
            </Alert>
          )}
          <Tabs
            value={currentTabIndex}
            onChange={handleChangeTab}
            sx={{ mt: 1 }}
            variant="scrollable"
          >
            {tabs.map(tab => (
              <Tab
                key={tab.label}
                label={tab.label}
                sx={{ px: 1, mx: 1, textTransform: 'none' }}
              />
            ))}
          </Tabs>
          {showAlert && (
            <Alert
              severity="error"
              sx={{
                my: 1,
                borderRadius: 0.5,
                '& .MuiAlert-message': {
                  p: 0.5,
                },
              }}
            >
              <Typography variant="subtitle1">
                {t('ALERT_ERROR_TITLE_FIELD', {
                  count: errorCount,
                })}
              </Typography>
              <Typography variant="body2">
                {t('ALERT_ERROR_DESCRIPTION_FIELD', {
                  count: errorCount,
                })}
              </Typography>
            </Alert>
          )}
          <Box sx={{ p: 2 }}>
            <CurrentTabComponent
              user={user}
              userGrants={user?.grants as UserGrant[]}
              instanceProfileFields={instanceProfileFields}
              userBoss={userBoss}
              timeTrackingInfo={timeTrackingInfo}
            />
          </Box>
          <Stack
            justifyContent="flex-end"
            direction="row"
            alignItems="center"
            sx={{
              position: 'sticky',
              bottom: 0,
              backgroundColor: 'background.paper',
              py: 1,
              zIndex: 10,
            }}
          >
            <Button
              variant="text"
              onClick={goBack}
            >
              {t('CANCEL')}
            </Button>
            <Tooltip
              title={!isValid ? t('CHECK_FIELDS') : ''}
              arrow
            >
              <span>
                <Button
                  loading={isSubmitting}
                  onClick={handleSave}
                  disabled={!isDirty || !isValid}
                  variant="contained"
                  sx={{ ml: 2 }}
                >
                  {t('SAVE')}
                </Button>
              </span>
            </Tooltip>
          </Stack>
        </Stack>
      </Stack>
    </FormProvider>
  );
};

const Container = () => {
  const id = parseInt(useParams().id ?? '');
  const pageData = useUserEditPageData(id);

  // Forbidden before isLoading: isLoading includes unrelated recognition/time-tracking fetches.
  if (pageData.isUserEditProxyForbidden) {
    return null;
  }

  if (pageData.isLoading) {
    return (
      <CenteredCircularProgress
        centered
        sx={{ mt: 5 }}
      />
    );
  }

  return (
    <UserEdit
      {...(pageData.completeUserData ?? {})}
      user={pageData.completeUserData?.user ?? null}
      instanceProfileFields={pageData.instanceProfileFields}
      timeTrackingInfo={pageData.timeTrackingInfo}
    />
  );
};

export default Container;
