package com.braintree.facade.impl;

import com.braintree.command.result.BrainTreePaymentDetailsResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.configuration.service.BrainTreeSupportedLocaleConfig;
import com.braintree.converters.BrainTreePayPalAddressDataConverter;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.customfield.service.BraintreeCustomFieldsService;
import com.braintree.delivery.service.BraintreeDeliveryService;
import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.enums.BraintreePageType;
import com.braintree.enums.BraintreePaymentMethodLimitData;
import com.braintree.facade.BrainTreeConnectWithPayPalFacade;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.facade.BraintreeConfigurationFacade;
import com.braintree.facade.BraintreeUrlResolutionFacade;
import com.braintree.hybris.data.BraintreeAssetsData;
import com.braintree.hybris.data.BraintreeCheckoutData;
import com.braintree.hybris.data.BraintreeConfigurationData;
import com.braintree.hybris.data.BraintreeLocalPaymentMethodData;
import com.braintree.hybris.data.BraintreePaymentInitiator;
import com.braintree.hybris.data.BraintreePluginConfigurationData;
import com.braintree.hybris.data.CreditCardPaymentMethodData;
import com.braintree.hybris.data.DropInData;
import com.braintree.hybris.data.LocalPaymentMethodData;
import com.braintree.hybris.data.PayPalAddressData;
import com.braintree.hybris.data.PayPalPaymentMethodData;
import com.braintree.hybris.data.PaymentMethodsLimitationData;
import com.braintree.hybris.data.SrcPaymentMethodData;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.model.BraintreeLocalPaymentMethodsModel;
import com.braintree.order.service.BraintreeOrderRetrievalService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintree.util.BrainTreeUtils;
import com.braintree.util.GenericBuilder;
import de.hybris.platform.acceleratorfacades.order.impl.DefaultAcceleratorCheckoutFacade;
import de.hybris.platform.catalog.CatalogVersionService;
import de.hybris.platform.catalog.model.CatalogVersionModel;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.service.data.CommerceOrderParameter;
import de.hybris.platform.core.model.media.MediaModel;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.PaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.core.model.user.UserModel;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.ordermanagementfacades.payment.data.PaymentTransactionEntryData;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.enums.PaymentTransactionType;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException;
import de.hybris.platform.servicelayer.i18n.I18NService;
import de.hybris.platform.servicelayer.media.MediaService;
import de.hybris.platform.servicelayer.session.SessionService;
import de.hybris.platform.servicelayer.user.UserService;
import de.hybris.platform.util.PriceValue;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.assertj.core.util.Lists;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS;
import static com.braintree.enums.BraintreeThreeDSecureFlow.DISABLED;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;

/**
 * This class extends DefaultAcceleratorCheckoutFacade
 */
public class DefaultBrainTreeCheckoutFacade extends DefaultAcceleratorCheckoutFacade {

    private static final Logger LOG = Logger.getLogger(DefaultBrainTreeCheckoutFacade.class);

    private static final String USER = "user";
    private static final String CONNECT_RETURN_URL = "/connect";
    private static final String LOCAL_PAYMENT_METHODS_FALLBACK_URL = "/localPaymentMethods/fallback?currency=";

    private Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter;
    private Converter<BraintreeLocalPaymentMethodsModel, BraintreeLocalPaymentMethodData> brainTreeLocalPaymentsConverter;
    private BrainTreePaymentService brainTreePaymentService;
    private BrainTreeTransactionService brainTreeTransactionService;
    private I18NService i18NService;
    private UserService userService;
    private MediaService mediaService;
    private CatalogVersionService catalogVersionService;
    private BraintreeDeliveryService braintreeDeliveryService;
    private BraintreeOrderRetrievalService orderRetrievalService;
    private BrainTreePayPalAddressDataConverter payPalAddressDataConverter;
    private BrainTreeConfigService brainTreeConfigService;
    private BraintreeConfigurationFacade braintreeConfigurationFacade;
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    private BrainTreeUserFacade brainTreeUserFacade;
    private BraintreeCustomFieldsService customFieldsService;
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;
    private BraintreePaymentInfoService braintreePaymentInfoService;
    private SessionService sessionService;
    private BrainTreeConnectWithPayPalFacade connectWithPayPalFacade;
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BraintreeUrlResolutionFacade braintreeUrlResolutionFacade;

    public void storeIntentToCart() {
        CartModel cart = getCartService().getSessionCart();
        storeIntentToCart(cart);
    }

    public void storeIntentToCart(CartModel cart) {
        BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
        paymentInfo.setPayPalIntent(brainTreeConfigService.getIntent());

        getModelService().save(paymentInfo);
    }

