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

import { format as formatDate } from 'date-fns';
import { isEqual, omit } from 'lodash-es';
import { useModal } from '@material-hu/hooks/useModal';
import Container from '@material-hu/mui/Container';
import Stack from '@material-hu/mui/Stack';

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

import useGeneralError from 'src/hooks/useGeneralError';
import useHuGoTheme from 'src/hooks/useHuGoTheme';
import useHugoUnsavedWarning from 'src/hooks/useUnsavedWarning/altHugo';
import {
  assignUsersToPolicy,
  type CreatePolicyParams,
  editPolicy as editPolicyService,
  modifyPolicyApprovers,
  type PolicyApprovalStepsParams,
  type PolicyAssignParams,
} from 'src/services/timeTrackingService';
import { type Policy } from 'src/types/timeTracking';
import { UserSelection } from 'src/types/user';
import { useLokaliseTranslation } from 'src/utils/i18n';
import {
  getAssignmentTrackingProps,
  LogEvents,
  logEvent,
} from 'src/utils/logging';

import { ApprovalWorkflow } from 'src/components/ApprovalStepsList/types';
import { parseAssignedCollaborators } from 'src/components/FormSelectUsers/utils';

import {
  invalidatePoliciesList,
  invalidatePolicy,
  invalidatePolicyHourCategories,
  updatePolicyQueryData,
} from '../../queries';
import { timeTrackingRoutes } from '../../routes';
import SectionHeader from '../../SectionHeader';
import {
  areApprovalStepsDirty,
  formatApprovalStepsForPoliciesApi,
  mapFormApprovalStepsToInput,
} from '../Approvers/utils';
import {
  type DirtyFieldsSubtree,
  INITIAL_VALUES,
  isDirtyFieldsSubtreeDirty,
  type PolicyFormFields,
  policiesFields,
} from '../form';
import {
  formatUsersBasedOnMode,
  isFaceRecognitionBlocking,
  mapPolicyResponseAutoCloseToFormValues,
  parseUpdateParams,
} from '../utils';

import { usePolicyData } from './usePolicyData';

const formatFormInitValues = (response: Policy, totalUserCount: number) => {
  const collaboratorIds = [...(response?.userIds || [])].sort();
  return {
    ...INITIAL_VALUES,
    ...response,
    faceRecognitionBlocking: isFaceRecognitionBlocking(
      response.faceRecognitionEnabled,
      response.faceRecognitionBlocking,
    ),
    collaborators: collaboratorIds,
    userMode: parseAssignedCollaborators(collaboratorIds, totalUserCount),
    latenessToleranceEnabled: response.latenessTolerance !== null,
    latenessTolerance: response.latenessTolerance?.toString() || '',
    autoCloseLimitHours: response.autoCloseLimitHours?.toString() || '20',
    ...mapPolicyResponseAutoCloseToFormValues(response),
  };
};

