package com.braintree.facade.impl;

import com.braintree.command.request.BrainTreeAddressRequest;
import com.braintree.command.request.BrainTreeCustomerRequest;
import com.braintree.command.request.BrainTreeDeletePaymentMethodRequest;
import com.braintree.command.request.BrainTreeUpdateCreditCardRequest;
import com.braintree.command.request.BrainTreeUpdateCustomerRequest;
import com.braintree.command.result.BrainTreeBillingAddressResult;
import com.braintree.command.result.BrainTreeFindCustomerResult;
import com.braintree.command.result.BrainTreeUpdateCreditCardBillingAddressResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.exceptions.BrainTreeConnectWithPayPalException;
import com.braintree.exceptions.BraintreeDeletePaymentDetailsWithNotCapturedOrderException;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.form.UpdateCreditCardBillingAddressForm;
import com.braintree.hybris.data.BraintreeUserData;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.order.service.BraintreeOrderRetrievalService;
import com.braintree.servicelayer.i18n.BraintreeRegionI18NService;
import com.braintree.util.BrainTreeUtils;
import com.google.common.collect.Lists;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercefacades.user.data.CustomerData;
import de.hybris.platform.commercefacades.user.impl.DefaultUserFacade;
import de.hybris.platform.commerceservices.customer.CustomerEmailResolutionService;
import de.hybris.platform.commerceservices.enums.CustomerType;
import de.hybris.platform.converters.Populator;
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.payment.AdapterException;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import de.hybris.platform.servicelayer.user.PasswordEncoderService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.regex.Pattern;

import static com.braintree.constants.BraintreeConstants.BRAINTREE_TRY_DELETE_PAYMENT_DETAILS_WITH_NOT_CAPTURED_ORDER;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;
import static java.util.Objects.nonNull;

public class DefaultBrainTreeUserFacade extends DefaultUserFacade implements BrainTreeUserFacade {

    private static final Logger LOGGER = Logger.getLogger(DefaultBrainTreeUserFacade.class);

    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BrainTreePaymentService brainTreePaymentService;
    private BrainTreeConfigService brainTreeConfigService;
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    private BraintreeRegionI18NService regionI18NService;
    private BraintreeOrderRetrievalService orderRetrievalService;
    private CustomerEmailResolutionService customerEmailResolutionService;
    private PasswordEncoderService passwordEncoderService;
    private Converter<BrainTreeBillingAddressResult, AddressModel> addressResultToAddressModelConverter;
    private Converter<BrainTreePaymentInfoModel, CCPaymentInfoData> brainTreePaymentInfoConverter;
    private Converter<CustomerModel, CustomerData> customerConverter;
    private Populator<CCPaymentInfoData, BrainTreePaymentInfoModel> braintreePaymentInfoReversePopulator;

    @Override
    public void addAddress(final AddressData addressData) {
        validateParameterNotNullStandardMessage("addressData", addressData);

        if (Objects.nonNull(addressData.getRegion())
                && Objects.isNull(regionI18NService.findRegion(getCommonI18NService().getCountry(addressData.getCountry().getIsocode()), addressData.getRegion().getIsocode()))) {
            addressData.setRegion(null);
        }

        if (StringUtils.isBlank(addressData.getFirstName()) || StringUtils.isBlank(addressData.getLastName())) {
            addressData.setVisibleInAddressBook(false);
        }

        final CustomerModel currentCustomer = getCurrentUserForCheckout();

        final boolean makeThisAddressTheDefault = addressData.isDefaultAddress()
                || (currentCustomer.getDefaultShipmentAddress() == null && addressData.isVisibleInAddressBook());

        // Create the new address model
        final AddressModel newAddress = getModelService().create(AddressModel.class);
        getAddressReversePopulator().populate(addressData, newAddress);

        // Store the address against the user
        getCustomerAccountService().saveAddressEntry(currentCustomer, newAddress);

        // Update the address ID in the newly created address
        addressData.setId(newAddress.getPk().toString());

        if (makeThisAddressTheDefault) {
            getCustomerAccountService().setDefaultAddressEntry(currentCustomer, newAddress);
        }
    }

