import {
  createContext,
  type ReactNode,
  useCallback,
  useContext,
  useMemo,
  useState,
} from 'react';
import { useQuery } from 'react-query';

import { normalizeContractMarkdownNewlines } from 'src/components/MarkdownContent/markdownParser';

import { TermsModal } from '../components/requestOffer/TermsModal';
import { useActiveCollaboratorLoanPlan } from '../hooks/useActiveCollaboratorLoanPlan';
import { useGetLoanOffer } from '../hooks/useGetLoanOffer';
import { microloansKeys } from '../queries';
import {
  fetchCollaboratorOfferContractPreview,
  fetchCollaboratorPlanContract,
} from '../services/microloanService';
import { trackLoanTermsOpened } from '../track';
import {
  collaboratorMinorUnitsToCurrency,
  currencyToCollaboratorMinorUnits,
} from '../utils/collaboratorAmount';
import { clampAmount } from '../utils/formatters';

export type TermsModalContractStrategy = 'requestOffer' | 'status';

type MicroloansTermsModalContextValue = {
  openTermsModal: (strategy?: TermsModalContractStrategy) => void;
  closeTermsModal: () => void;
  setTermsModalAmount: (amount: number | undefined) => void;
  setTermsModalInstallmentsWeeks: (weeks: number | undefined) => void;
  setTermsModalBankAccountNumber: (value: string | undefined) => void;
  setTermsModalBankName: (value: string | undefined) => void;
};

const MicroloansTermsModalContext =
  createContext<MicroloansTermsModalContextValue | null>(null);

export const useMicroloansTermsModal = (): MicroloansTermsModalContextValue => {
  const ctx = useContext(MicroloansTermsModalContext);
  if (!ctx) {
    throw new Error(
      'useMicroloansTermsModal must be used within MicroloansTermsModalProvider',
    );
  }
  return ctx;
};

type MicroloansTermsModalProviderProps = {
  children: ReactNode;
};

