import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FormProvider, useForm, useFormState } from 'react-hook-form';

import Stack from '@material-hu/mui/Stack';

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

import { useAuth } from 'src/contexts/JWTContext';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { getCurrentLocale } from 'src/utils/locale';
import { getWebAcceptanceClientContext } from 'src/utils/webClientContext';

import { LOAN_REASON_OTHER } from '../../constants';
import { useMicroloansTermsModal } from '../../contexts/MicroloansTermsModalContext';
import { useAcceptCollaboratorOffer } from '../../hooks/useAcceptCollaboratorOffer';
import { useOfferPaymentPlan } from '../../hooks/useOfferPaymentPlan';
import {
  buildLoanPlanAnalyticsPayload,
  trackLoanAcceptTyc,
  trackLoanApplicationSubmitted,
  trackLoanBankAccountNumberFilled,
  trackLoanBankAccountNumberModalBack,
  trackLoanBankAccountNumberModalConfirmed,
  trackLoanBankAccountNumberModalOpened,
  trackLoanBankAccountNumberShown,
  trackLoanBankNameResolved,
  trackLoanContactPhoneChanged,
  trackLoanStep1Viewed,
  trackLoanStep2Viewed,
} from '../../track';
import { type LoanOffer } from '../../types';
import { isAmountWithinOfferBounds } from '../../utils/buildLoanPaymentPlan';
import { collaboratorMinorUnitsToCurrency } from '../../utils/collaboratorAmount';
import {
  clampAmount,
  formatPaymentDateMX,
  getNextPayrollFridayDateMexico,
  parseApiDateString,
} from '../../utils/formatters';
import { resolveBankAccountState } from '../../utils/resolveBankAccountState';
import { validateBankAccountNumber } from '../../utils/validateBankAccountNumber';

import { AmountAndPlanStep } from './AmountAndPlanStep';
import { ConfirmBankAccountModal } from './ConfirmBankAccountModal';
import { ConfirmStep } from './ConfirmStep';
import { RequestOfferFormHeader } from './RequestOfferFormHeader';
import { SuccessStep } from './SuccessStep';

type RequestOfferStepsFormProps = {
  loanOffer: LoanOffer;
};

type RequestOfferContactFormValues = {
  phoneNumber: string;
  bankAccountNumber: string;
  bankName: string;
};

