import usePromotionCodeForm from "@insite/client-framework/Common/Hooks/usePromotionCodeForm";
import {
    IFrame,
    TokenEx as TokenExType,
    TokenExCvvOnlyIframeConfig,
    TokenExIframeStyles,
    TokenExPCIIframeConfig,
    useTokenExFrame,
} from "@insite/client-framework/Common/Hooks/useTokenExFrame";
import StyledWrapper, { getStyledWrapper } from "@insite/client-framework/Common/StyledWrapper";
import validateCreditCard from "@insite/client-framework/Common/Utilities/validateCreditCard";
import logger from "@insite/client-framework/Logger";
import { TokenExConfig } from "@insite/client-framework/Services/SettingsService";
import siteMessage from "@insite/client-framework/SiteMessage";
import ApplicationState from "@insite/client-framework/Store/ApplicationState";
import { getSettingsCollection } from "@insite/client-framework/Store/Context/ContextSelectors";
import loadTokenExConfig from "@insite/client-framework/Store/Context/Handlers/LoadTokenExConfig";
import signOut from "@insite/client-framework/Store/Context/Handlers/SignOut";
import {
    canApplyPromotionsToCart,
    canPlaceOrder,
    getCartState,
    getCurrentCartState,
} from "@insite/client-framework/Store/Data/Carts/CartsSelector";
import resetCurrentCartId from "@insite/client-framework/Store/Data/Carts/Handlers/ResetCurrentCartId";
import { getLocation, getQueryStrings } from "@insite/client-framework/Store/Data/Pages/PageSelectors";
import {
    getCurrentPromotionsDataView,
    getPromotionsDataView,
} from "@insite/client-framework/Store/Data/Promotions/PromotionsSelectors";
import { getPageLinkByPageType } from "@insite/client-framework/Store/Links/LinksSelectors";
import applyPromotion from "@insite/client-framework/Store/Pages/CheckoutReviewAndSubmit/Handlers/ApplyPromotion";
import placeOrder from "@insite/client-framework/Store/Pages/CheckoutReviewAndSubmit/Handlers/PlaceOrder";
import removePromotion from "@insite/client-framework/Store/Pages/CheckoutReviewAndSubmit/Handlers/RemovePromotion";
import setIsPlacingOrder from "@insite/client-framework/Store/Pages/OnePageCheckout/Handlers/SetIsPlacingOrder";
import setOnePageCheckoutPaymentMethod from "@insite/client-framework/Store/Pages/OnePageCheckout/Handlers/SetOnePageCheckoutPaymentMethod";
import setPaymentDetailsState from "@insite/client-framework/Store/Pages/OnePageCheckout/Handlers/SetPaymentDetailsState";
import preloadOrderConfirmationData from "@insite/client-framework/Store/Pages/OrderConfirmation/Handlers/PreloadOrderConfirmationData";
import translate from "@insite/client-framework/Translate";
import { PromotionModel } from "@insite/client-framework/Types/ApiModels";
import WidgetModule from "@insite/client-framework/Types/WidgetModule";
import CreditCardDetailsEntry, {
    CreditCardDetailsEntryStyles,
} from "@insite/content-library/Widgets/CheckoutReviewAndSubmit/CreditCardDetailsEntry";
import ECheckDetailsEntry, {
    ECheckDetailsEntryStyles,
    Validatable,
} from "@insite/content-library/Widgets/CheckoutReviewAndSubmit/ECheckDetailsEntry";
import PaymentProfileBillingAddress from "@insite/content-library/Widgets/CheckoutReviewAndSubmit/PaymentProfileBillingAddress";
import SavedPaymentProfileEntry, {
    SavedPaymentProfileEntryStyles,
} from "@insite/content-library/Widgets/CheckoutReviewAndSubmit/SavedPaymentProfileEntry";
import {
    convertApiDataToTokenExCardType,
    convertTokenExCardType,
} from "@insite/content-library/Widgets/SavedPayments/PaymentUtilities";
import Button, { ButtonPresentationProps } from "@insite/mobius/Button";
import Checkbox from "@insite/mobius/Checkbox";
import { BaseTheme } from "@insite/mobius/globals/baseTheme";
import GridContainer, { GridContainerProps } from "@insite/mobius/GridContainer";
import GridItem, { GridItemProps } from "@insite/mobius/GridItem";
import Link, { LinkPresentationProps } from "@insite/mobius/Link";
import LoadingOverlay from "@insite/mobius/LoadingOverlay";
import LoadingSpinner from "@insite/mobius/LoadingSpinner";
import Select, { SelectPresentationProps } from "@insite/mobius/Select";
import Tag, { TagPresentationProps, TagProps } from "@insite/mobius/Tag";
import TextField, { TextFieldPresentationProps } from "@insite/mobius/TextField";
import ToasterContext, { HasToasterContext, withToaster } from "@insite/mobius/Toast/ToasterContext";
import { generateTokenExFrameStyleConfig } from "@insite/mobius/TokenExFrame";
import Typography, { TypographyPresentationProps } from "@insite/mobius/Typography";
import { HasHistory, withHistory } from "@insite/mobius/utilities/HistoryContext";
import InjectableCss from "@insite/mobius/utilities/InjectableCss";
import VisuallyHidden from "@insite/mobius/VisuallyHidden";
import React, { useEffect, useRef, useState } from "react";
import { connect, ResolveThunks } from "react-redux";
import { css, ThemeProps, withTheme } from "styled-components";

const mapStateToProps = (state: ApplicationState) => {
    const { cartId, isPlacingOrder, paymentDetailsState, placeOrderErrorMessage } = state.pages.onePageCheckout;
    const cartState = cartId ? getCartState(state, cartId) : getCurrentCartState(state);
    const {
        websiteSettings,
        productSettings: { enableVat },
        cartSettings: { bypassCvvForSavedCards },
    } = getSettingsCollection(state);
    const promotionsDataView = cartId ? getPromotionsDataView(state, cartId) : getCurrentPromotionsDataView(state);
    const promotions = promotionsDataView.value;
    return {
        cartState,
        websiteSettings,
        tokenExConfigs: state.context.tokenExConfigs,
        savedPaymentsPageLink: getPageLinkByPageType(state, "SavedPaymentsPage"),
        session: state.context.session,
        signInPageLink: getPageLinkByPageType(state, "SignInPage"),
        location: getLocation(state),
        enableVat,
        bypassCvvForSavedCards,
        isPlacingOrder,
        paymentDetailsState,
        placeOrderErrorMessage,
        confirmationPageLink: getPageLinkByPageType(state, "OnePageCheckoutConfirmationPage"),
        canApplyPromosToCart: canApplyPromotionsToCart(cartState?.value),
        promotionErrorMessage: state.pages.checkoutReviewAndSubmit.promotionErrorMessage,
        promotions,
        canPlaceOrder: canPlaceOrder(cartState.value),
    };
};