    @Override
    public boolean isDuplicateAddressForCurrentUser(final AddressData addressData) {
        return isDuplicateAddress(addressData, (CustomerModel) getUserService().getCurrentUser());
    }

    @Override
    public void saveAddressForCurrentCustomer(AddressData addressData) {
        CustomerModel customer = getCurrentUserForCheckout();
        addressData.setShippingAddress(true);
        addressData.setEmail(customerEmailResolutionService.getEmailForCustomer(customer));
        addAddress(addressData);
        customer.getAddresses().stream()
                .filter(address -> address.getPk().toString().equals(addressData.getId()))
                .findAny()
                .ifPresentOrElse(address -> {
                    addressData.setBrainTreeAddressId(address.getBrainTreeAddressId());
                    addressData.setEmail(address.getEmail());
                    address.setVisibleInAddressBook(true);
                    getModelService().save(address);
                }, () -> LOGGER.error("Failed to saving address"));
        ;
    }

    private boolean isDuplicateAddress(final AddressData addressData, final CustomerModel customerModel) {
        BiPredicate<String, String> compare = (s1, s2) -> {
            if (StringUtils.isEmpty(s1) && s2 == null) {
                return true;
            }
            return s1 != null && s1.equals(s2);
        };

        BiPredicate<AddressModel, AddressData> compareRegion = (a1, a2) -> {
            if (a1.getRegion() == null || a2.getRegion() == null) {
                return true;
            }
            return a1.getRegion().getIsocode().equals(a2.getRegion().getIsocode());
        };

        for (AddressModel address : customerModel.getAddresses()) {
            if (address.getVisibleInAddressBook()
                    && compare.test(address.getCountry().getIsocode(), addressData.getCountry().getIsocode())
                    && compareRegion.test(address, addressData)
                    && compare.test(address.getFirstname(), addressData.getFirstName())
                    && compare.test(address.getLastname(), addressData.getLastName())
                    && compare.test(address.getTown(), addressData.getTown())
                    && compare.test(address.getPostalcode(), addressData.getPostalCode())
                    && compare.test(address.getLine1(), addressData.getLine1())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void editAddress(final AddressData addressData) {
        final String brainTreeAddressId = getBrainTreeAddressId(addressData);

        if (StringUtils.isNotEmpty(brainTreeAddressId)) {
            final BrainTreeAddressRequest addressRequest = convertBrainTreeAddress(addressData);
            addressRequest.setAddressId(brainTreeAddressId);
            brainTreePaymentService.updateAddress(addressRequest);
        }

        super.editAddress(addressData);
    }

    @Override
    public void removeAddress(final AddressData addressData) {
        final String brainTreeAddressId = getBrainTreeAddressId(addressData);

        if (StringUtils.isNotEmpty(brainTreeAddressId)) {
            final BrainTreeAddressRequest addressRequest = convertBrainTreeAddress(addressData);
            addressRequest.setAddressId(brainTreeAddressId);
            brainTreePaymentService.removeAddress(addressRequest);
        }

        super.removeAddress(addressData);
    }

    private String getBrainTreeAddressId(final AddressData addressData) {
        validateParameterNotNullStandardMessage("addressData", addressData);
        final CustomerModel currentCustomer = getCurrentUserForCheckout();
        final AddressModel addressModel = getCustomerAccountService()
                .getAddressForCode(currentCustomer, addressData.getId());
        return addressModel.getBrainTreeAddressId();
    }

    private BrainTreeAddressRequest convertBrainTreeAddress(final AddressData address) {
        final String customerID = getCheckoutCustomerStrategy().getCurrentUserForCheckout().getBraintreeCustomerId();
        final BrainTreeAddressRequest addressRequest = new BrainTreeAddressRequest(customerID);
        addressRequest.setCompany(address.getTitle());
        addressRequest.setStreetAddress(address.getLine1());
        addressRequest.setExtendedAddress(address.getLine2());
        addressRequest.setFirstName(address.getFirstName());
        addressRequest.setLastName(address.getLastName());
        addressRequest.setLocality(address.getTown());
        addressRequest.setPostalCode(address.getPostalCode());

        if (address.getCountry() != null) {
            addressRequest.setCountryCodeAlpha2(address.getCountry().getIsocode());
        }
        if (address.getRegion() != null) {
            //The state or province. For PayPal addresses, the region must be a 2-letter abbreviation; for all other payment methods, it must be less than or equal to 255 characters.
            //because of hybris specific use the isocodeShort - 2 character isocode - and its right for braintree
            //the isocode return  2 character isocode US-CA - wrong for braintree
            addressRequest.setRegion(address.getRegion().getIsocodeShort());
        }

        return addressRequest;
    }

    @Override
    public void editPaymentMethod(final UpdateCreditCardBillingAddressForm updateCreditCardBillingAddressForm) {
        validateParameterNotNullStandardMessage("updateCreditCardBillingAddressForm", updateCreditCardBillingAddressForm);
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();
        final BrainTreePaymentInfoModel ccPaymentInfoModel = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(
                        currentCustomer, updateCreditCardBillingAddressForm.getPaymentMethodId());
        final BrainTreeUpdateCreditCardRequest request = new BrainTreeUpdateCreditCardRequest(
                ccPaymentInfoModel.getPaymentMethodToken());
        request.setToken(ccPaymentInfoModel.getPaymentMethodToken());
        final BillingInfo billingInfo = new BillingInfo();
        populateBillingInfo(billingInfo, updateCreditCardBillingAddressForm);
        request.setBillingInfo(billingInfo);
        final BrainTreeUpdateCreditCardBillingAddressResult result = brainTreePaymentService.updatePaymentMethod(request);
        if (result.isSuccess()) {
            final BrainTreeBillingAddressResult billingAddressResult = result.getBillingAddressResult();
            billingAddressResult.setCountyCode(updateCreditCardBillingAddressForm.getAddressData().getCountry().getIsocode());
            final AddressModel billingAddress = getOrCreateBillingAddress(ccPaymentInfoModel);
            addressResultToAddressModelConverter.convert(billingAddressResult, billingAddress);
            getModelService().save(billingAddress);
            ccPaymentInfoModel.setPaymentMethodToken(result.getPaymentMethodToken());
            ccPaymentInfoModel.setBillingAddress(billingAddress);
            getModelService().save(ccPaymentInfoModel);
            getModelService().refresh(ccPaymentInfoModel);
        } else {
            throw new AdapterException(result.getErrorMessage());
        }
    }

    private AddressModel getOrCreateBillingAddress(BrainTreePaymentInfoModel ccPaymentInfoModel) {
        return Optional.ofNullable(ccPaymentInfoModel.getBillingAddress())
                .orElseGet(() -> {
                    AddressModel billingAddress = getModelService().create(AddressModel.class);
                    billingAddress.setOwner(ccPaymentInfoModel);
                    return billingAddress;
                });
    }

    private void populateBillingInfo(final BillingInfo billingInfo, final UpdateCreditCardBillingAddressForm updateCreditCardBillingAddressForm) {
        billingInfo.setFirstName(updateCreditCardBillingAddressForm.getAddressData().getFirstName());
        billingInfo.setLastName(updateCreditCardBillingAddressForm.getAddressData().getLastName());
        billingInfo.setStreet1(updateCreditCardBillingAddressForm.getAddressData().getLine1());
        billingInfo.setStreet2(updateCreditCardBillingAddressForm.getAddressData().getLine2());
        billingInfo.setCity(updateCreditCardBillingAddressForm.getAddressData().getTown());
        billingInfo.setCountry(updateCreditCardBillingAddressForm.getAddressData().getCountry().getIsocode());

        if (updateCreditCardBillingAddressForm.getAddressData().getRegion() != null) {
            billingInfo.setState(updateCreditCardBillingAddressForm.getAddressData().getRegion().getIsocode());
        }

        billingInfo.setPostalCode(updateCreditCardBillingAddressForm.getAddressData().getPostalCode());
    }

    @Override
    public boolean isCurrentUserHasBrainTreeCustomerId() {
        UserModel currentUser = getUserService().getCurrentUser();
        return !StringUtils.isEmpty(((CustomerModel) currentUser).getBraintreeCustomerId());
    }


    @Override
    public List<CCPaymentInfoData> getCCPaymentInfos(boolean saved) {

        final List<CCPaymentInfoData> ccPaymentInfos = super.getCCPaymentInfos(saved);
        final List<CCPaymentInfoData> brainTreeCCPaymentInfos = getBrainTreeCCPaymentInfos(saved);
        ccPaymentInfos.addAll(brainTreeCCPaymentInfos);
        return ccPaymentInfos;

    }

    @Override
    public List<CCPaymentInfoData> getBrainTreeCCPaymentInfos(final boolean saved) {

        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();
        final Collection<BrainTreePaymentInfoModel> paymentInfos = brainTreeCustomerAccountService
                .getBrainTreePaymentInfos(
                        currentCustomer, saved);

        final List<BrainTreePaymentInfoModel> creditCards = Lists.newArrayList();

        boolean isPayPalEnabled = brainTreeConfigService.isPayPalEnabled();
        boolean isHostedFieldsEnabled = brainTreeConfigService.isHostedFieldEnabled();
        boolean isVenmoEnabled = brainTreeConfigService.isVenmoEnabled();
        boolean isGooglePayEnabled = brainTreeConfigService.isGooglePayEnabled();
        boolean isSrcEnabled = brainTreeConfigService.isSrcEnabled();
        boolean isUsBankAccountEnabled = brainTreeConfigService.isUsBankAccountEnabled();
        boolean isAccountPaymentEnabled = isPayPalEnabled && isVenmoEnabled && isGooglePayEnabled;
        if (isHostedFieldsEnabled && isAccountPaymentEnabled && isSrcEnabled && isUsBankAccountEnabled) {
            addAllPaymentInfos(paymentInfos, creditCards);
        } else {
            for (final PaymentInfoModel paymentInfoModel : paymentInfos) {
                if (!(paymentInfoModel instanceof BrainTreePaymentInfoModel)) {
                    continue;
                }
                final BrainTreePaymentInfoModel brainTreePaymentInfo = (BrainTreePaymentInfoModel) paymentInfoModel;
                final String paymentProvider = (brainTreePaymentInfo).getPaymentProvider();
                if (isHostedFieldsEnabled && BrainTreeUtils.isCreditCardPayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                    continue;
                }
                if (isPayPalEnabled && BrainTreeUtils.isPayPalPayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                    continue;
                }
                if (isVenmoEnabled && BrainTreeUtils.isVenmoPayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                    continue;
                }
                if (isGooglePayEnabled && BrainTreeUtils.isGooglePayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                    continue;
                }
                if (isSrcEnabled && BrainTreeUtils.isSrcPayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                }
                if (isUsBankAccountEnabled && BrainTreeUtils.isUsBankAccountPayment(paymentProvider)) {
                    creditCards.add(brainTreePaymentInfo);
                }
            }
        }

        final List<CCPaymentInfoData> ccPaymentInfos = new ArrayList<>();
        final PaymentInfoModel defaultPaymentInfoModel = currentCustomer.getDefaultPaymentInfo();

        for (final BrainTreePaymentInfoModel ccPaymentInfoModel : creditCards) {
            final CCPaymentInfoData defaultPaymentInfoData = brainTreePaymentInfoConverter
                    .convert(ccPaymentInfoModel);

            if (ccPaymentInfoModel.equals(defaultPaymentInfoModel) &&
                    nonNull(defaultPaymentInfoData)) {
                defaultPaymentInfoData.setDefaultPaymentInfo(true);
            }

            if (isPaymentMethodDeletedOnBraintree(defaultPaymentInfoData)) {
                ccPaymentInfoModel.setIsDeletedOnBraintree(true);
                getModelService().save(ccPaymentInfoModel);
                continue;
            }

            ccPaymentInfos.add(defaultPaymentInfoData);
        }
        return ccPaymentInfos;
    }

    private boolean isPaymentMethodDeletedOnBraintree(CCPaymentInfoData paymentInfoData) {
        brainTreePaymentFacade.setPaymentMethodNonce(paymentInfoData);
        return Optional.ofNullable(paymentInfoData).map(CCPaymentInfoData::getPaymentMethodNonce).isEmpty();
    }

    private void addAllPaymentInfos(Collection<BrainTreePaymentInfoModel> paymentInfos,
                                    List<BrainTreePaymentInfoModel> creditCards) {
        for (final PaymentInfoModel paymentInfoModel : paymentInfos) {
            if (paymentInfoModel instanceof BrainTreePaymentInfoModel) {
                creditCards.add((BrainTreePaymentInfoModel) paymentInfoModel);
            }
        }
    }

    @Override
    public BrainTreeFindCustomerResult findBrainTreeCustomer(String braintreeCustomerId) {
        if (StringUtils.isEmpty(braintreeCustomerId)) {
            return new BrainTreeFindCustomerResult(false);
        }
        final BrainTreeCustomerRequest findCustomerRequest = new BrainTreeCustomerRequest(braintreeCustomerId);
        findCustomerRequest.setCustomerId(braintreeCustomerId);
        return brainTreePaymentService.findCustomer(findCustomerRequest);
    }

    @Override
    public CustomerData getCustomerByPayerId(String payerId) {
        return brainTreeCustomerAccountService.getCustomerByPayerId(payerId)
                .map(customerConverter::convert)
                .orElseThrow(() -> new BrainTreeConnectWithPayPalException("Customer with this payer id does not exist."));
    }

    @Override
    public boolean isCustomerWithPayerIdExist(String payerId) {
        return brainTreeCustomerAccountService.getCustomerByPayerId(payerId).isPresent();
    }

    public CustomerData getCustomerDataByUid(final String uId) {
        return customerConverter.convert(getUserService().getUserForUID(uId, CustomerModel.class));
    }

    @Override
    public AddressData getBillingAddressByPaymentMethodId(String paymentMethodId) {
        validateParameterNotNullStandardMessage("paymentMethodId", paymentMethodId);
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();
        final BrainTreePaymentInfoModel ccPaymentInfoModel = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(
                        currentCustomer, paymentMethodId);
        AddressData addressData = null;
        if (ccPaymentInfoModel.getBillingAddress() != null) {
            addressData = getAddressConverter().convert(ccPaymentInfoModel.getBillingAddress());
        }
        return addressData;
    }

    @Override
    public boolean isLoginDisabledForCurrentEmail(String email) {
        return getUserService().getUserForUID(email).isLoginDisabled();
    }

    @Override
    public boolean isPasswordValid(final String username, final String password) {
        final UserModel user = getUserService().getUserForUID(username);
        return passwordEncoderService.isValid(user, password);
    }

    @Override
    public void unlinkCCPaymentInfo(final String id) {
        validateParameterNotNullStandardMessage("id", id);
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();

        final BrainTreePaymentInfoModel brainTreePaymentInfo = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(
                        currentCustomer, id);

        brainTreeCustomerAccountService.unlinkCCPaymentInfo(currentCustomer, brainTreePaymentInfo);
    }

    @Override
    public void updateCCPaymentInfo(final CCPaymentInfoData paymentInfo) {
        validateParameterNotNullStandardMessage("paymentInfo", paymentInfo);
        validateParameterNotNullStandardMessage("paymentInfoID", paymentInfo.getId());
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();

        BrainTreePaymentInfoModel brainTreePaymentInfoForCode = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(currentCustomer, paymentInfo.getId());
        if (brainTreePaymentInfoForCode != null) {
            braintreePaymentInfoReversePopulator.populate(paymentInfo, brainTreePaymentInfoForCode);
            getModelService().save(brainTreePaymentInfoForCode);
            if (brainTreePaymentInfoForCode.getBillingAddress() != null) {
                getModelService().save(brainTreePaymentInfoForCode.getBillingAddress());
                getModelService().refresh(brainTreePaymentInfoForCode);
            }

        } else {
            super.updateCCPaymentInfo(paymentInfo);
        }

    }

    protected void updateDefaultPaymentInfo(final CustomerModel currentCustomer) {
        if (currentCustomer.getDefaultPaymentInfo() == null) {
            List<BrainTreePaymentInfoModel> brainTreePaymentInfoModelList = brainTreeCustomerAccountService
                    .getBrainTreePaymentInfos(currentCustomer, true);

            if (CollectionUtils.isNotEmpty(brainTreePaymentInfoModelList)) {
                BrainTreePaymentInfoModel paymentInfoModel = brainTreePaymentInfoModelList.get(brainTreePaymentInfoModelList.size() - 1);
                paymentInfoModel.setIsDefault(Boolean.TRUE);
                getModelService().save(paymentInfoModel);
                getCustomerAccountService().setDefaultPaymentInfo(currentCustomer, paymentInfoModel);
            } else {
                super.updateDefaultPaymentInfo(currentCustomer);
            }
        }
    }

    @Override
    public void removeBTCCPaymentInfo(final String paymentMethodToken) {
        validateParameterNotNullStandardMessage("paymentMethodToken", paymentMethodToken);
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();
        final BrainTreeDeletePaymentMethodRequest request = new BrainTreeDeletePaymentMethodRequest(
                currentCustomer.getBraintreeCustomerId(), paymentMethodToken);
        brainTreePaymentService.deletePaymentMethod(request);
    }

    @Override
    public CCPaymentInfoData getCCPaymentInfoForCode(final String code) {
        if (StringUtils.isNotBlank(code)) {
            final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();
            final BrainTreePaymentInfoModel ccPaymentInfoModel = brainTreeCustomerAccountService
                    .getBrainTreePaymentInfoForCode(
                            currentCustomer, code);
            if (ccPaymentInfoModel != null) {
                final PaymentInfoModel defaultPaymentInfoModel = currentCustomer.getDefaultPaymentInfo();
                final CCPaymentInfoData paymentInfoData = brainTreePaymentInfoConverter
                        .convert(ccPaymentInfoModel);
                if (ccPaymentInfoModel.equals(defaultPaymentInfoModel)) {
                    paymentInfoData.setDefaultPaymentInfo(true);
                }
                return paymentInfoData;
            } else {
                return super.getCCPaymentInfoForCode(code);
            }
        }

        return null;
    }

    @Override
    public void setDefaultPaymentInfo(final CCPaymentInfoData paymentInfoData) {
        validateParameterNotNullStandardMessage("paymentInfoData", paymentInfoData);
        final CustomerModel currentCustomer = getCurrentUserForCheckout();

        BrainTreePaymentInfoModel paymentInfoModel = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(currentCustomer, paymentInfoData.getId());

        if (paymentInfoModel != null) {
            paymentInfoModel.setIsDefault(Boolean.TRUE);
            getCustomerAccountService().setDefaultPaymentInfo(currentCustomer, paymentInfoModel);
            getModelService().save(paymentInfoModel);
            setBrainTreeDefaultPaymentInfo(currentCustomer, paymentInfoModel);
        } else {
            super.setDefaultPaymentInfo(paymentInfoData);
        }
    }

    private void setBrainTreeDefaultPaymentInfo(CustomerModel customer, BrainTreePaymentInfoModel paymentInfo) {
        BrainTreeUpdateCustomerRequest customerRequest = new BrainTreeUpdateCustomerRequest(paymentInfo.getCode());
        customerRequest.setId(customer.getBraintreeCustomerId());
        customerRequest.setDefaultPaymentMethodToken(paymentInfo.getPaymentMethodToken());
        brainTreePaymentService.setBrainTreeDefaultPaymentInfo(customerRequest);
    }

    @Override
    public void removeCCPaymentInfo(final String id) {
        validateParameterNotNullStandardMessage("id", id);
        final CustomerModel currentCustomer = (CustomerModel) getUserService().getCurrentUser();

        final BrainTreePaymentInfoModel brainTreePaymentInfo = brainTreeCustomerAccountService
                .getBrainTreePaymentInfoForCode(
                        currentCustomer, id);

        if (brainTreePaymentInfo != null) {
            if (brainTreeConfigService.isRestrictionPaymentMethodRemovalEnable()) {
                boolean isAvailableDeletePaymentDetails =
                        BrainTreeUtils.isPayPalPayment(brainTreePaymentInfo.getPaymentProvider())
                                || orderRetrievalService.countOrdersWithExpiredTransactions(brainTreePaymentInfo, brainTreeConfigService.getRestrictionPaymentMethodRemovalExpiration()) == 0;

                if (!isAvailableDeletePaymentDetails) {
                    throw new BraintreeDeletePaymentDetailsWithNotCapturedOrderException(
                            BRAINTREE_TRY_DELETE_PAYMENT_DETAILS_WITH_NOT_CAPTURED_ORDER);
                }
            }

            brainTreeCustomerAccountService.deleteBrainTreePaymentInfo(currentCustomer, brainTreePaymentInfo);

            if (brainTreePaymentInfo.isIsDefault()) {
                brainTreePaymentInfo.setIsDefault(Boolean.FALSE);
                currentCustomer.setDefaultPaymentInfo(null);
                getModelService().save(currentCustomer);
            }

            if (brainTreePaymentInfo.getPaymentMethodToken() != null) {
                removeBTCCPaymentInfo(brainTreePaymentInfo.getPaymentMethodToken());
                brainTreePaymentInfo.setIsDeletedOnBraintree(true);
                getModelService().save(brainTreePaymentInfo);
            }


        } else {
            super.removeCCPaymentInfo(id);
        }

        updateDefaultPaymentInfo(currentCustomer);
    }


    @Override
    public boolean isPayerIdInCustomer(String uid) {
        return StringUtils.isNotBlank(((CustomerModel) getUserService().getUserForUID(uid)).getPayPalPayerId());
    }

    public BraintreeUserData getUserData() {
        UserModel currentUser = getCartService().getSessionCart().getUser();

        BraintreeUserData userData = new BraintreeUserData();

        if (currentUser.getName() != null) {
            final String[] names = currentUser.getName().split(" ", 2);
            userData.setFirstName(names[0]);
            if (names.length > 1) {
                userData.setLastName(names[1]);
            }
        }

        if (CustomerType.GUEST.getCode().equalsIgnoreCase(currentUser.getName())) {
            String[] splatUid = currentUser.getUid().split(Pattern.quote("|"));
            userData.setEmail(splatUid[splatUid.length - 1]);
            userData.setLastName(currentUser.getName());

        } else {
            userData.setEmail(currentUser.getUid());
        }

        return userData;
    }

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

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

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

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

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

    public void setBraintreePaymentInfoReversePopulator(
            Populator<CCPaymentInfoData, BrainTreePaymentInfoModel> braintreePaymentInfoReversePopulator) {
        this.braintreePaymentInfoReversePopulator = braintreePaymentInfoReversePopulator;
    }

    public void setAddressResultToAddressModelConverter(
            Converter<BrainTreeBillingAddressResult, AddressModel> addressResultToAddressModelConverter) {
        this.addressResultToAddressModelConverter = addressResultToAddressModelConverter;
    }

    public void setCustomerConverter(Converter<CustomerModel, CustomerData> customerConverter) {
        this.customerConverter = customerConverter;
    }

    public void setRegionI18NService(BraintreeRegionI18NService regionI18NService) {
        this.regionI18NService = regionI18NService;
    }

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

    public void setCustomerEmailResolutionService(CustomerEmailResolutionService customerEmailResolutionService) {
        this.customerEmailResolutionService = customerEmailResolutionService;
    }

    public void setPasswordEncoderService(PasswordEncoderService passwordEncoderService) {
        this.passwordEncoderService = passwordEncoderService;
    }
}