const PolicyEdit = () => {
  const { t } = useLokaliseTranslation(['time_tracker', 'approval_requests']);
  const HuGoThemeProvider = useHuGoTheme();
  const { pathname } = useLocation();
  const policyId = parseInt(useParams().id || '');
  const navigate = useNavigate();
  const { enqueueSnackbar } = useHuSnackbar();
  const showGeneralError = useGeneralError();
  const form = useForm({
    defaultValues: INITIAL_VALUES,
    mode: 'onSubmit',
  });
  const {
    handleSubmit,
    formState: { dirtyFields, isDirty },
    reset,
  } = form;

  const [collaborators, userMode, approvalUsers, approvalWorkflow] = useWatch({
    control: form.control,
    name: [
      policiesFields.assignments.collaborators,
      policiesFields.assignments.userMode,
      policiesFields.approval.approvalUsers,
      policiesFields.approval.approvalWorkflow,
    ],
  });

  const { policyInfo, usersData, isLoadingInfo, initialApprovalState } =
    usePolicyData({
      policyId,
      reset,
      formatFormInitValues,
    });

  const { mutate: editPolicy, isLoading: isLoadingEdit } = useMutation(
    (params: Partial<CreatePolicyParams>) =>
      editPolicyService(policyId, params),
    {
      onSuccess: ({ data }) => {
        invalidatePoliciesList();
        invalidatePolicyHourCategories(policyId);
        // Since the edit endpoint always returns the userIds as [] we need to overwrite it
        const updatedData = { ...data, userIds: policyInfo?.userIds || [] };
        updatePolicyQueryData(policyId, updatedData);
        reset(formatFormInitValues(data, usersData?.data.count || 0));
        enqueueSnackbar({
          title: t('policies.edit_success'),
          variant: 'success',
        });
      },
      onError: err => {
        showGeneralError(err, t('policies.error_edit'));
      },
    },
  );

  const { mutate: modifyApprovers, isLoading: isLoadingModifyApprovers } =
    useMutation(
      (params: PolicyApprovalStepsParams) =>
        modifyPolicyApprovers(policyId, params),
      {
        onSuccess: () => {
          invalidatePolicy(policyId);
          invalidatePoliciesList();
          enqueueSnackbar({
            title: t('policies.approval_flow_edit_success'),
            variant: 'success',
          });
        },
        onError: err => {
          showGeneralError(
            err,
            t('approval_requests:step_configuration.actions.error'),
            t('approval_requests:step_configuration.actions.error_description'),
          );
        },
      },
    );

  const { mutate: assignUsers, isLoading: isLoadingAssignment } = useMutation(
    (params: PolicyAssignParams) => assignUsersToPolicy(policyId, params),
    {
      onSuccess: (_, params) => {
        // Since we do not have all the users we need to invalidate to fetch all the data and maintain the form consistency
        invalidatePoliciesList();
        logEvent(LogEvents.TIME_TRACKING_TIME_POLICY_ASSIGNMENT_UPDATED, {
          timePolicyId: policyId,
          ...getAssignmentTrackingProps(
            params.userMode,
            params.collaborators,
            usersData?.data.count ?? 0,
          ),
        });
        if (params.userMode === UserSelection.ALL_USERS) {
          invalidatePolicy(policyId);
        } else {
          if (policyInfo) {
            const updatedPolicy = {
              ...policyInfo,
              userIds: [...(params.collaborators || [])].sort(),
            };
            updatePolicyQueryData(policyId, updatedPolicy);
          }
        }
        enqueueSnackbar({
          title: t('policies.assignment_success'),
          variant: 'success',
        });
      },
      onError: err => {
        showGeneralError(err, t('policies.error_assignment'));
      },
    },
  );

  const userSelectionIsValid =
    userMode === UserSelection.NONE ||
    userMode === UserSelection.ALL_USERS ||
    (userMode === UserSelection.TARGETED_USERS && collaborators?.length > 0);

  const manuallySelectedAllCollabs =
    userMode === UserSelection.TARGETED_USERS &&
    collaborators.length === usersData?.data.count;

  const tabs = useMemo(() => {
    const list = [
      {
        label: t('policies.general_data'),
        value: timeTrackingRoutes.policyGeneral(policyId),
      },
      {
        label: t('policies.marking_methods'),
        value: timeTrackingRoutes.policyMethods(policyId),
      },
      {
        label: t('policies.hours_management'),
        value: timeTrackingRoutes.policyHoursManagement(policyId),
      },
      // Restrictions section will be enabled later
      // {
      //   label: t('restrictions'),
      //   value: timeTrackingRoutes.policyRestrictions(policyId),
      // },
      {
        label: t('policies.automations'),
        value: timeTrackingRoutes.policyAutomations(policyId),
      },
      {
        label: t('policies.assignments'),
        value: timeTrackingRoutes.policyAssignments(policyId),
        validations: [userSelectionIsValid],
      },
    ];
    list.push({
      label: t('approval_requests:approvers'),
      value: timeTrackingRoutes.policyApprovers(policyId),
    });
    return list;
  }, [policyId, t, userSelectionIsValid]);

  const currentTab = tabs.find(tab => tab.value === pathname);

  const handleTabChange = (value: string) => {
    navigate(value);
  };

  const confirmAssignmentModal = useModal(
    HuDialog,
    { maxWidth: 'sm' },
    {
      title: t('policies.confirm_assign_edit'),
      textBody: t('policies.confirm_assign_edit_description'),
      primaryButtonProps: {
        variant: 'primary',
        children: t('general:confirm'),
        loading: isLoadingAssignment,
        onClick: () => {
          if (userMode) {
            const selectionUserMode = manuallySelectedAllCollabs
              ? UserSelection.ALL_USERS
              : userMode;
            const assignmentParams = {
              userMode: selectionUserMode,
              collaborators:
                formatUsersBasedOnMode(collaborators)[selectionUserMode] || [],
              fromDate: formatDate(new Date(), 'yyyy-MM-dd'),
            };
            confirmAssignmentModal?.closeModal();
            assignUsers(assignmentParams);
          }
        },
      },
      secondaryButtonProps: {
        variant: 'tertiary',
        children: t('general:cancel'),
        onClick: () => {
          confirmAssignmentModal.closeModal();
        },
      },
    },
  );

  const invalidForm = currentTab?.validations
    ? !currentTab.validations?.every(Boolean)
    : false;

  const collaboratorsChanged = useMemo(
    () =>
      !isEqual(
        [...(collaborators || [])].sort(),
        [...(policyInfo?.userIds || [])].sort(),
      ),
    [collaborators, policyInfo],
  );

  const modifiedAssignments = useMemo(
    () =>
      userMode !==
        parseAssignedCollaborators(
          [...(policyInfo?.userIds ?? [])],
          usersData?.data.count,
        ) || collaboratorsChanged,
    [userMode, policyInfo, usersData, collaboratorsChanged],
  );

  const dirtyOutsideApproval = useMemo(
    () =>
      isDirtyFieldsSubtreeDirty(
        omit(dirtyFields, [
          policiesFields.approval.approvalUsers,
          policiesFields.approval.approvalWorkflow,
        ]),
      ),
    [dirtyFields],
  );

  // Do not memoize against the dirtyFields object reference:
  // RHF can keep the same Proxy identity while mutating nested dirty nodes.
  const usersApprovalDirtyNode =
    dirtyFields[policiesFields.approval.approvalUsers];
  const workflowApprovalDirtyNode =
    dirtyFields[policiesFields.approval.approvalWorkflow];
  const rhfApprovalSubtreeDirty =
    isDirtyFieldsSubtreeDirty(usersApprovalDirtyNode as DirtyFieldsSubtree) ||
    isDirtyFieldsSubtreeDirty(workflowApprovalDirtyNode as DirtyFieldsSubtree);

  // biome-ignore lint/correctness/useExhaustiveDependencies: <initialApprovalState is a ref>
  const dirtyApprovalSteps = useMemo(
    () =>
      areApprovalStepsDirty(initialApprovalState.current, {
        workflow: approvalWorkflow,
        steps: approvalUsers ?? [],
      }),
    [approvalWorkflow, approvalUsers],
  );

  // RHF can keep approval dirty marks after values return to the initial snapshot.
  const hasOnlyStaleApprovalDirtyFromRhf =
    !dirtyApprovalSteps && rhfApprovalSubtreeDirty && !dirtyOutsideApproval;

  const hasMeaningfulRhfDirty = isDirty && !hasOnlyStaleApprovalDirtyFromRhf;

  const dirtiedForm =
    collaboratorsChanged ||
    dirtyApprovalSteps ||
    dirtyOutsideApproval ||
    hasMeaningfulRhfDirty;

  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: () => {
          form.reset();
          UnsavedChangesModal.closeModal();
          blocker.proceed?.();
        },
      },
    },
    shouldBlock: dirtiedForm,
  });

  const submitChanges = (formValues: PolicyFormFields) => {
    const updatedValues = parseUpdateParams(formValues, dirtyFields);

    // Modify assignments has its own endpoint
    if (modifiedAssignments) {
      confirmAssignmentModal.showModal();
      return;
    }

    const approvalStepsChanged = areApprovalStepsDirty(
      initialApprovalState.current,
      {
        workflow: formValues.approvalWorkflow,
        steps: formValues.approvalUsers,
      },
    );
    // Modify approval steps has its own endpoint
    if (approvalStepsChanged) {
      const approversParams =
        formValues.approvalWorkflow === ApprovalWorkflow.CUSTOM
          ? formatApprovalStepsForPoliciesApi(
              mapFormApprovalStepsToInput(formValues.approvalUsers),
            )
          : formatApprovalStepsForPoliciesApi([]);
      modifyApprovers(approversParams);
      return;
    }

    editPolicy(updatedValues);
  };

  return (
    <HuGoThemeProvider>
      {UnsavedChangesModal.modal}
      {confirmAssignmentModal.modal}
      <Stack sx={{ minHeight: '100%' }}>
        <SectionHeader
          goBack={() => {
            navigate(timeTrackingRoutes.policiesList());
          }}
          title={t('policies.tracking_policies')}
        />
        <Container
          maxWidth={false}
          sx={{
            alignItems: 'center',
            backgroundColor: ({ palette }) =>
              palette.new.background.layout.default,
            display: 'flex',
            flex: 1,
            flexDirection: 'column',
            pt: 3,
          }}
          disableGutters
        >
          <FormProvider {...form}>
            <Stack
              sx={{
                flex: 1,
                width: '100%',
                '& > *': { px: '22%' },
              }}
            >
              <HuTabs
                value={currentTab?.value}
                tabs={tabs}
                onTabChange={handleTabChange}
                sx={{
                  mb: 4,
                  '.MuiTabs-flexContainer': {
                    gap: 1,
                  },
                }}
              />
              <Stack sx={{ flex: 1, pb: 10 }}>
                <Outlet
                  context={{
                    loadingPolicy: isLoadingInfo,
                  }}
                />
              </Stack>
              <Stack
                sx={{
                  position: 'sticky',
                  bottom: 0,
                  backgroundColor: ({ palette }) =>
                    palette.new.background.layout.tertiary,
                  boxShadow: theme => theme.shadows[2],
                  flexDirection: 'row',
                  justifyContent: 'flex-end',
                  py: 2,
                  width: '100%',
                  zIndex: 1000,
                }}
              >
                <Button
                  variant="primary"
                  size="large"
                  loading={
                    isLoadingEdit ||
                    isLoadingAssignment ||
                    isLoadingModifyApprovers
                  }
                  disabled={isLoadingInfo || !dirtiedForm || invalidForm}
                  onClick={handleSubmit(submitChanges)}
                >
                  {t('general:save')}
                </Button>
              </Stack>
            </Stack>
          </FormProvider>
        </Container>
      </Stack>
    </HuGoThemeProvider>
  );
};

export default PolicyEdit;