const mapDispatchToProps = {
    loadTokenExConfig,
    preloadOrderConfirmationData,
    resetCurrentCartId,
    setOnePageCheckoutPaymentMethod,
    signOut,
    setPaymentDetailsState,
    placeOrder,
    setIsPlacingOrder,
    applyPromotion,
    removePromotion,
};

type Props = ReturnType<typeof mapStateToProps> &
    ResolveThunks<typeof mapDispatchToProps> &
    ThemeProps<BaseTheme> &
    HasHistory &
    HasToasterContext;

export interface OnePageCheckoutPaymentDetailsStyles {
    form?: InjectableCss;
    fieldset?: InjectableCss;
    paymentDetailsGridContainer?: GridContainerProps;
    paymentDetailsHeadingGridItem?: GridItemProps;
    paymentDetailsHeadingText?: TypographyPresentationProps;
    paymentdMethodInfoGridItem?: GridItemProps;
    paymentdMethodNameText?: TypographyPresentationProps;
    paymentdMethodCreditCardInfoText?: TypographyPresentationProps;
    editGridItem?: GridItemProps;
    editLink?: LinkPresentationProps;
    paymentMethodAndPONumberContainer?: GridContainerProps;
    paymentMethodGridItem?: GridItemProps;
    paymentMethodSelect?: SelectPresentationProps;
    paymentProfileBillingAddress?: any;
    paymentProfileExpiredErrorWrapper?: InjectableCss;
    paymentProfileExpiredErrorText?: TypographyPresentationProps;
    paymentProfileEditCardLink?: LinkPresentationProps;
    vatNumberGridItem?: GridItemProps;
    vatNumberText?: TextFieldPresentationProps;
    poNumberGridItem?: GridItemProps;
    poNumberText?: TextFieldPresentationProps;
    emptyGridItem?: GridItemProps;
    adyenDropInGridItem?: GridItemProps;
    mainContainer?: GridContainerProps;
    savedPaymentProfile?: SavedPaymentProfileEntryStyles;
    creditCardDetailsGridItem?: GridItemProps;
    eCheckDetailsGridItem?: GridItemProps;
    creditCardDetails?: CreditCardDetailsEntryStyles;
    creditCardAddressGridItem?: GridItemProps;
    eCheckDetailsEntryStyles?: ECheckDetailsEntryStyles;
    saveAndContinueButtonGridItem: GridItemProps;
    saveAndContinueButton?: ButtonPresentationProps;
    saveCardCheckboxGridItem: GridItemProps;
    promotionsCodeGridItem?: GridItemProps;
    promotionTagsGridContainerItem?: GridItemProps;
    promotionTagsGridContainer?: GridContainerProps;
    promotionCodeGridContainer?: GridContainerProps;
    promotionTagsGridItem?: GridItemProps;
    promotionCodeTag?: TagPresentationProps;
    promotionCodeTagName?: TypographyPresentationProps;
    addDiscountGridItem?: GridItemProps;
    addDiscountButton?: ButtonPresentationProps;
    enterPromotionCodeGridItem?: GridItemProps;
    enterPromotionCodeTextField?: TextFieldPresentationProps;
    enterPromotionCodeApplyButton?: ButtonPresentationProps;
    appliedPromoCodeOverviewText?: TypographyPresentationProps;
    discountLabel?: TypographyPresentationProps;
}

export const onePageCheckoutPaymentDetailsStyles: OnePageCheckoutPaymentDetailsStyles = {
    fieldset: {
        css: css`
            margin: 0;
            padding: 0;
            border: 0;
        `,
    },
    paymentDetailsGridContainer: { gap: 20 },
    paymentDetailsHeadingGridItem: {
        width: 10,
        css: css`
            align-items: center;
        `,
    },
    paymentDetailsHeadingText: {
        variant: "h5",
        css: css`
            margin-bottom: 0;
        `,
    },
    paymentdMethodInfoGridItem: {
        width: 12,
        css: css`
            flex-direction: column;
        `,
    },
    editGridItem: {
        width: 2,
        css: css`
            justify-content: end;
            align-items: center;
        `,
    },
    paymentMethodAndPONumberContainer: {
        gap: 10,
        css: css`
            margin-bottom: 8px;
        `,
    },
    paymentMethodGridItem: {
        width: 6,
        css: css`
            flex-direction: column;
        `,
    },
    paymentProfileExpiredErrorWrapper: {
        css: css`
            display: flex;
            width: 100%;
        `,
    },
    paymentProfileExpiredErrorText: { color: "danger" },
    paymentProfileEditCardLink: {
        css: css`
            margin-left: 1rem;
        `,
    },
    vatNumberGridItem: { width: 6 },
    poNumberGridItem: { width: 6 },
    emptyGridItem: { width: 6 },
    creditCardDetailsGridItem: { width: 12 },
    eCheckDetailsGridItem: { width: [12, 12, 12, 6, 6] },
    creditCardAddressGridItem: {
        width: [12, 12, 12, 6, 6],
        css: css`
            flex-direction: column;
        `,
    },
    adyenDropInGridItem: { width: 12 },
    saveAndContinueButtonGridItem: {
        width: 12,
    },
    saveAndContinueButton: {
        css: css`
            margin-top: 1em;
        `,
    },
    saveCardCheckboxGridItem: {
        width: 12,
    },
    promotionsCodeGridItem: {
        width: 12,
    },
    promotionCodeGridContainer: {
        gap: 10,
    },
    promotionTagsGridContainer: {
        gap: 2,
    },
    promotionTagsGridContainerItem: {
        width: 12,
    },
    promotionTagsGridItem: {
        width: 12,
    },
    addDiscountGridItem: {
        width: 12,
    },
    addDiscountButton: {
        variant: "secondary",
    },
    enterPromotionCodeGridItem: {
        width: 6,
    },
    enterPromotionCodeApplyButton: {
        variant: "secondary",
    },
    promotionCodeTag: {
        css: css`
            & > button {
                min-height: 0;
            }
        `,
    },
    promotionCodeTagName: {
        css: css`
            align-self: flex-end;
            padding-bottom: 15px;
        `,
    },
    appliedPromoCodeOverviewText: {
        css: css`
            padding-bottom: 15px;
        `,
    },
};