    public void storeShipsFromPostalCodeToCart(final String shipsFromPostalCode) {
        CartModel cart = getCartService().getSessionCart();
        storeShipsFromPostalCodeToCart(shipsFromPostalCode, cart);
    }

    public void storeShipsFromPostalCodeToCart(final String shipsFromPostalCode, final CartModel cart) {
        BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
        paymentInfo.setShipsFromPostalCode(shipsFromPostalCode);

        getModelService().save(paymentInfo);
    }

    public void storeCustomFieldsToCart(final Map<String, String> customFields) {
        CartModel cart = getCartService().getSessionCart();
        storeCustomFieldsToCart(customFields, cart);
    }

    public void storeCustomFieldsToCart(final Map<String, String> customFields, final CartModel cart) {
        BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
        paymentInfo.setCustomFields(customFields);

        getModelService().save(paymentInfo);
    }

    @Override
    public boolean authorizePayment(final String securityCode) {
        try {
            if (getCart().getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
                BrainTreePaymentInfoModel brainTreePaymentInfoModel = (BrainTreePaymentInfoModel) getCart().getPaymentInfo();
                if (brainTreePaymentInfoModel.isSaved() && !DISABLED.equals(brainTreeConfigService.getThreeDSecureFlow())) {
                    brainTreePaymentFacade.verifyThreeDSForSelectedPayment(brainTreePaymentInfoModel,
                            brainTreePaymentInfoModel.getNonce());
                }

                final CommerceOrderParameter parameter = new CommerceOrderParameter();
                parameter.setOrder(getCart());
                parameter.setCustomFields(getCustomFields());

                final PaymentTransactionEntryModel authorizationTransaction = brainTreeTransactionService.createAuthorizationTransaction(parameter);

                return authorizationTransaction != null
                        && (TransactionStatus.ACCEPTED.name().equals(authorizationTransaction.getTransactionStatus())
                        || TransactionStatus.REVIEW.name().equals(authorizationTransaction.getTransactionStatus()));
            } else {
                LOG.info(
                        "Payment info is not instance of BrainTreePaymentInfoModel. Use default accelerator checkout flow.");
                return super.authorizePayment(securityCode);
            }

        } catch (final AdapterException ae) {
            LOG.error(ae.getMessage(), ae);
            return false;
        }
    }

    public boolean authorizePayment(CartModel cart) {
        CommerceOrderParameter parameter = new CommerceOrderParameter();
        parameter.setOrder(cart);
        parameter.setCustomFields(getCustomFields());
        PaymentTransactionEntryModel paymentTransactionEntryModel = brainTreeTransactionService
                .createAuthorizationTransaction(parameter);
        return paymentTransactionEntryModel != null
                && (TransactionStatus.ACCEPTED.name().equals(paymentTransactionEntryModel.getTransactionStatus())
                || TransactionStatus.REVIEW.name().equals(paymentTransactionEntryModel.getTransactionStatus()));
    }

    public PaymentTransactionEntryData capturePayment(OrderModel order, BigDecimal amount, PaymentTransactionEntryModel entry, boolean isOrderToBeForceCompleted) {
        CommerceOrderParameter parameter = new CommerceOrderParameter();
        parameter.setOrder(order);
        parameter.setAmount(amount);
        parameter.setTransactionEntry(entry);
        parameter.setPaymentInitiator(BraintreePaymentInitiator.UNSCHEDULED);
        return ordermanagementPaymentTransactionEntryConverter
                .convert(brainTreeTransactionService.createAuthorizationTransactionForCapture(parameter, isOrderToBeForceCompleted));
    }

    @Override
    public CCPaymentInfoData getPaymentDetails() {
        final CartModel cart = getCart();
        if (cart != null) {
            final PaymentInfoModel paymentInfo = cart.getPaymentInfo();
            if (paymentInfo instanceof BrainTreePaymentInfoModel) {
                return brainTreePaymentInfoConverter.convert((BrainTreePaymentInfoModel) paymentInfo);
            } else {
                return super.getPaymentDetails();
            }
        }

        return null;
    }