export const MicroloansTermsModalProvider = ({
  children,
}: MicroloansTermsModalProviderProps) => {
  const [open, setOpen] = useState(false);
  const [strategy, setStrategy] =
    useState<TermsModalContractStrategy>('requestOffer');
  const [termsModalAmount, setTermsModalAmount] = useState<number | undefined>(
    undefined,
  );
  const [termsModalInstallmentsWeeks, setTermsModalInstallmentsWeeks] =
    useState<number | undefined>(undefined);
  const [termsModalBankAccountNumber, setTermsModalBankAccountNumber] =
    useState<string | undefined>(undefined);
  const [termsModalBankName, setTermsModalBankName] = useState<
    string | undefined
  >(undefined);

  const { loanOffer } = useGetLoanOffer();
  const { data: activeLoanPlan } = useActiveCollaboratorLoanPlan();

  const openTermsModal = useCallback(
    (nextStrategy: TermsModalContractStrategy = 'requestOffer') => {
      setStrategy(nextStrategy);
      trackLoanTermsOpened();
      setOpen(true);
    },
    [],
  );
  const closeTermsModal = useCallback(() => {
    setOpen(false);
  }, []);

  const setTermsModalAmountStable = useCallback(
    (amount: number | undefined) => {
      setTermsModalAmount(amount);
    },
    [],
  );

  const setTermsModalInstallmentsWeeksStable = useCallback(
    (weeks: number | undefined) => {
      setTermsModalInstallmentsWeeks(weeks);
    },
    [],
  );

  const setTermsModalBankAccountNumberStable = useCallback(
    (value: string | undefined) => {
      setTermsModalBankAccountNumber(value);
    },
    [],
  );

  const setTermsModalBankNameStable = useCallback(
    (value: string | undefined) => {
      setTermsModalBankName(value);
    },
    [],
  );

  const offerId = loanOffer?.id;
  const planId = activeLoanPlan?.id;
  const shouldUsePlanStrategy = strategy === 'status';
  const defaultPlanWeeks = useMemo(() => {
    if (loanOffer?.planOptions.length) {
      return (
        loanOffer.planOptions[loanOffer.planOptions.length - 1]?.weeks ?? 12
      );
    }
    if (activeLoanPlan?.installmentsChosen) {
      return activeLoanPlan.installmentsChosen;
    }
    return 12;
  }, [activeLoanPlan?.installmentsChosen, loanOffer]);

  const effectiveAmount = useMemo(() => {
    if (termsModalAmount != null) {
      return termsModalAmount;
    }
    if (loanOffer) {
      return clampAmount(
        loanOffer.preApprovedMaxAmountMxn,
        loanOffer.minAmountMxn,
        loanOffer.preApprovedMaxAmountMxn,
      );
    }
    if (
      activeLoanPlan?.totalAmount != null &&
      Number.isFinite(activeLoanPlan.totalAmount)
    ) {
      return collaboratorMinorUnitsToCurrency(activeLoanPlan.totalAmount);
    }
    return undefined;
  }, [activeLoanPlan?.totalAmount, loanOffer, termsModalAmount]);

  const effectiveInstallmentsWeeks =
    termsModalInstallmentsWeeks ?? defaultPlanWeeks;
  const contractQueryEnabled = shouldUsePlanStrategy
    ? open && Boolean(planId)
    : open &&
      Boolean(offerId) &&
      effectiveAmount !== undefined &&
      effectiveInstallmentsWeeks >= 4;

  const contractQueryKey = shouldUsePlanStrategy
    ? microloansKeys.planContract(planId ?? '')
    : microloansKeys.contractPreview(
        offerId ?? '',
        currencyToCollaboratorMinorUnits(effectiveAmount ?? 0),
        effectiveInstallmentsWeeks,
        termsModalBankAccountNumber ?? '',
        termsModalBankName ?? '',
      );

  const { data: bffContractMarkdown, isLoading: isBffContractLoading } =
    useQuery(
      contractQueryKey,
      async () => {
        if (shouldUsePlanStrategy) {
          if (!planId) {
            return Promise.reject(new Error('plan contract: missing plan id'));
          }
          const markdown = await fetchCollaboratorPlanContract(planId);
          return normalizeContractMarkdownNewlines(markdown);
        }
        if (!offerId) {
          return Promise.reject(
            new Error('contract preview: missing active offer id'),
          );
        }
        if (effectiveAmount === undefined) {
          return Promise.reject(
            new Error('contract preview: missing amount for request-offer'),
          );
        }
        const markdown = await fetchCollaboratorOfferContractPreview(offerId, {
          amount: currencyToCollaboratorMinorUnits(effectiveAmount),
          installments: effectiveInstallmentsWeeks,
          ...(termsModalBankAccountNumber
            ? { bankAccountNumber: termsModalBankAccountNumber }
            : {}),
          ...(termsModalBankName ? { bankName: termsModalBankName } : {}),
        });
        return normalizeContractMarkdownNewlines(markdown);
      },
      {
        enabled: contractQueryEnabled,
        staleTime: 60_000,
        retry: 1,
      },
    );

  const contractMarkdown = bffContractMarkdown ?? '';

  const contractLoading = contractQueryEnabled && isBffContractLoading;

  const value = useMemo(
    () => ({
      openTermsModal,
      closeTermsModal,
      setTermsModalAmount: setTermsModalAmountStable,
      setTermsModalInstallmentsWeeks: setTermsModalInstallmentsWeeksStable,
      setTermsModalBankAccountNumber: setTermsModalBankAccountNumberStable,
      setTermsModalBankName: setTermsModalBankNameStable,
    }),
    [
      closeTermsModal,
      openTermsModal,
      setTermsModalAmountStable,
      setTermsModalInstallmentsWeeksStable,
      setTermsModalBankAccountNumberStable,
      setTermsModalBankNameStable,
    ],
  );

  return (
    <MicroloansTermsModalContext.Provider value={value}>
      {children}
      <TermsModal
        open={open}
        onClose={closeTermsModal}
        contractMarkdown={contractMarkdown}
        contractLoading={contractLoading}
      />
    </MicroloansTermsModalContext.Provider>
  );
};