const RequestOfferStepsForm = ({ loanOffer }: RequestOfferStepsFormProps) => {
  const { t } = useLokaliseTranslation('loans');
  const { user } = useAuth();
  const locale = useMemo(() => getCurrentLocale(user), [user]);
  const { enqueueSnackbar } = useHuSnackbar();
  const {
    setTermsModalAmount,
    setTermsModalInstallmentsWeeks,
    setTermsModalBankAccountNumber,
    setTermsModalBankName,
  } = useMicroloansTermsModal();

  const hasMissingBankAccount =
    loanOffer.requiredAcceptanceFields.includes('bankAccountNumber');

  const defaultWeeks =
    loanOffer.planOptions[loanOffer.planOptions.length - 1]?.weeks ?? 12;

  const [step, setStep] = useState<number>(1);
  const goToStep = useCallback(
    (newStep: number) => {
      if (newStep === 2 && hasMissingBankAccount) {
        trackLoanBankAccountNumberShown();
      }
      setStep(newStep);
    },
    [hasMissingBankAccount],
  );
  const [amountMxn, setAmountMxn] = useState<number>(() =>
    clampAmount(
      loanOffer.preApprovedMaxAmountMxn,
      loanOffer.minAmountMxn,
      loanOffer.preApprovedMaxAmountMxn,
    ),
  );
  const [weeksPlan, setWeeksPlan] = useState<number>(defaultWeeks);
  const [amountValid, setAmountValid] = useState(true);
  const [loanReason, setLoanReason] = useState('');
  const [loanReasonComment, setLoanReasonComment] = useState('');
  const [loanTermsAccepted, setLoanTermsAccepted] = useState(false);
  const [confirmBankAccountModalOpen, setConfirmBankAccountModalOpen] =
    useState(false);

  const handleLoanReasonChange = useCallback((value: string) => {
    setLoanReason(value);
    if (value !== LOAN_REASON_OTHER) {
      setLoanReasonComment('');
    }
  }, []);

  const contactFormMethods = useForm<RequestOfferContactFormValues>({
    defaultValues: {
      phoneNumber: loanOffer.phoneNumber ?? '',
      bankAccountNumber: '',
      bankName: '',
    },
    mode: 'onChange',
    reValidateMode: 'onChange',
  });
  const { watch, getValues, setValue, setFocus, control } = contactFormMethods;
  const { errors } = useFormState({ control });
  const phoneNumber = watch('phoneNumber') ?? '';
  const bankAccountNumber = watch('bankAccountNumber') ?? '';
  const bankName = watch('bankName') ?? '';

  const {
    isFormatValid,
    resolvedBankName,
    isBankNameValid,
    contractBankAccountNumber,
    contractBankName,
  } = resolveBankAccountState(bankAccountNumber, loanOffer.currency, bankName);

  const prevResolvedBankNameRef = useRef<string | null>(null);
  useEffect(() => {
    if (!isFormatValid) {
      prevResolvedBankNameRef.current = null;
      if (getValues('bankName')) {
        setValue('bankName', '', { shouldValidate: true });
      }
      return;
    }
    if (resolvedBankName !== null) {
      if (prevResolvedBankNameRef.current !== resolvedBankName) {
        setValue('bankName', resolvedBankName, { shouldValidate: true });
        prevResolvedBankNameRef.current = resolvedBankName;
      }
    } else {
      if (prevResolvedBankNameRef.current !== null) {
        setValue('bankName', '', { shouldValidate: true });
      }
      prevResolvedBankNameRef.current = null;
    }
  }, [isFormatValid, resolvedBankName, getValues, setValue]);

  const phoneChangeEventLoggedRef = useRef(false);
  const phoneTrackSnapshotRef = useRef<string | undefined>(undefined);
  const bankAccountFilledLoggedRef = useRef(false);

  const handleLoanTermsAcceptedChange = useCallback((accepted: boolean) => {
    setLoanTermsAccepted(accepted);
    if (accepted) {
      trackLoanAcceptTyc();
    }
  }, []);

  const offerForPlan = step === 1 || step === 2 ? loanOffer : undefined;
  const { paymentPlan, isPaymentPlanLoading, simulateByWeeks } =
    useOfferPaymentPlan(offerForPlan, amountMxn);

  const acceptMutation = useAcceptCollaboratorOffer(loanOffer.id);

  const hasNoEligiblePlans = useMemo(
    () =>
      !isPaymentPlanLoading &&
      amountValid &&
      isAmountWithinOfferBounds(loanOffer, amountMxn) &&
      paymentPlan === undefined,
    [amountMxn, amountValid, isPaymentPlanLoading, loanOffer, paymentPlan],
  );

  const step1ContinueDisabled = useMemo(
    () =>
      !amountValid ||
      isPaymentPlanLoading ||
      hasNoEligiblePlans ||
      !simulateByWeeks?.get(weeksPlan),
    [
      amountValid,
      hasNoEligiblePlans,
      isPaymentPlanLoading,
      simulateByWeeks,
      weeksPlan,
    ],
  );

  const confirmSimulation = useMemo(() => {
    const raw = simulateByWeeks?.get(weeksPlan);
    if (!raw) {
      return undefined;
    }
    const firstPayrollDate = getNextPayrollFridayDateMexico();
    const y = firstPayrollDate.getUTCFullYear();
    const m = firstPayrollDate.getUTCMonth();
    const d = firstPayrollDate.getUTCDate();
    const installments = raw.installments?.map((installment, index) => {
      const week =
        installment.installmentNumber ?? installment.sequence ?? index + 1;
      const fallbackDate = new Date(Date.UTC(y, m, d + index * 7, 12, 0, 0));
      const parsedDate =
        parseApiDateString(installment.dueDate) ?? fallbackDate;
      return {
        week,
        dateLabel: formatPaymentDateMX(parsedDate, locale),
        paymentMxn: collaboratorMinorUnitsToCurrency(
          installment.dueAmount ?? installment.amount ?? raw.amountPerCycle,
        ),
      };
    });
    const catFromApi = raw.cat ?? raw.cea;
    const tnaFromApi = raw.tna ?? loanOffer.tnaAnnualPercent;
    return {
      principalMxn: collaboratorMinorUnitsToCurrency(raw.totalAmount),
      weeklyPaymentMxn: collaboratorMinorUnitsToCurrency(raw.amountPerCycle),
      totalToRepayMxn: collaboratorMinorUnitsToCurrency(raw.totalToRepay),
      costMxn: collaboratorMinorUnitsToCurrency(raw.cost),
      ...(catFromApi != null && Number.isFinite(catFromApi)
        ? { catPercent: catFromApi }
        : {}),
      ...(tnaFromApi != null && Number.isFinite(tnaFromApi)
        ? { tnaPercent: tnaFromApi }
        : {}),
      installments,
    };
  }, [loanOffer.tnaAnnualPercent, locale, simulateByWeeks, weeksPlan]);

  const isContactFormValid = useMemo(() => {
    const phoneValid = phoneNumber.trim() !== '' && !errors.phoneNumber;
    const bankAccountValid = hasMissingBankAccount
      ? bankAccountNumber.trim() !== '' &&
        !errors.bankAccountNumber &&
        isBankNameValid
      : true;
    return phoneValid && bankAccountValid && loanReason !== '';
  }, [
    bankAccountNumber,
    errors.bankAccountNumber,
    errors.phoneNumber,
    hasMissingBankAccount,
    isBankNameValid,
    loanReason,
    phoneNumber,
  ]);

  const submitAcceptance = useCallback(
    async (bankAccountNumber?: string, bankName?: string): Promise<boolean> => {
      try {
        const deviceMetadata = await getWebAcceptanceClientContext();
        await acceptMutation.mutateAsync({
          amountMxn,
          installmentsChosen: weeksPlan,
          phoneNumber: getValues('phoneNumber')?.trim() ?? '',
          loanReason,
          loanReasonComment,
          ...(bankAccountNumber !== undefined ? { bankAccountNumber } : {}),
          ...(bankName ? { bankName } : {}),
          deviceMetadata,
        });
        trackLoanApplicationSubmitted(
          buildLoanPlanAnalyticsPayload(
            amountMxn,
            weeksPlan,
            paymentPlan?.options.find(option => option.weeks === weeksPlan),
          ),
        );
        return true;
      } catch {
        enqueueSnackbar({
          variant: 'error',
          title: t('errors.accept_failed'),
        });
        return false;
      }
    },
    [
      acceptMutation,
      amountMxn,
      enqueueSnackbar,
      getValues,
      loanReason,
      loanReasonComment,
      paymentPlan?.options,
      t,
      weeksPlan,
    ],
  );

  const handleAccept = useCallback(async () => {
    if (hasMissingBankAccount) {
      trackLoanBankAccountNumberModalOpened();
      setConfirmBankAccountModalOpen(true);
      return;
    }
    if (await submitAcceptance(undefined, contractBankName ?? undefined)) {
      goToStep(3);
    }
  }, [contractBankName, goToStep, hasMissingBankAccount, submitAcceptance]);

  const handleConfirmBankAccount = useCallback(async () => {
    trackLoanBankAccountNumberModalConfirmed();
    const ok = await submitAcceptance(
      getValues('bankAccountNumber')?.trim() ?? '',
      contractBankName ?? undefined,
    );
    if (ok) {
      setConfirmBankAccountModalOpen(false);
      goToStep(3);
    }
  }, [contractBankName, getValues, goToStep, submitAcceptance]);

  const shouldRefocusBankAccountRef = useRef(false);

  const handleBackFromBankAccountModal = useCallback(() => {
    trackLoanBankAccountNumberModalBack();
    shouldRefocusBankAccountRef.current = true;
    setConfirmBankAccountModalOpen(false);
  }, []);

  const handleBankAccountModalExited = useCallback(() => {
    if (shouldRefocusBankAccountRef.current) {
      shouldRefocusBankAccountRef.current = false;
      setFocus('bankAccountNumber');
    }
  }, [setFocus]);

  useEffect(() => {
    setTermsModalAmount(amountMxn);
    setTermsModalInstallmentsWeeks(weeksPlan);
    return () => {
      setTermsModalAmount(undefined);
      setTermsModalInstallmentsWeeks(undefined);
    };
  }, [
    amountMxn,
    setTermsModalAmount,
    setTermsModalInstallmentsWeeks,
    weeksPlan,
  ]);

  useEffect(() => {
    setTermsModalBankAccountNumber(contractBankAccountNumber);
    setTermsModalBankName(contractBankName);
    return () => {
      setTermsModalBankAccountNumber(undefined);
      setTermsModalBankName(undefined);
    };
  }, [
    contractBankAccountNumber,
    contractBankName,
    setTermsModalBankAccountNumber,
    setTermsModalBankName,
  ]);

  useEffect(() => {
    setAmountMxn(
      clampAmount(
        loanOffer.preApprovedMaxAmountMxn,
        loanOffer.minAmountMxn,
        loanOffer.preApprovedMaxAmountMxn,
      ),
    );
  }, [loanOffer.minAmountMxn, loanOffer.preApprovedMaxAmountMxn]);

  useEffect(() => {
    const fallback =
      loanOffer.planOptions[loanOffer.planOptions.length - 1]?.weeks ?? 12;
    setWeeksPlan(prev =>
      loanOffer.planOptions.some(o => o.weeks === prev) ? prev : fallback,
    );
  }, [loanOffer.planOptions]);

  useEffect(() => {
    if (step === 1) {
      trackLoanStep1Viewed();
    }
  }, [step]);

  useEffect(() => {
    if (step === 2) {
      setLoanTermsAccepted(false);
    }
  }, [step]);

  useEffect(() => {
    if (step === 2) {
      phoneChangeEventLoggedRef.current = false;
      phoneTrackSnapshotRef.current = getValues('phoneNumber');
    }
  }, [getValues, step]);

  useEffect(() => {
    if (step !== 2) {
      return;
    }
    if (phoneChangeEventLoggedRef.current) {
      return;
    }
    if (phoneNumber === phoneTrackSnapshotRef.current) {
      return;
    }
    phoneChangeEventLoggedRef.current = true;
    trackLoanContactPhoneChanged();
  }, [phoneNumber, step]);

  useEffect(() => {
    if (!hasMissingBankAccount) {
      return;
    }
    const isValid = validateBankAccountNumber(bankAccountNumber);
    if (isValid && !bankAccountFilledLoggedRef.current) {
      bankAccountFilledLoggedRef.current = true;
      trackLoanBankAccountNumberFilled();
      trackLoanBankNameResolved(
        resolvedBankName !== null ? 'catalog' : 'manual',
      );
    }
    if (!isValid) {
      bankAccountFilledLoggedRef.current = false;
    }
  }, [bankAccountNumber, hasMissingBankAccount, resolvedBankName]);

  return (
    <FormProvider {...contactFormMethods}>
      <Stack sx={{ width: '100%', gap: 2.5, minWidth: 0 }}>
        <RequestOfferFormHeader />
        {step === 1 && (
          <AmountAndPlanStep
            loanOffer={loanOffer}
            paymentPlan={paymentPlan}
            isPaymentPlanLoading={isPaymentPlanLoading}
            continueDisabled={step1ContinueDisabled}
            selectedAmount={amountMxn}
            onAmountChange={setAmountMxn}
            selectedWeeks={weeksPlan}
            onSelectWeeks={setWeeksPlan}
            onAmountValidityChange={setAmountValid}
            onContinue={() => {
              trackLoanStep2Viewed(
                buildLoanPlanAnalyticsPayload(
                  amountMxn,
                  weeksPlan,
                  paymentPlan?.options.find(
                    option => option.weeks === weeksPlan,
                  ),
                ),
              );
              goToStep(2);
            }}
          />
        )}
        {step === 2 && (
          <ConfirmStep
            selectedAmount={amountMxn}
            selectedWeeks={weeksPlan}
            selectedPaymentOption={paymentPlan?.options.find(
              option => option.weeks === weeksPlan,
            )}
            onBack={() => goToStep(1)}
            confirmSimulation={confirmSimulation}
            acceptInFlight={acceptMutation.isLoading}
            acceptDisabled={
              !confirmSimulation || !isContactFormValid || !loanTermsAccepted
            }
            loanTermsAccepted={loanTermsAccepted}
            onLoanTermsAcceptedChange={handleLoanTermsAcceptedChange}
            loanReason={loanReason}
            onLoanReasonChange={handleLoanReasonChange}
            loanReasonComment={loanReasonComment}
            onLoanReasonCommentChange={setLoanReasonComment}
            tnaPercent={loanOffer.tnaAnnualPercent}
            onAccept={handleAccept}
            requiredAcceptanceFields={loanOffer.requiredAcceptanceFields}
            periodicity={loanOffer.periodicity}
            bankFound={
              resolvedBankName !== null && bankName === resolvedBankName
            }
            bankAccountNumberValid={isFormatValid}
          />
        )}
        {step >= 3 && <SuccessStep />}
      </Stack>
      <ConfirmBankAccountModal
        open={confirmBankAccountModalOpen}
        bankAccountNumber={bankAccountNumber}
        bankName={contractBankName ?? ''}
        onConfirm={handleConfirmBankAccount}
        onBack={handleBackFromBankAccountModal}
        onExited={handleBankAccountModalExited}
        inFlight={acceptMutation.isLoading}
        translations={{
          title: t('clabe.modal.title'),
          description: t('clabe.modal.description'),
          bankAccountNumberLabel: t('clabe.label'),
          bankLabel: t('clabe.bank_label'),
          bankPlaceholder: t('clabe.bank_placeholder'),
          confirm: t('clabe.modal.confirm'),
          back: t('clabe.modal.back'),
          close: t('legal.close_modal_aria'),
        }}
      />
    </FormProvider>
  );
};

export default RequestOfferStepsForm;