    public boolean setPaymentDetails(final String paymentInfoId, final String paymentMethodNonce,
                                     final String cvvNonce, final boolean is3dSecureFlow) {
        validateParameterNotNullStandardMessage("paymentInfoId", paymentInfoId);

        if (checkIfCurrentUserIsTheCartUser()) {
            final CustomerModel currentUserForCheckout = getCurrentUserForCheckout();
            if (StringUtils.isNotBlank(paymentInfoId)) {
                final BrainTreePaymentInfoModel paymentInfo = brainTreePaymentService
                        .completeCreateSubscriptionWithCvvVerification(currentUserForCheckout, paymentInfoId, cvvNonce);
                if (paymentInfo != null) {

                    brainTreePaymentService.saveBillingAddressIfNotExistForVenmoPaymentMethod(paymentInfo);
                    paymentInfo.setNonce(paymentMethodNonce);
                    paymentInfo.setThreeDSecureFlow(is3dSecureFlow);
                    getModelService().save(paymentInfo);
                    return true;
                } else {
                    super.setPaymentDetails(paymentInfoId);
                }
            }
        }
        return false;
    }

    public boolean updatePaymentMethodNonce(final String paymentId, final String nonce) {
        validateParameterNotNullStandardMessage("nonce", nonce);
        validateParameterNotNullStandardMessage("paymentId", paymentId);
        final CustomerModel currentUserForCheckout = getCurrentUserForCheckout();
        final BrainTreePaymentInfoModel paymentInfo = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(currentUserForCheckout, paymentId);
        if (paymentInfo != null) {
            paymentInfo.setNonce(nonce);
            getModelService().save(paymentInfo);
            return true;
        }
        return false;
    }

    @Override
    public boolean setPaymentDetails(final String paymentInfoId) {
        validateParameterNotNullStandardMessage("paymentInfoId", paymentInfoId);

        if (checkIfCurrentUserIsTheCartUser()) {
            final CustomerModel currentUserForCheckout = getCurrentUserForCheckout();
            if (StringUtils.isNotBlank(paymentInfoId)) {
                final BrainTreePaymentInfoModel paymentInfo = brainTreePaymentService
                        .completeCreateSubscription(currentUserForCheckout, paymentInfoId);
                if (paymentInfo != null) {
                    return true;
                } else {
                    return super.setPaymentDetails(paymentInfoId);
                }

            }
        }

        return false;
    }

    public String generateClientToken(final boolean generateWithBraintreeCustomerId) {
        String clientToken = StringUtils.EMPTY;
        try {
            UserModel userModel = userService.getCurrentUser();
            String braintreeCustomerId = ((CustomerModel) userModel).getBraintreeCustomerId();
            if (generateWithBraintreeCustomerId && braintreeCustomerId != null) {
                clientToken = brainTreePaymentService
                        .generateClientToken(braintreeCustomerId);
            } else {
                clientToken = brainTreePaymentService.generateClientToken(StringUtils.EMPTY);
            }
        } catch (final AdapterException exception) {
            LOG.error("[View Handler] Error during token generation!", exception);
        }
        return clientToken;
    }

    public BraintreeCheckoutData getBraintreeCheckoutData(final BraintreePageType pageType) {
        BraintreeCheckoutData braintreeCheckoutData = generateConfigurationData(pageType);

        if (braintreeCheckoutData != null) {
            braintreeCheckoutData.setSrcPaymentMethod(generateSrcMethodData());
            braintreeCheckoutData.setPaymentMethodsLimitation(getPaymentsLimitationData());
            if (checkPageType(pageType)) {
                braintreeCheckoutData.setCreditCardPaymentMethod(generateCreditCardMethodData());
                if (checkPageTypeAndConfigService(pageType, braintreeCheckoutData)) {
                    setShippingInformationForPayPalCheckoutData(braintreeCheckoutData);
                } else if (pageType.equals(BraintreePageType.MARK)) {
                    populateShippingAddress(braintreeCheckoutData);
                    braintreeCheckoutData.setLocalPaymentMethod(generateLocalPaymentMethodData());
                } else if (pageType.equals(BraintreePageType.CART) || pageType.equals(BraintreePageType.MINICART)) {
                    populateShippingAddress(braintreeCheckoutData);
                }
            }
        }
        return braintreeCheckoutData;
    }

    public boolean isPayPalLimitedDuringConnect(String email) {
        BraintreePluginConfigurationData configData = braintreeConfigurationFacade.getBraintreePluginConfigurationData();
        int maxAllowedPayments = configData.getPayPalConfigurationData().getSavedPayPalAccountLimit().getIntValue();

        if (maxAllowedPayments == 0) {
            return false;
        }

        CustomerModel customer = (CustomerModel) userService.getUserForUID(email);
        String braintreeCustomerId = customer.getBraintreeCustomerId();

        if (StringUtils.isBlank(braintreeCustomerId)) {
            return false;
        }

        int paypalPaymentsCount = braintreePaymentInfoService.countPaymentInfoByCustomerIdAndPaymentProvider(
                braintreeCustomerId, BrainTreePaymentMethod.PAYPALACCOUNT
        );

        return paypalPaymentsCount >= maxAllowedPayments;
    }