declare const TokenEx: TokenExType;
let tokenExIframe: IFrame | undefined;
let tokenExAccountNumberIframe: IFrame | undefined;
let tokenExFrameStyleConfig: TokenExIframeStyles;

const isNonEmptyString = (value: string | undefined) => value !== undefined && value.trim() !== "";

const isMonthAndYearBeforeToday = (month: number, year: number) => {
    const today = new Date();
    const currentMonth = today.getMonth();
    const currentYear = today.getFullYear();
    return year < currentYear || (year === currentYear && month < currentMonth + 1);
};

const styles = onePageCheckoutPaymentDetailsStyles;
const StyledForm = getStyledWrapper("form");
const StyledFieldSet = getStyledWrapper("fieldset");

const OnePageCheckoutPaymentDetails = ({
    cartState,
    websiteSettings,
    tokenExConfigs,
    savedPaymentsPageLink,
    history,
    loadTokenExConfig,
    theme,
    enableVat,
    bypassCvvForSavedCards,
    setOnePageCheckoutPaymentMethod,
    isPlacingOrder,
    paymentDetailsState,
    placeOrderErrorMessage,
    signInPageLink,
    setPaymentDetailsState,
    placeOrder,
    setIsPlacingOrder,
    confirmationPageLink,
    canApplyPromosToCart,
    applyPromotion,
    promotionErrorMessage,
    promotions,
    removePromotion,
    canPlaceOrder,
}: Props) => {
    const [paymentMethod, setPaymentMethod] = useState("");
    const [poNumber, setPONumber] = useState("");
    const [vatNumber, setVatNumber] = useState("");
    const [saveCard, setSaveCard] = useState(false);
    const [cardHolderName, setCardHolderName] = useState("");
    const [cardNumber, setCardNumber] = useState("");
    const [cardType, setCardType] = useState("");
    const [possibleCardType, setPossibleCardType] = useState("");
    const [expirationMonth, setExpirationMonth] = useState(1);
    const [expirationYear, setExpirationYear] = useState(new Date().getFullYear());
    const [securityCode, setSecurityCode] = useState("");

    const [paymentMethodError, setPaymentMethodError] = useState("");
    const [poNumberError, setPONumberError] = useState("");
    const [cardHolderNameError, setCardHolderNameError] = useState("");
    const [cardNumberError, setCardNumberError] = useState("");
    const [cardTypeError, setCardTypeError] = useState("");
    const [securityCodeError, setSecurityCodeError] = useState("");
    const [expirationError, setExpirationError] = useState("");
    const [paystandError, setPaystandError] = useState("");

    const [showFormErrors, setShowFormErrors] = useState(false);
    const [isCardNumberTokenized, setIsCardNumberTokenized] = useState(false);
    const [isTokenExIframeLoaded, setIsTokenExIframeLoaded] = useState(false);
    const [isECheckTokenized, setIsECheckTokenized] = useState(false);
    const [accountHolderName, setAccountHolderName] = useState("");
    const [accountNumber, setAccountNumber] = useState("");
    const [routingNumber, setRoutingNumber] = useState("");
    const [creditCardLastFourDigits, setCreditCardLastFourDigits] = useState("");
    const [bankAccountLastFourDigits, setBankAccountLastFourDigits] = useState("");
    const [isIframeLoaded, setIsIframeLoaded] = useState(false);
    const {
        promotionCode,
        setPromotionCode,
        promotionsWithCode,
        setRemovingPromotionCode,
        isAddingDiscount,
        setIsAddingDiscount,
        isUpdatingPromotions,
        setIsUpdatingPromotions,
    } = usePromotionCodeForm(promotions);

    const applyPromotionHandler = () => {
        setIsUpdatingPromotions(true);
        applyPromotion({
            promotionCode,
            onComplete: (
                result: Partial<{
                    successMessage: string;
                    errorMessage: string;
                    isPromotionAppliedSuccess: boolean;
                }>,
            ) => {
                if (result.isPromotionAppliedSuccess) {
                    setPromotionCode("");
                    setIsAddingDiscount(false);
                }
                setIsUpdatingPromotions(false);
            },
        });
    };

    const removePromotionHandler = (promotion: PromotionModel) => {
        setIsUpdatingPromotions(true);
        removePromotion({
            promotionId: promotion.id,
            onComplete: () => {
                setIsUpdatingPromotions(false);
            },
        });
        setRemovingPromotionCode(promotion.promotionCode);
    };

    // Used in deciding which form element to focus on in the case the form is submitted with errors
    const paymentMethodRef = useRef<HTMLSelectElement>(null);
    const poNumberRef = useRef<HTMLInputElement>(null);
    const cardHolderNameRef = useRef<HTMLInputElement>(null);
    const cardNumberRef = useRef<HTMLInputElement>(null);
    const cardTypeRef = useRef<HTMLSelectElement>(null);
    const securityCodeRef = useRef<HTMLInputElement>(null);
    const expirationMonthRef = useRef<HTMLSelectElement>(null);
    const expirationYearRef = useRef<HTMLSelectElement>(null);

    const resetForm = () => {
        setCardHolderNameError("");
        setCardNumberError("");
        setCardTypeError("");
        setExpirationError("");
        setSecurityCodeError("");
        setPaystandError("");
        setIsCardNumberTokenized(false);
        setIsTokenExIframeLoaded(false);
    };

    useEffect(() => {
        if (!isPlacingOrder || !canPlaceOrder) {
            return;
        }

        placeOrder({
            paymentMethod,
            poNumber,
            vatNumber,
            saveCard,
            cardHolderName,
            cardNumber,
            cardType,
            expirationMonth,
            expirationYear,
            securityCode,
            useBillingAddress: true,
            address1: "",
            countryId: "",
            stateId: "",
            city: "",
            postalCode: "",
            accountHolderName,
            accountNumber,
            routingNumber,
            payerId: "",
            onSuccess: (cartId: string) => {
                setIsPlacingOrder({ isPlacingOrder: false });

                if (!confirmationPageLink) {
                    return;
                }

                history.push(`${confirmationPageLink.url}?cartId=${cartId}`);
            },
            onError: () => {
                setIsCardNumberTokenized(false);
                tokenExIframe?.reset();
                setIsPlacingOrder({ isPlacingOrder: false });
            },
            onComplete(resultProps) {
                if (resultProps.apiResult?.cart) {
                    // "this" is targeting the object being created, not the parent SFC
                    // eslint-disable-next-line react/no-this-in-sfc
                    this.onSuccess?.(resultProps.apiResult.cart.id);
                } else {
                    // "this" is targeting the object being created, not the parent SFC
                    // eslint-disable-next-line react/no-this-in-sfc
                    this.onError?.();
                }
            },
        });
    }, [isPlacingOrder]);

    const [isTokenExContainerRendered, setIsTokenExContainerRendered] = useState(false);
    useEffect(() => {
        if (paymentDetailsState === "active") {
            setIsTokenExContainerRendered(true);
        }
    }, [paymentDetailsState]);

    useTokenExFrame(websiteSettings);

    useEffect(() => resetForm(), [paymentMethod]);

    useEffect(() => {
        if (isCardNumberTokenized || isECheckTokenized) {
            setPaymentDetailsState({ paymentDetailsState: "complete" });
        }
    }, [isCardNumberTokenized, isECheckTokenized]);

    useEffect(() => {
        if (!cartState.isLoading && cartState.value && cartState.value.paymentMethod && paymentMethod === "") {
            setPaymentMethod(cartState.value.paymentMethod.name);
        }
    }, [cartState]);

    const { value: cart } = cartState;

    const { useTokenExGateway, useECheckTokenExGateway } = websiteSettings;

    const paymentOptions = cart ? cart.paymentOptions : undefined;
    const paymentMethods = paymentOptions ? paymentOptions.paymentMethods : undefined;

    const paymentMethodDto = paymentMethods?.find(method => method.name === paymentMethod);
    const isCreditCard = paymentMethodDto?.isCreditCard === true;
    const isECheck = paymentMethodDto?.isECheck === true;
    const isPaymentProfile = paymentMethodDto?.isPaymentProfile === true;
    const isPaymentProfileExpired = paymentMethodDto?.isPaymentProfileExpired === true;
    let tokenName: string | undefined;
    const eCheckDetails = useRef<Validatable>(null);
    const tokenExReinitTimeout = 500;
    let lastTokenExReinitTime = 0;
    const tokenExErrorsLimit = (20 * 1e3) / tokenExReinitTimeout;
    let currentTokenExErrorCount = 0;

    if (useTokenExGateway) {
        if (isPaymentProfile) {
            tokenName = paymentMethodDto!.name;
        } else if (isCreditCard) {
            tokenName = "";
        }
    }

    if (useECheckTokenExGateway) {
        if (isECheck) {
            tokenName = "";
        }
    }

    useEffect(() => {
        if (typeof tokenName !== "undefined") {
            loadTokenExConfig({ token: tokenName, isECheck });
        }
    }, [paymentMethod]);

    const tokenExConfig = typeof tokenName !== "undefined" ? tokenExConfigs[tokenName] : undefined;

    useEffect(() => {
        if (typeof TokenEx === "undefined" || !tokenExConfig || !isTokenExContainerRendered) {
            return;
        }

        setIsTokenExIframeLoaded(false);
        setUpTokenEx();
    }, [tokenExConfig, typeof TokenEx, isTokenExContainerRendered]);

    const setUpTokenEx = () => {
        if (
            !tokenExConfig ||
            !paymentMethodDto ||
            !cart?.showCreditCard ||
            cart.requiresApproval ||
            cart.lineCount === 0
        ) {
            return;
        }

        currentTokenExErrorCount = 0;
        if (isPaymentProfile && !isPaymentProfileExpired && !bypassCvvForSavedCards) {
            setUpTokenExIFrameCvvOnly(tokenExConfig);
        } else if (isCreditCard) {
            setUpTokenExIFrame(tokenExConfig);
        }
    };

    const setUpTokenExIFrame = (config: TokenExConfig) => {
        if (tokenExIframe) {
            tokenExIframe.remove();
        }

        const iframeConfig: TokenExPCIIframeConfig = {
            authenticationKey: config.authenticationKey,
            cvv: true,
            cvvContainerID: "tokenExSecurityCode",
            cvvInputType: "text",
            enablePrettyFormat: true,
            inputType: "text",
            origin: config.origin,
            pci: true,
            styles: tokenExFrameStyleConfig,
            timestamp: config.timestamp,
            tokenExID: config.tokenExId,
            tokenScheme: config.tokenScheme,
            enableAriaRequired: true,
            title: translate("Credit card information"),
            customDataLabel: translate("credit card number"),
        };

        tokenExIframe = new TokenEx.Iframe("tokenExCardNumber", iframeConfig);
        tokenExIframe.load();
        tokenExIframe.on("load", _ => setIsTokenExIframeLoaded(true));
        tokenExIframe.on("tokenize", data => {
            setCardNumber(data.token);
            setSecurityCode("CVV");
            setCardType(convertTokenExCardType(data.cardType));
            setIsCardNumberTokenized(true);
        });
        tokenExIframe.on("cardTypeChange", data => setPossibleCardType(data.possibleCardType));
        tokenExIframe.on("validate", data => {
            setShowFormErrors(true);
            if (data.isValid) {
                setCardNumberError("");
            } else {
                if (data.validator === "required") {
                    setCardNumberError(translate("Credit card number is required."));
                }
                if (data.validator === "format") {
                    setCardNumberError(translate("Credit card number is invalid."));
                }
            }

            if (data.isCvvValid) {
                setSecurityCodeError("");
            } else {
                if (data.cvvValidator === "required") {
                    setSecurityCodeError(translate("Security code is required."));
                }
                if (data.cvvValidator === "format") {
                    setSecurityCodeError(translate("Security code is invalid."));
                }
            }
        });
        tokenExIframe.on("error", data => {
            logger.error(data);
            if (
                Date.now() - lastTokenExReinitTime > tokenExReinitTimeout &&
                ++currentTokenExErrorCount <= tokenExErrorsLimit
            ) {
                lastTokenExReinitTime = Date.now();
                setTimeout(() => {
                    setUpTokenExIFrame(config);
                }, tokenExReinitTimeout);
            }
        });
    };

    const setUpTokenExIFrameCvvOnly = (config: TokenExConfig) => {
        if (tokenExIframe) {
            tokenExIframe.remove();
        }

        const iframeConfig: TokenExCvvOnlyIframeConfig = {
            authenticationKey: config.authenticationKey,
            cardType: convertApiDataToTokenExCardType(paymentMethodDto!.cardType),
            cvv: true,
            cvvOnly: true,
            inputType: "text",
            origin: config.origin,
            styles: tokenExFrameStyleConfig,
            timestamp: config.timestamp,
            token: paymentMethodDto!.name,
            tokenExID: config.tokenExId,
            tokenScheme: paymentMethodDto!.tokenScheme,
            enableAriaRequired: true,
            title: translate("Security code"),
            customDataLabel: translate("Security code"),
        };

        tokenExIframe = new TokenEx.Iframe("ppTokenExSecurityCode", iframeConfig);
        tokenExIframe.load();
        tokenExIframe.on("load", _ => setIsTokenExIframeLoaded(true));
        tokenExIframe.on("tokenize", _ => setIsCardNumberTokenized(true));
        tokenExIframe.on("validate", data => {
            if (data.isValid) {
                setSecurityCodeError("");
            } else {
                setShowFormErrors(true);
                if (data.validator === "required") {
                    setSecurityCodeError(translate("Security code is required."));
                }
                if (data.validator === "format") {
                    setSecurityCodeError(translate("Security code is invalid."));
                }
            }
        });
        tokenExIframe.on("error", data => {
            logger.error(data);
            if (
                Date.now() - lastTokenExReinitTime > tokenExReinitTimeout &&
                ++currentTokenExErrorCount <= tokenExErrorsLimit
            ) {
                lastTokenExReinitTime = Date.now();
                setTimeout(() => {
                    setUpTokenExIFrameCvvOnly(config);
                }, tokenExReinitTimeout);
            }
        });
    };

    const toasterContext = React.useContext(ToasterContext);
    useEffect(() => {
        if (placeOrderErrorMessage === siteMessage("CreditCardInfo_MaximumCreditCardAttempts")) {
            toasterContext.addToast({
                body: placeOrderErrorMessage,
                messageType: "danger",
            });

            setTimeout(() => {
                signOut();
                signInPageLink && history.push(signInPageLink.url);
            }, 3000);
        }
    }, [placeOrderErrorMessage]);

    const handlePaymentMethodChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
        setPaymentMethod(event.currentTarget.value);
        validatePaymentMethod(event.currentTarget.value);

        setOnePageCheckoutPaymentMethod({
            paymentMethod: paymentMethods?.find(method => method.name === event.target.value),
        });
    };
    const handlePONumberChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setPONumber(event.currentTarget.value);
        validatePONumber(event.currentTarget.value);
    };
    const handleVatNumberChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setVatNumber(event.currentTarget.value);
    };
    const handleSaveCardChange = (_: React.SyntheticEvent<Element, Event>, value: boolean) => setSaveCard(value);
    const handleCardHolderNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setCardHolderName(event.currentTarget.value);
        validateCardHolderName(event.currentTarget.value);
    };
    const handleCardNumberChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setCardNumber(event.currentTarget.value);
        validateCardNumber(event.currentTarget.value);
    };
    const handleCardTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
        setCardType(event.currentTarget.value);
        validateCardType(event.currentTarget.value);
    };
    const handleExpirationMonthChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
        const month = Number(event.currentTarget.value);
        if (Number.isNaN(month)) {
            return;
        }
        setExpirationMonth(month);
        validateCardExpiration(month, expirationYear);
    };
    const handleExpirationYearChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
        const year = Number(event.currentTarget.value);
        if (Number.isNaN(year)) {
            return;
        }
        setExpirationYear(year);
        validateCardExpiration(expirationMonth, year);
    };
    const handleSecurityCodeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setSecurityCode(event.currentTarget.value);
        validateSecurityCode(event.currentTarget.value);
    };

    const handleEditCardClick = () => {
        if (!savedPaymentsPageLink) {
            return;
        }

        history.push(savedPaymentsPageLink.url);
    };

    const validatePaymentMethod = (paymentMethod: string) => {
        const paymentMethodValid = !paymentMethods || paymentMethods.length === 0 || isNonEmptyString(paymentMethod);
        setPaymentMethodError(!paymentMethodValid ? translate("Payment Method is required.") : "");
        return paymentMethodValid;
    };

    const validatePONumber = (poNumber: string) => {
        const poNumberValid = !cart || !cart.showPoNumber || !cart.requiresPoNumber || isNonEmptyString(poNumber);
        setPONumberError(!poNumberValid ? translate("PO Number is required.") : "");
        return poNumberValid;
    };

    const validateCardHolderName = (cardHolderName: string) => {
        const cardHolderNameValid = !isCreditCard || isNonEmptyString(cardHolderName);
        setCardHolderNameError(!cardHolderNameValid ? translate("Cardholder name is required.") : "");
        return cardHolderNameValid;
    };

    const validateCardNumber = (cardNumber: string) => {
        let cardNumberEmpty = false;
        let cardNumberValid = true;

        if (isCreditCard && !useTokenExGateway) {
            cardNumberEmpty = !isNonEmptyString(cardNumber);
            cardNumberValid = validateCreditCard(cardNumber);
        }

        if (cardNumberEmpty) {
            setCardNumberError(translate("Credit card number is required."));
        } else if (!cardNumberValid) {
            setCardNumberError(translate("Credit card number is invalid."));
        } else {
            setCardNumberError("");
        }

        return { cardNumberEmpty, cardNumberValid };
    };

    const validateCardType = (cardType: string) => {
        const cardTypeValid = !isCreditCard || useTokenExGateway || (!useTokenExGateway && isNonEmptyString(cardType));
        setCardTypeError(!cardTypeValid ? translate("Credit card type is required.") : "");
        return cardTypeValid;
    };

    const validateCardExpiration = (expirationMonth: number, expirationYear: number) => {
        const cardExpired = isCreditCard && isMonthAndYearBeforeToday(expirationMonth, expirationYear);
        setExpirationError(cardExpired ? translate("Card is expired. Please enter a valid expiration date.") : "");
        return cardExpired;
    };

    const validateSecurityCode = (securityCode: string) => {
        let securityCodeEmpty = false;
        let securityCodeValid = true;

        if (isCreditCard && !useTokenExGateway) {
            securityCodeEmpty = !isNonEmptyString(securityCode);
            securityCodeValid = /^\d+$/.test(securityCode);
        }

        if (securityCodeEmpty) {
            setSecurityCodeError(!securityCodeValid ? translate("Security code is required.") : "");
        } else if (!securityCodeValid) {
            setSecurityCodeError(translate("Security code is invalid."));
        } else {
            setSecurityCodeError("");
        }

        return { securityCodeEmpty, securityCodeValid };
    };

    const handleAccountNumberIFrame = (accountNumberIFrame: IFrame) => {
        tokenExAccountNumberIframe = accountNumberIFrame;
    };

    const validateForm = () => {
        const paymentMethodValid = validatePaymentMethod(paymentMethod);
        const poNumberValid = validatePONumber(poNumber);

        if (useTokenExGateway && ((isPaymentProfile && !bypassCvvForSavedCards) || isCreditCard)) {
            tokenExIframe?.validate();
        } else if (useECheckTokenExGateway && isECheck) {
            tokenExAccountNumberIframe?.validate();
        }

        const cardHolderNameValid = validateCardHolderName(cardHolderName);
        const cardNumberResult = validateCardNumber(cardNumber);
        const cardTypeValid = validateCardType(cardType);
        const cardExpired = validateCardExpiration(expirationMonth, expirationYear);
        const securityCodeResult = validateSecurityCode(securityCode);
        let accountHolderNameValid = true;
        let accountNumberValid = true;
        let routingNumberValid = true;
        if (eCheckDetails?.current) {
            accountHolderNameValid = eCheckDetails.current.validateAccountHolderNameChange(accountHolderName);
            accountNumberValid = eCheckDetails.current.validateAccountNumberChange(accountNumber);
            routingNumberValid = eCheckDetails.current.validateRoutingNumberChange(routingNumber);
        }

        if (!paymentMethodValid) {
            paymentMethodRef.current?.focus();
        } else if (!poNumberValid) {
            poNumberRef.current?.focus();
        } else if (!cardHolderNameValid) {
            cardHolderNameRef.current?.focus();
        } else if (cardNumberResult.cardNumberEmpty || !cardNumberResult.cardNumberValid) {
            cardNumberRef.current?.focus();
        } else if (!cardTypeValid) {
            cardTypeRef.current?.focus();
        } else if (cardExpired) {
            const today = new Date();
            if (expirationYear < today.getFullYear()) {
                expirationYearRef.current?.focus();
            } else {
                expirationMonthRef.current?.focus();
            }
        } else if (securityCodeResult.securityCodeEmpty || !securityCodeResult.securityCodeValid) {
            securityCodeRef.current?.focus();
        }

        return (
            paymentMethodValid &&
            poNumberValid &&
            cardHolderNameValid &&
            !cardNumberResult.cardNumberEmpty &&
            cardNumberResult.cardNumberValid &&
            cardTypeValid &&
            !cardExpired &&
            !securityCodeResult.securityCodeEmpty &&
            securityCodeResult.securityCodeValid &&
            accountHolderNameValid &&
            accountNumberValid &&
            routingNumberValid
        );
    };

    const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();

        if (!validateForm()) {
            setShowFormErrors(true);
            return false;
        }

        if (useTokenExGateway && ((isPaymentProfile && !bypassCvvForSavedCards) || isCreditCard)) {
            tokenExIframe?.tokenize();
        } else if (useECheckTokenExGateway && isECheck) {
            tokenExAccountNumberIframe?.tokenize();
        } else {
            setPaymentDetailsState({ paymentDetailsState: "complete" });
        }

        return false;
    };

    if (!tokenExFrameStyleConfig) {
        tokenExFrameStyleConfig = generateTokenExFrameStyleConfig({ theme });
    }

    if (!cart || cart.requiresApproval || !paymentOptions) {
        return null;
    }

    const iframeName = useTokenExGateway ? "TokenEx" : undefined;

    return (
        <GridContainer {...styles.paymentDetailsGridContainer}>
            <GridItem {...styles.paymentDetailsHeadingGridItem}>
                <Typography {...styles.paymentDetailsHeadingText} as="h2">
                    {translate("Payment")}
                </Typography>
            </GridItem>
            {paymentDetailsState === "complete" && (
                <GridItem {...styles.editGridItem}>
                    <Link
                        {...styles.editLink}
                        onClick={() => {
                            setIsCardNumberTokenized(false);
                            setIsTokenExContainerRendered(false);
                            setPaymentDetailsState({
                                paymentDetailsState: "active",
                            });
                        }}
                    >
                        {translate("Edit")}
                    </Link>
                </GridItem>
            )}
            {paymentDetailsState === "complete" && (
                <GridItem {...styles.paymentdMethodInfoGridItem}>
                    {promotionsWithCode.length > 0 && (
                        <>
                            <Typography {...styles.discountLabel}>{translate("Discount")}:</Typography>
                            {promotionsWithCode.map(promotion => (
                                <Typography
                                    as="p"
                                    key={`promotion-with-code-${promotion.id}`}
                                    {...styles.appliedPromoCodeOverviewText}
                                >{`${promotion.promotionCode} ${promotion.name}`}</Typography>
                            ))}
                        </>
                    )}
                    <Typography as="p" {...styles.paymentdMethodNameText} data-test-selector="paymentMethodName">
                        {paymentMethodDto?.description || paymentMethodDto?.name}
                    </Typography>
                    {paymentMethodDto?.isCreditCard && (
                        <Typography
                            as="p"
                            {...styles.paymentdMethodCreditCardInfoText}
                            data-test-selector="paymentMethodCreditCardInfoText"
                        >
                            {cardType}&nbsp;
                            {`****${cardNumber.slice(-4)}`}&nbsp;
                            {`0${expirationMonth}`.slice(-2)}/{`${expirationYear}`.slice(-2)}
                        </Typography>
                    )}
                </GridItem>
            )}
            {paymentDetailsState === "active" && (
                <GridItem {...styles.paymentdMethodInfoGridItem}>
                    <StyledForm
                        {...styles.form}
                        id="reviewAndSubmitPaymentForm"
                        onSubmit={handleFormSubmit}
                        noValidate={true}
                        data-test-selector="reviewAndSubmitPaymentForm"
                    >
                        <StyledFieldSet {...styles.fieldset}>
                            <GridContainer {...styles.paymentMethodAndPONumberContainer}>
                                {canApplyPromosToCart && (
                                    <LoadingOverlay loading={isUpdatingPromotions}>
                                        <GridItem {...styles.promotionsCodeGridItem}>
                                            <GridContainer {...styles.promotionCodeGridContainer}>
                                                {promotionsWithCode.length > 0 && (
                                                    <GridItem {...styles.promotionTagsGridContainerItem}>
                                                        <GridContainer {...styles.promotionTagsGridContainer}>
                                                            {promotionsWithCode.map(promotion => (
                                                                <GridItem
                                                                    key={promotion.id}
                                                                    {...styles.promotionTagsGridItem}
                                                                >
                                                                    <Tag
                                                                        onDelete={e => {
                                                                            e.preventDefault();
                                                                            removePromotionHandler(promotion);
                                                                        }}
                                                                        data-test-selector={`promotionCode_promotionTag_${promotion.promotionCode}`}
                                                                        {...styles.promotionCodeTag}
                                                                    >
                                                                        {promotion.promotionCode}
                                                                    </Tag>
                                                                    <Typography {...styles.promotionCodeTagName}>
                                                                        {promotion.name}
                                                                    </Typography>
                                                                </GridItem>
                                                            ))}
                                                        </GridContainer>
                                                    </GridItem>
                                                )}
                                                {!isAddingDiscount && (
                                                    <GridItem {...styles.addDiscountGridItem}>
                                                        <Button
                                                            onClick={() => {
                                                                setIsAddingDiscount(true);
                                                            }}
                                                            {...styles.addDiscountButton}
                                                        >
                                                            {translate("Add Discount")}
                                                        </Button>
                                                    </GridItem>
                                                )}
                                                {isAddingDiscount && (
                                                    <>
                                                        <GridItem {...styles.enterPromotionCodeGridItem}>
                                                            <TextField
                                                                onChange={event => {
                                                                    setPromotionCode(event.target.value);
                                                                }}
                                                                onKeyDown={event => {
                                                                    if (event.keyCode === 13) {
                                                                        applyPromotionHandler();
                                                                        event.preventDefault();
                                                                    }
                                                                }}
                                                                value={promotionCode}
                                                                placeholder={translate("Enter code")}
                                                                error={promotionErrorMessage}
                                                                {...styles.enterPromotionCodeTextField}
                                                            />
                                                        </GridItem>
                                                        <GridItem {...styles.enterPromotionCodeGridItem}>
                                                            <Button
                                                                onClick={applyPromotionHandler}
                                                                disabled={!(promotionCode?.length > 0)}
                                                                {...styles.enterPromotionCodeApplyButton}
                                                            >
                                                                {translate("Apply")}
                                                            </Button>
                                                        </GridItem>
                                                    </>
                                                )}
                                            </GridContainer>
                                        </GridItem>
                                    </LoadingOverlay>
                                )}
                                <GridItem {...styles.paymentMethodGridItem}>
                                    {paymentMethods && paymentMethods.length > 0 && (
                                        <>
                                            <Select
                                                {...styles.paymentMethodSelect}
                                                label={translate("Payment Method")}
                                                value={paymentMethod ?? paymentMethodDto?.name}
                                                onChange={handlePaymentMethodChange}
                                                required
                                                error={showFormErrors && paymentMethodError}
                                                data-test-selector="onePageCheckout_paymentMethod"
                                                ref={paymentMethodRef}
                                            >
                                                <option value="">{translate("Select Payment Method")}</option>
                                                {paymentMethods.map(method => (
                                                    <option key={method.name} value={method.name}>
                                                        {method.description || method.name}
                                                    </option>
                                                ))}
                                            </Select>
                                            {paymentMethodDto?.isPaymentProfile &&
                                                !paymentMethodDto.isPaymentProfileExpired && (
                                                    <PaymentProfileBillingAddress
                                                        address={paymentMethodDto.billingAddress}
                                                        extendedStyles={styles.paymentProfileBillingAddress}
                                                    />
                                                )}
                                            {paymentMethodDto?.isPaymentProfile &&
                                                paymentMethodDto.isPaymentProfileExpired && (
                                                    <StyledWrapper {...styles.paymentProfileExpiredErrorWrapper}>
                                                        <Typography {...styles.paymentProfileExpiredErrorText}>
                                                            {siteMessage("Checkout_PaymentProfileExpired")}
                                                        </Typography>
                                                        {savedPaymentsPageLink && (
                                                            <Link
                                                                {...styles.paymentProfileEditCardLink}
                                                                onClick={handleEditCardClick}
                                                            >
                                                                {translate("Edit Card")}
                                                            </Link>
                                                        )}
                                                    </StyledWrapper>
                                                )}
                                        </>
                                    )}
                                </GridItem>
                                {cart.showPoNumber && (
                                    <GridItem {...styles.poNumberGridItem}>
                                        <TextField
                                            {...styles.poNumberText}
                                            label={
                                                <>
                                                    <span aria-hidden>{translate("PO Number")}</span>
                                                    <VisuallyHidden>
                                                        {translate("Purchase Order Number")}
                                                    </VisuallyHidden>
                                                </>
                                            }
                                            value={poNumber}
                                            onChange={handlePONumberChange}
                                            required={cart.requiresPoNumber}
                                            maxLength={50}
                                            error={showFormErrors && poNumberError}
                                            data-test-selector="onePageCheckout_poNumber"
                                            ref={poNumberRef}
                                        />
                                    </GridItem>
                                )}
                                {enableVat && (
                                    <GridItem {...styles.vatNumberGridItem}>
                                        <TextField
                                            {...styles.vatNumberText}
                                            label={translate("VAT Number")}
                                            value={vatNumber}
                                            onChange={handleVatNumberChange}
                                            maxLength={50}
                                            data-test-selector="onePageCheckout_vatNumber"
                                        />
                                    </GridItem>
                                )}
                                {paymentMethodDto?.isPaymentProfile &&
                                    !paymentMethodDto.isPaymentProfileExpired &&
                                    !bypassCvvForSavedCards && (
                                        <GridItem width={6}>
                                            <SavedPaymentProfileEntry
                                                iframe={iframeName}
                                                isTokenExIframeLoaded={isTokenExIframeLoaded}
                                                securityCode={securityCode}
                                                onSecurityCodeChange={handleSecurityCodeChange}
                                                securityCodeError={showFormErrors ? securityCodeError : undefined}
                                                extendedStyles={styles.savedPaymentProfile}
                                            />
                                        </GridItem>
                                    )}
                                {cart.showCreditCard && paymentMethodDto?.isCreditCard && (
                                    <GridItem {...styles.creditCardDetailsGridItem}>
                                        <CreditCardDetailsEntry
                                            canSaveCard={paymentOptions.canStorePaymentProfile}
                                            iframe={iframeName}
                                            isTokenExIframeLoaded={isTokenExIframeLoaded}
                                            saveCard={saveCard}
                                            onSaveCardChange={handleSaveCardChange}
                                            cardHolderName={cardHolderName}
                                            cardHolderNameRef={cardHolderNameRef}
                                            onCardHolderNameChange={handleCardHolderNameChange}
                                            cardHolderNameError={showFormErrors ? cardHolderNameError : undefined}
                                            cardNumber={cardNumber}
                                            cardNumberRef={cardNumberRef}
                                            onCardNumberChange={handleCardNumberChange}
                                            cardNumberError={showFormErrors ? cardNumberError : undefined}
                                            cardType={cardType}
                                            cardTypeRef={cardTypeRef}
                                            possibleCardType={possibleCardType}
                                            onCardTypeChange={handleCardTypeChange}
                                            cardTypeError={showFormErrors ? cardTypeError : undefined}
                                            expirationMonth={expirationMonth}
                                            expirationMonthRef={expirationMonthRef}
                                            onExpirationMonthChange={handleExpirationMonthChange}
                                            expirationYear={expirationYear}
                                            expirationYearRef={expirationYearRef}
                                            onExpirationYearChange={handleExpirationYearChange}
                                            expirationError={showFormErrors ? expirationError : undefined}
                                            securityCode={securityCode}
                                            securityCodeRef={securityCodeRef}
                                            onSecurityCodeChange={handleSecurityCodeChange}
                                            securityCodeError={showFormErrors ? securityCodeError : undefined}
                                            availableCardTypes={paymentOptions.cardTypes ?? []}
                                            availableMonths={paymentOptions.expirationMonths ?? []}
                                            availableYears={paymentOptions.expirationYears ?? []}
                                            extendedStyles={styles.creditCardDetails}
                                            isIframeLoaded={isIframeLoaded}
                                            error={showFormErrors ? paystandError : undefined}
                                            bankAccountLastFourDigits={bankAccountLastFourDigits}
                                            creditCardLastFourDigits={creditCardLastFourDigits}
                                            variation="onePageCheckout"
                                        />
                                    </GridItem>
                                )}
                                {cart.showECheck && paymentMethodDto?.isECheck && (
                                    <GridItem {...styles.eCheckDetailsGridItem}>
                                        <ECheckDetailsEntry
                                            iframe={useECheckTokenExGateway ? "TokenEx" : undefined}
                                            paymentMethod={paymentMethod}
                                            onAccountHolderNameChange={setAccountHolderName}
                                            onAccountNumberChange={setAccountNumber}
                                            onRoutingNumberChange={setRoutingNumber}
                                            tokenExConfig={tokenExConfig}
                                            tokenExFrameStyleConfig={tokenExFrameStyleConfig}
                                            extendedStyles={styles.eCheckDetailsEntryStyles}
                                            showFormErrors={showFormErrors}
                                            updateIsECheckTokenized={setIsECheckTokenized}
                                            updateShowFormErrors={setShowFormErrors}
                                            setAccountNumberIFrame={handleAccountNumberIFrame}
                                            ref={eCheckDetails}
                                        />
                                    </GridItem>
                                )}
                                {paymentOptions.canStorePaymentProfile && paymentMethodDto?.isCreditCard && (
                                    <GridItem {...styles.saveCardCheckboxGridItem}>
                                        <Checkbox checked={saveCard} onChange={handleSaveCardChange}>
                                            {translate("Save card information")}
                                        </Checkbox>
                                    </GridItem>
                                )}
                                <GridItem {...styles.saveAndContinueButtonGridItem}>
                                    <Button
                                        {...styles.saveAndContinueButton}
                                        data-test-selector="saveAndContinueButton"
                                    >
                                        {translate("Save & Continue")}
                                    </Button>
                                </GridItem>
                            </GridContainer>
                        </StyledFieldSet>
                    </StyledForm>
                </GridItem>
            )}
        </GridContainer>
    );
};

const widgetModule: WidgetModule = {
    component: connect(
        mapStateToProps,
        mapDispatchToProps,
    )(withToaster(withHistory(withTheme(OnePageCheckoutPaymentDetails)))),
    definition: {
        displayName: "Payment Details",
        group: "One Page Checkout",
        allowedContexts: ["OnePageCheckoutPage"],
    },
};

export default widgetModule;