    private PaymentMethodsLimitationData getPaymentsLimitationData() {

        BraintreePluginConfigurationData configurationData = braintreeConfigurationFacade.getBraintreePluginConfigurationData();

        return GenericBuilder.of(PaymentMethodsLimitationData::new)
                .with(PaymentMethodsLimitationData::setPayPalOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.PAYPALACCOUNT,
                                configurationData.getPayPalConfigurationData().getSavedPayPalAccountLimit(),
                                configurationData.getPayPalConfigurationData().isPayPalEnabled()))
                .with(PaymentMethodsLimitationData::setSRCOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.VISACHECKOUTCARD,
                                configurationData.getSrcConfigurationData().getSavedSRCAccountLimit(),
                                configurationData.getSrcConfigurationData().isEnabled()))
                .with(PaymentMethodsLimitationData::setVenmoOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.VENMOACCOUNT,
                                configurationData.getVenmoConfigurationData().getSavedVenmoAccountLimit(),
                                configurationData.getVenmoConfigurationData().isEnabled()))
                .with(PaymentMethodsLimitationData::setGooglePayOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.ANDROIDPAYCARD,
                                configurationData.getGooglePayConfigurationData().getSavedGooglePayAccountLimit(),
                                configurationData.getGooglePayConfigurationData().isEnabled()))
                .with(PaymentMethodsLimitationData::setUsBankAccountOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.USBANKACCOUNT,
                                configurationData.getAchConfigurationData().getSavedACHAccountLimit(),
                                configurationData.getAchConfigurationData().isEnabled()))
                .with(PaymentMethodsLimitationData::setCreditCardOnLimit,
                        isPaymentOnLimit(BrainTreePaymentMethod.CREDITCARD,
                                configurationData.getHostedFieldsConfigurationData().getSavedCardAccountLimit(),
                                configurationData.getHostedFieldsConfigurationData().isEnabled()))
                .build();
    }

    private boolean isPaymentOnLimit(BrainTreePaymentMethod paymentProvider,
                                     BraintreePaymentMethodLimitData paymentMethodLimitData,
                                     boolean isPaymentEnable) {
        if (!isPaymentEnable) {
            return true;
        }
        if (paymentMethodLimitData.getIntValue() == 0) {
            return false;
        }
        CustomerModel customerModel = (CustomerModel) userService.getCurrentUser();

        if (StringUtils.isBlank(customerModel.getBraintreeCustomerId())) {
            return false;
        }

        String brainTreeCustomerId = customerModel.getBraintreeCustomerId();
        return braintreePaymentInfoService.countPaymentInfoByCustomerIdAndPaymentProvider(
                brainTreeCustomerId, paymentProvider
        ) >= paymentMethodLimitData.getIntValue();
    }

    private void populateShippingAddress(BraintreeCheckoutData braintreeCheckoutData) {
        if (getCartService().hasSessionCart() && getCartService().getSessionCart().getDeliveryAddress() != null) {
            final PayPalAddressData payPalAddress = payPalAddressDataConverter
                    .convert(getCartService().getSessionCart().getDeliveryAddress());
            braintreeCheckoutData.setShippingAddressOverride(payPalAddress);
        }
    }

    private void setShippingInformationForPayPalCheckoutData(BraintreeCheckoutData braintreeCheckoutData) {
        UserModel userModel = userService.getCurrentUser();
        AddressModel addressModel = userModel.getDefaultShipmentAddress();
        AddressModel sourceAddress = addressModel != null ? addressModel : userModel.getAddresses()
                .stream()
                .filter(address -> !address.getDuplicate())
                .reduce((first, second) -> second)
                .orElse(null);
        if (sourceAddress != null) {
            final PayPalAddressData payPalAddress = payPalAddressDataConverter
                    .convert(sourceAddress);
            if (sourceAddress.getCountry() != null) {
                Optional.ofNullable(getCart()).ifPresent(cartModel -> braintreeCheckoutData.setDeliveryAmount(
                        countDeliveryAddress(sourceAddress.getCountry().getIsocode()).getValue()));
            }
            braintreeCheckoutData.setShippingAddressOverride(payPalAddress);
        } else {
            braintreeCheckoutData.getPayPalPaymentMethod().setShouldShowShippingAddressMessage(true);
        }
    }

    private boolean checkPageTypeAndConfigService(BraintreePageType pageType, BraintreeCheckoutData braintreeCheckoutData) {
        return isPageTypeCartOrMinicart(pageType)
                && braintreeCheckoutData.getPayPalPaymentMethod().getShouldRenderPayPalChangePaymentButton();
    }

    private boolean isPageTypeCartOrMinicart(BraintreePageType pageType) {
        return pageType.equals(BraintreePageType.CART) || pageType.equals(BraintreePageType.MINICART);
    }

    private boolean checkPageType(BraintreePageType pageType) {
        return pageType.equals(BraintreePageType.ACCOUNT) || pageType.equals(BraintreePageType.MARK)
                || pageType.equals(BraintreePageType.CART) || pageType.equals(BraintreePageType.MINICART);
    }

    /**
     * Used to generate configuration data
     *
     * @param pageType
     * @return BraintreeCheckoutData
     */
    public BraintreeCheckoutData generateConfigurationData(final BraintreePageType pageType) {
        final BraintreeCheckoutData braintreeCheckoutData = new BraintreeCheckoutData();
        BraintreeConfigurationData braintreeConfigurationData = new BraintreeConfigurationData();
        braintreeConfigurationData.setCurrency(i18NService.getCurrentJavaCurrency().getCurrencyCode());
        braintreeConfigurationData.setEnvironment(brainTreeConfigService.getEnvironmentTypeName());
        braintreeConfigurationData.setLocale(i18NService.getCurrentLocale().toString());
        braintreeConfigurationData.setDbaName(brainTreeConfigService.getBrainTreeDisplayName());
        braintreeConfigurationData.setEndpointURL(brainTreeConfigService.getEndpointURL());
        braintreeConfigurationData.setUserAction(brainTreeConfigService.getUserAction());
        if (getCartService().hasSessionCart()) {
            CartModel cart = getCartService().getSessionCart();
            braintreeCheckoutData.setEnableShippingAddress(!isOnlyPickUp());
            braintreeCheckoutData.setAmount(cart.getTotalPrice());
            braintreeCheckoutData.setSubtotal(cart.getSubtotal());
        } else {
            braintreeCheckoutData.setEnableShippingAddress(false);
        }
        braintreeCheckoutData.setDropIn(generateDropInData());
        braintreeCheckoutData.setPayPalPaymentMethod(generatePayPalMethodData(pageType));
        braintreeCheckoutData.setConfigurationData(braintreeConfigurationData);
        braintreeCheckoutData.getConfigurationData().setClient_id(generateClientToken(
                braintreeCheckoutData.getPayPalPaymentMethod().getShouldRenderPayPalChangePaymentButton()
                        || brainTreeConfigService.isDropInEnabled()));
        braintreeCheckoutData.getConfigurationData().setBraintreeLocale(getBraintreeLocale());

        return braintreeCheckoutData;
    }

    private Boolean isOnlyPickUp() {
        return getCheckoutCart().getDeliveryOrderGroups().isEmpty();
    }

    private PayPalPaymentMethodData generatePayPalMethodData(final BraintreePageType pageType) {
        PayPalPaymentMethodData paymentMethodData = new PayPalPaymentMethodData();
        paymentMethodData.setPayPalStandardEnabled(brainTreeConfigService.isPayPalEnabled());
        paymentMethodData.setPayPalExpressEnabled(brainTreeConfigService.isPayPalExpressEnabled());
        paymentMethodData.setDisableFunding(brainTreeConfigService.getDisableFunding(pageType));
        paymentMethodData.setShouldRenderPayPalChangePaymentButton(getShouldRenderPayPalChangePaymentButton(pageType));
        paymentMethodData.setShouldShowShippingAddressMessage(false);
        return paymentMethodData;
    }

    private boolean getShouldRenderPayPalChangePaymentButton(BraintreePageType pageType) {
        String braintreeCustomerId = ((CustomerModel) userService.getCurrentUser()).getBraintreeCustomerId();

        List<BrainTreePaymentDetailsResult> savedPaymentMethods = new ArrayList<>();

        if (braintreeCustomerId != null) {
            List<BrainTreePaymentDetailsResult> paymentMethods = brainTreeUserFacade
                    .findBrainTreeCustomer(braintreeCustomerId)
                    .getPaymentMethods();
            if (paymentMethods != null) {
                savedPaymentMethods = paymentMethods
                        .stream()
                        .filter(brainTreePaymentDetailsResult -> BrainTreeUtils.isPayPalPayment(
                                brainTreePaymentDetailsResult.getPaymentProvider()))
                        .collect(Collectors.toList());
            }
        }
        return canRenderPayPalChangePaymentButton(pageType, savedPaymentMethods);
    }

    private boolean canRenderPayPalChangePaymentButton(BraintreePageType pageType,
                                                       List<BrainTreePaymentDetailsResult> savedPaymentMethods) {
        final boolean isConfigDataTrue = brainTreeConfigService.isChangePaymentMethodButtonEnabled()
                && !brainTreeConfigService.isDropInEnabled();
        final boolean isUserDataTrue = !brainTreeUserFacade.isAnonymousUser()
                && brainTreeUserFacade.isCurrentUserHasBrainTreeCustomerId();
        final boolean isNotAccountPageTrue = !BraintreePageType.ACCOUNT.equals(pageType);
        final boolean isSavedPaymentMethodPresent = savedPaymentMethods.size() > 0;
        return isConfigDataTrue && isUserDataTrue && isNotAccountPageTrue && isSavedPaymentMethodPresent;
    }

    private DropInData generateDropInData() {
        final CustomerModel customer = sessionService.getAttribute(USER);

        int expirationDays = brainTreeConfigService.getRestrictionPaymentMethodRemovalExpiration();
        boolean isForbiddenRemovingPm = orderRetrievalService.countOrdersWithExpiredTransactionsByCustomer(customer, expirationDays) != 0;
        boolean isRestrictionPaymentMethodRemovalEnabled = brainTreeConfigService.isRestrictionPaymentMethodRemovalEnable();

        DropInData dropInData = new DropInData();
        dropInData.setIsLimitedDeletePaymentMethods(isForbiddenRemovingPm && isRestrictionPaymentMethodRemovalEnabled);

        return dropInData;
    }


    private CreditCardPaymentMethodData generateCreditCardMethodData() {
        CreditCardPaymentMethodData paymentMethodData = new CreditCardPaymentMethodData();
        paymentMethodData
                .setPaymentsImagesUrl(getAcceptedPaymentMethodImages(BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS));
        return paymentMethodData;
    }

    private SrcPaymentMethodData generateSrcMethodData() {
        SrcPaymentMethodData paymentMethodData = new SrcPaymentMethodData();
        paymentMethodData.setSrcSdkUrl(brainTreeConfigService.getSrcSdkUrl());
        paymentMethodData.setSrcImgUrl(brainTreeConfigService.getSrcImgUrl());
        return paymentMethodData;
    }

    private LocalPaymentMethodData generateLocalPaymentMethodData() {
        LocalPaymentMethodData paymentMethodData = new LocalPaymentMethodData();
        paymentMethodData.setLpmFallbackReturnUrl(braintreeUrlResolutionFacade.buildReturnUrl(
                LOCAL_PAYMENT_METHODS_FALLBACK_URL + i18NService.getCurrentJavaCurrency().getCurrencyCode()));
        paymentMethodData.setLocalPaymentMethods(brainTreeLocalPaymentsConverter.convertAll(brainTreePaymentFacade.getLocalPaymentMethods()));
        return paymentMethodData;
    }

    public BraintreeCheckoutData getBraintreeConfigurationDataForConnectWithPayPalButton() {
        final BraintreeCheckoutData braintreeCheckoutData = new BraintreeCheckoutData();
        BraintreeConfigurationData braintreeConfigurationData = new BraintreeConfigurationData();
        braintreeConfigurationData.setClient_id(brainTreeConfigService.getClientID());
        braintreeConfigurationData.setPayPalConnectScript(brainTreeConfigService.getPayPalConnectScript());
        braintreeConfigurationData.setPayPalConnectScopes(brainTreeConfigService.getPayPalConnectScopes());
        braintreeConfigurationData.setPayPalConnectResponseType(brainTreeConfigService.getPayPalConnectResponseType());
        braintreeConfigurationData.setPayPalConnectReturnUrl(braintreeUrlResolutionFacade.buildReturnUrl(CONNECT_RETURN_URL));
        braintreeConfigurationData.setEnvironment(brainTreeConfigService.getEnvironmentType().getEnvironmentName());
        braintreeCheckoutData.setConfigurationData(braintreeConfigurationData);
        return braintreeCheckoutData;
    }

    /**
     * Used to count delivery address
     *
     * @param countyCode
     * @return PriceValue
     */
    public PriceValue countDeliveryAddress(String countyCode) {
        if (getCart() != null) {
            return braintreeDeliveryService.countDeliveryPriceForOrderAndCountyCode(getCart(), countyCode);
        }
        return null;
    }

    public BraintreeAssetsData getBraintreeAssets() {
        BraintreeAssetsData braintreeAssetsData = new BraintreeAssetsData();
        braintreeAssetsData.setGooglePayLogo(getBraintreeImageUrlByCode("googlePayMark"));
        braintreeAssetsData.setVenmoLogo(getBraintreeImageUrlByCode("venmoLogo"));
        braintreeAssetsData.setUsBankAccountImg(getBraintreeImageUrlByCode("usBankAccount"));
        braintreeAssetsData.setVenmoMarkImg(getBraintreeImageUrlByCode("venmoAcceptanceMark"));
        braintreeAssetsData.setApplePayLogo(getBraintreeImageUrlByCode("applePayLogo"));
        return braintreeAssetsData;
    }

    private String getBraintreeImageUrlByCode(String code) {
        if (StringUtils.isNotEmpty(code)) {
            for (final CatalogVersionModel catalogVersionModel : catalogVersionService.getSessionCatalogVersions()) {
                try {
                    final MediaModel media = mediaService.getMedia(catalogVersionModel, code);
                    if (media != null) {
                        return media.getDownloadURL();
                    }
                } catch (final UnknownIdentifierException ignore) {
                    // Ignore this exception
                }
            }
        }
        return null;
    }

    public String getBraintreeLocale() {
        final String brainTreeLocale = brainTreeConfigService.getBrainTreeLocale();
        try {
            if (StringUtils.isNotEmpty(brainTreeLocale) && BrainTreeSupportedLocaleConfig
                    .supportLocale(brainTreeLocale)) {
                return brainTreeLocale;
            } else {
                return BrainTreeSupportedLocaleConfig
                        .getSupportedLocaleTagByLanguage(i18NService.getCurrentLocale().getLanguage());
            }
        } catch (final Exception e) {
            LOG.error(e.getMessage(), e);
            return BrainTreeSupportedLocaleConfig
                    .getDefaultLocaleTag(i18NService.getCurrentLocale().getLanguage());
        }
    }

    public Boolean isPayPalCheckout(final OrderModel order) {
        final String paymentProvider = order.getPaymentInfo().getProperty("paymentProvider");
        return paymentProvider != null && BrainTreeUtils.isPayPalPayment(paymentProvider);
    }

    public AddressData getDeliveryAddressForCurrentCart() {
        return getDeliveryAddress();
    }

    public void setBrainTreePaymentInfoConverter(
            final Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter) {
        this.brainTreePaymentInfoConverter = brainTreePaymentInfoConverter;
    }

    /**
     * Used to get accepted payment method images
     *
     * @param paymentMethodsString
     * @return Map<String, String>
     */
    public Map<String, String> getAcceptedPaymentMethodImages(final String paymentMethodsString) {
        return brainTreeConfigService.getAcceptedPaymentMethodImages(paymentMethodsString);
    }

    public OrderData placeOrderByCart(CartModel cartModel) throws InvalidCartException {
        if (cartModel != null) {
            beforePlaceOrder(cartModel);
            final OrderModel orderModel = placeOrder(cartModel);
            afterPlaceOrder(cartModel, orderModel);
            if (orderModel != null) {
                return getOrderConverter().convert(orderModel);
            }
        }
        return null;
    }

    public OrderModel placeOrderForLocalPaymentReversed(CartModel cartModel) throws InvalidCartException {
        if (cartModel != null) {
            beforePlaceOrder(cartModel);
            final OrderModel orderModel = placeOrder(cartModel);
            afterPlaceOrder(cartModel, orderModel);
            return orderModel;
        }
        return null;
    }

    @Override
    public void beforePlaceOrder(final CartModel cartModel)
    {
        PaymentInfoModel paymentInfo = cartModel.getPaymentInfo();
        if (paymentInfo instanceof BrainTreePaymentInfoModel) {

            storeIntentToCart(cartModel);

            if (((BrainTreePaymentInfoModel) paymentInfo).getShipsFromPostalCode() == null) {
                storeShipsFromPostalCodeToCart(cartModel.getDeliveryAddress().getPostalcode(), cartModel);
            }

            if (((BrainTreePaymentInfoModel) paymentInfo).getCustomFields() == null) {
                storeCustomFieldsToCart(customFieldsService.getDefaultCustomFieldsMap(), cartModel);
            }
        }
    }

    @Override
    protected void afterPlaceOrder(CartModel cartModel, OrderModel orderModel) {
        if (orderModel != null) {
            getModelService().remove(cartModel);
            getModelService().refresh(orderModel);
        }
    }

    /**
     * This method is used to process Local Payment Reversed Webhook
     *
     * @param cartModel cart
     */
    public void processLocalPaymentReversedWebhook(final CartModel cartModel) {
        List<PaymentTransactionEntryModel> paymentTransactionEntrys = Lists.newArrayList();
        paymentTransactionEntrys.add(brainTreeTransactionService
                .createTransactionEntryForLocalPaymentReversed(cartModel, PaymentTransactionType.AUTHORIZATION));
        paymentTransactionEntrys.add(brainTreeTransactionService
                .createTransactionEntryForLocalPaymentReversed(cartModel, PaymentTransactionType.CAPTURE));
        paymentTransactionEntrys.add(brainTreeTransactionService
                .createTransactionEntryForLocalPaymentReversed(cartModel, PaymentTransactionType.REFUND_FOLLOW_ON));

        brainTreeTransactionService
                .createPaymentTransactionForLocalPaymentReversed(cartModel, paymentTransactionEntrys);
    }


    private Map<String, String> getCustomFields() {
        return customFieldsService.getDefaultCustomFieldsMap();
    }

    public void setBrainTreePaymentService(final BrainTreePaymentService brainTreePaymentService) {
        this.brainTreePaymentService = brainTreePaymentService;
    }

    public void setBrainTreeTransactionService(final BrainTreeTransactionService brainTreeTransactionService) {
        this.brainTreeTransactionService = brainTreeTransactionService;
    }

    @Override
    public void setUserService(final UserService userService) {
        this.userService = userService;
    }

    @Override
    public UserService getUserService() {
        return userService;
    }

    public void setPayPalAddressDataConverter(final BrainTreePayPalAddressDataConverter payPalAddressDataConverter) {
        this.payPalAddressDataConverter = payPalAddressDataConverter;
    }

    public void setBrainTreeConfigService(final BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }

    public void setBrainTreeLocalPaymentsConverter(
            Converter<BraintreeLocalPaymentMethodsModel, BraintreeLocalPaymentMethodData>
                    brainTreeLocalPaymentsConverter) {
        this.brainTreeLocalPaymentsConverter = brainTreeLocalPaymentsConverter;
    }

    public void setBrainTreePaymentFacade(DefaultBrainTreePaymentFacade brainTreePaymentFacade) {
        this.brainTreePaymentFacade = brainTreePaymentFacade;
    }

    public void setCustomFieldsService(BraintreeCustomFieldsService customFieldsService) {
        this.customFieldsService = customFieldsService;
    }

    public void setI18NService(I18NService i18NService) {
        this.i18NService = i18NService;

    }

    public void setOrdermanagementPaymentTransactionEntryConverter(
            Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter) {
        this.ordermanagementPaymentTransactionEntryConverter = ordermanagementPaymentTransactionEntryConverter;
    }

    public void setMediaService(MediaService mediaService) {
        this.mediaService = mediaService;
    }

    public void setCatalogVersionService(CatalogVersionService catalogVersionService) {
        this.catalogVersionService = catalogVersionService;
    }

    public void setBrainTreeUserFacade(BrainTreeUserFacade brainTreeUserFacade) {
        this.brainTreeUserFacade = brainTreeUserFacade;
    }

    public void setBraintreeDeliveryService(BraintreeDeliveryService braintreeDeliveryService) {
        this.braintreeDeliveryService = braintreeDeliveryService;
    }

    public void setBraintreePaymentInfoService(
            BraintreePaymentInfoService braintreePaymentInfoService) {
        this.braintreePaymentInfoService = braintreePaymentInfoService;
    }

    public void setOrderRetrievalService(BraintreeOrderRetrievalService orderRetrievalService) {
        this.orderRetrievalService = orderRetrievalService;
    }

    public void setSessionService(SessionService sessionService) {
        this.sessionService = sessionService;
    }

    public void setBraintreeConfigurationFacade(BraintreeConfigurationFacade braintreeConfigurationFacade) {
        this.braintreeConfigurationFacade = braintreeConfigurationFacade;
    }

    public void setConnectWithPayPalFacade(BrainTreeConnectWithPayPalFacade connectWithPayPalFacade) {
        this.connectWithPayPalFacade = connectWithPayPalFacade;
    }

    public void setBrainTreeCustomerAccountService(BrainTreeCustomerAccountService brainTreeCustomerAccountService) {
        this.brainTreeCustomerAccountService = brainTreeCustomerAccountService;
    }

    public void setBraintreeUrlResolutionFacade(BraintreeUrlResolutionFacade braintreeUrlResolutionFacade) {
        this.braintreeUrlResolutionFacade = braintreeUrlResolutionFacade;
    }
}
