/**
 *
 */
package com.braintree.controllers.handler;

import com.braintree.configuration.BrainTreeConfigurationListener;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeControllerConstants;
import com.braintree.controllers.BraintreeaddonControllerConstants;
import com.braintree.enums.BraintreePageType;
import com.braintree.facade.BrainTreeStylesConfigurationFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.facade.impl.DefaultBrainTreeSetUpPasswordFacade;
import com.braintree.facade.BraintreeConfigurationFacade;
import com.braintree.facade.impl.DefaultBrainTreeUserFacade;
import com.braintree.hybris.data.BraintreeLocalPaymentMethodData;
import com.braintree.hybris.data.BraintreeUserData;
import com.braintree.hybris.data.BraintreeCheckoutData;
import com.braintree.util.BrainTreeUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.hybris.platform.addonsupport.interceptors.BeforeViewHandlerAdaptee;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.cms2.servicelayer.services.CMSComponentService;
import com.braintree.hybris.data.PaymentMethodsLimitationData;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.core.Registry;
import de.hybris.platform.servicelayer.session.SessionService;
import de.hybris.platform.util.config.ConfigIntf;
import org.apache.log4j.Logger;
import org.springframework.ui.ModelMap;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


import static com.braintree.controllers.BraintreeaddonControllerConstants.BRAINTREE_CHECKOUT_DATA;
import static com.braintree.controllers.BraintreeaddonControllerConstants.BRAINTREE_CONFIG_DATA;
import static com.braintree.controllers.BraintreeaddonControllerConstants.Views.Pages.MultiStepCheckout.CheckoutSummaryPage;
import static com.braintree.controllers.BraintreeaddonControllerConstants.Views.Pages.MultiStepCheckout.FallbackPage;
import static com.braintree.controllers.BraintreeaddonControllerConstants.Views.Pages.MultiStepCheckout.SilentOrderPostPage;
import static com.braintree.constants.BraintreeaddonWebConstants.PAYMENT_INFOS;
import static com.braintree.controllers.BraintreeaddonControllerConstants.CLIENT_TOKEN;
import static com.braintree.controllers.BraintreeaddonControllerConstants.Views.Pages.Checkout.CheckoutConfirmationPage;
import static org.apache.commons.lang.StringUtils.isNotBlank;


public class BrainTreeBeforeViewHandler implements BeforeViewHandlerAdaptee {

    private static final Logger LOG = Logger.getLogger(BrainTreeBeforeViewHandler.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    private static final String B2C_CHECKOUT_CONFIRMATION_PAGE = "pages/checkout/checkoutConfirmationPage";
    private static final String CART_PAGE = "pages/cart/cartPage";
    private static final String BRAIN_TREE_PAYMENT_DATA = "brainTreePaymentInfoData";
    private static final String ORDER_DATA = "orderData";
    private static final String PAYPAL_BUTTON_CONFIG = "payPalButtonConfig";
    private static final String GOOGLE_PAY_BUTTON_CONFIG = "googlePayButtonConfig";
    private static final String APPLE_PAY_BUTTON_CONFIG = "applePayButtonConfig";
    private static final String B2C_ADD_TO_CART_POPUP_PAGE = "fragments/cart/addToCartPopup";
    private static final String B2C_CART_POPUP_PAGE = "fragments/cart/cartPopup";
    private static final String ACCOUNT_LAYOUT_PAGE = "pages/account/accountLayoutPage";
    private static final String B2C_CHECKOUT_CONFIRMATION_LAYOUT_PAGE = "pages/checkout/checkoutConfirmationLayoutPage";
    private static final String B2C_CHECKOUT_DELIVERY_ADDRESS_PAGE = "pages/checkout/multi/addEditDeliveryAddressPage";
    private static final String LPM_IDS = "lpmids";
    private static final String USER_DATA = "userData";
    private static final String ARE_ALL_PAYMENTS_ON_LIMIT = "areAllPaymentsOnLimit";
    private static final String BRAINTREE_GENERAL_ERROR_SHIPPING_ADDRESS = "braintree.general.error.shippingAddress";
    private static final String SHOULD_SET_UP_PASSWORD_FORM_DISPLAYED = "shouldSetUpPasswordFormDisplayed";


    @Resource(name = "brainTreeCheckoutFacade")
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Resource(name = "brainTreePaymentFacadeImpl")
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Resource(name = "defaultBraintreeConfigurationFacade")
    private BraintreeConfigurationFacade brainTreeConfigFacade;
    @Resource(name = "brainTreeUserFacade")
    private DefaultBrainTreeUserFacade brainTreeUserFacade;
    @Resource(name = "brainTreeSetUpPasswordFacade")
    private DefaultBrainTreeSetUpPasswordFacade defaultBrainTreeSetUpPasswordFacade;
    @Resource(name = "brainTreeConfigService")
    private BrainTreeConfigService brainTreeConfigService;
    @Resource(name = "cmsComponentService")
    private CMSComponentService cmsComponentService;
    @Resource
    private BrainTreeStylesConfigurationFacade brainTreeStylesConfigurationFacade;

    private SessionService sessionService;
    private ConfigIntf.ConfigChangeListener configurationChangeListener;

    @Override
    public String beforeView(final HttpServletRequest request, final HttpServletResponse response, final ModelMap model,
                             final String viewName) throws Exception {

        if ((!B2C_CART_POPUP_PAGE.equals(viewName) && !B2C_ADD_TO_CART_POPUP_PAGE.equals(viewName))
                && !isPaymentMethodsAvailable(viewName)) {
            return viewName;
        }
        model.addAttribute(SHOULD_SET_UP_PASSWORD_FORM_DISPLAYED, defaultBrainTreeSetUpPasswordFacade.isSetUpPasswordForm());
        return handleBrainTreeCheckoutScenario(model, viewName);
    }

    private String handleBrainTreeCheckoutScenario(final ModelMap model, final String viewName)
            throws CMSItemNotFoundException, JsonProcessingException {

        if (configurationChangeListener == null) {
            registerConfigChangeLister();
        }

        model.addAttribute(BRAINTREE_CONFIG_DATA, brainTreeConfigFacade.getBraintreePluginConfigurationData());

        if (CART_PAGE.equals(viewName)) {
            fillPaymentMethodsInfo(model, BraintreePageType.CART);
            brainTreeStylesConfigurationFacade.getPayPalStyleConfiguration(BraintreePageType.CART)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(PAYPAL_BUTTON_CONFIG, jsonConfig));
            brainTreeStylesConfigurationFacade.getGooglePayStyleConfiguration(BraintreePageType.CART)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(GOOGLE_PAY_BUTTON_CONFIG, jsonConfig));
            brainTreeStylesConfigurationFacade.getApplePayStyleConfigurationFor(BraintreePageType.CART)
                    .ifPresent(config -> model.addAttribute(APPLE_PAY_BUTTON_CONFIG, config));
        } else if (BraintreeControllerConstants.Views.Pages.MultiStepCheckout.SilentOrderPostPage.equals(viewName)) {
            fillPaymentMethodsInfo(model, BraintreePageType.MARK);
            brainTreeStylesConfigurationFacade.getPayPalStyleConfiguration(BraintreePageType.MARK)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(PAYPAL_BUTTON_CONFIG, jsonConfig));
            brainTreeStylesConfigurationFacade.getGooglePayStyleConfiguration(BraintreePageType.MARK)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(GOOGLE_PAY_BUTTON_CONFIG, jsonConfig));
            brainTreeStylesConfigurationFacade.getApplePayStyleConfigurationFor(BraintreePageType.MARK)
                    .ifPresent(config -> model.addAttribute(APPLE_PAY_BUTTON_CONFIG, config));
            if (brainTreeConfigService.isLocalPaymentsEnabled()) {
                BraintreeUserData userData = brainTreeUserFacade.getUserData();
                model.addAttribute(USER_DATA, userData);
            }

            return SilentOrderPostPage;
        } else if (BraintreeControllerConstants.Views.Pages.MultiStepCheckout.AccountLayoutPage.equals(viewName)) {
            // add BRAIN_TREE_PAYMENT_DATA for order view page
            final OrderData orderData = (OrderData) model.get(ORDER_DATA);
            if (orderData != null && isNotBlank(orderData.getCode())) {
                model.addAttribute(BRAIN_TREE_PAYMENT_DATA,
                        brainTreePaymentFacade.getBrainTreePaymentInfoData(orderData.getCode()));
                return viewName;
            }

            if (!isPaymentMethodsAvailable(viewName)) {
                return viewName;
            }

            final BraintreeCheckoutData braintreeCheckoutData = brainTreeCheckoutFacade.getBraintreeCheckoutData(BraintreePageType.ACCOUNT);
            model.addAttribute(BRAINTREE_CHECKOUT_DATA, braintreeCheckoutData);
            model.addAttribute(PAYMENT_INFOS, brainTreeUserFacade.getBrainTreeCCPaymentInfos(true));
            brainTreeStylesConfigurationFacade.getPayPalStyleConfiguration(BraintreePageType.ACCOUNT)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(PAYPAL_BUTTON_CONFIG, jsonConfig));
            brainTreeStylesConfigurationFacade.getGooglePayStyleConfiguration(BraintreePageType.ACCOUNT)
                    .map(this::serializeToJson)
                    .ifPresent(jsonConfig -> model.addAttribute(GOOGLE_PAY_BUTTON_CONFIG, jsonConfig));
            model.addAttribute(ARE_ALL_PAYMENTS_ON_LIMIT, areAllPaymentsOnLimit(braintreeCheckoutData.getPaymentMethodsLimitation(), brainTreeConfigService.isDropInEnabled()));
            return ACCOUNT_LAYOUT_PAGE;
        } else if (B2C_CHECKOUT_CONFIRMATION_LAYOUT_PAGE.equals(viewName)) {
            final String orderCode = ((OrderData) model.get(ORDER_DATA)).getCode();
            if (isNotBlank(orderCode)) {
                model.addAttribute(BRAIN_TREE_PAYMENT_DATA,
                        brainTreePaymentFacade.getBrainTreePaymentInfoData(orderCode));
            }
            return viewName;
        } else if (BraintreeControllerConstants.Views.Pages.MultiStepCheckout.CheckoutSummaryPage.equals(viewName)) {
            model.addAttribute(BRAIN_TREE_PAYMENT_DATA, brainTreePaymentFacade.getBrainTreePaymentInfoData());
            return CheckoutSummaryPage;
        } else if (B2C_CHECKOUT_CONFIRMATION_PAGE.equals(viewName)) {
            final String orderCode = ((OrderData) model.get(ORDER_DATA)).getCode();
            if (isNotBlank(orderCode)) {
                model.addAttribute(BRAIN_TREE_PAYMENT_DATA,
                        brainTreePaymentFacade.getBrainTreePaymentInfoData(orderCode));
            }
            return CheckoutConfirmationPage;
        } else if (B2C_ADD_TO_CART_POPUP_PAGE.equals(viewName)) {
            if (isPaymentMethodsAvailable(viewName)) {
                fillPaymentMethodsInfo(model, BraintreePageType.MINICART);
                brainTreeStylesConfigurationFacade.getPayPalStyleConfiguration(BraintreePageType.MINICART)
                        .map(this::serializeToJson)
                        .ifPresent(jsonConfig -> model.addAttribute(PAYPAL_BUTTON_CONFIG, jsonConfig));
                brainTreeStylesConfigurationFacade.getGooglePayStyleConfiguration(BraintreePageType.MINICART)
                        .map(this::serializeToJson)
                        .ifPresent(jsonConfig -> model.addAttribute(GOOGLE_PAY_BUTTON_CONFIG, jsonConfig));
                brainTreeStylesConfigurationFacade.getApplePayStyleConfigurationFor(BraintreePageType.MINICART)
                        .ifPresent(config -> model.addAttribute(APPLE_PAY_BUTTON_CONFIG, config));
                return BraintreeaddonControllerConstants.Views.Fragments.Cart.AddToCartPopup;
            }
            return viewName;
        } else if (B2C_CART_POPUP_PAGE.equals(viewName)) {
            if (isPaymentMethodsAvailable(viewName)) {
                fillPaymentMethodsInfo(model, BraintreePageType.MINICART);
                brainTreeStylesConfigurationFacade.getPayPalStyleConfiguration(BraintreePageType.MINICART)
                        .map(this::serializeToJson)
                        .ifPresent(jsonConfig -> model.addAttribute(PAYPAL_BUTTON_CONFIG, jsonConfig));
                brainTreeStylesConfigurationFacade.getGooglePayStyleConfiguration(BraintreePageType.MINICART)
                        .map(this::serializeToJson)
                        .ifPresent(jsonConfig -> model.addAttribute(GOOGLE_PAY_BUTTON_CONFIG, jsonConfig));
                brainTreeStylesConfigurationFacade.getApplePayStyleConfigurationFor(BraintreePageType.MINICART)
                        .ifPresent(config -> model.addAttribute(APPLE_PAY_BUTTON_CONFIG, config));
                return BraintreeaddonControllerConstants.Views.Fragments.Cart.CartPopup;
            }
            return viewName;
        } else if (B2C_CHECKOUT_DELIVERY_ADDRESS_PAGE.equals(viewName)
                && getSessionService().getAttribute(BRAINTREE_GENERAL_ERROR_SHIPPING_ADDRESS) != null) {
            model.addAttribute("accErrorMsgs",
                    getSessionService().getAttribute(BRAINTREE_GENERAL_ERROR_SHIPPING_ADDRESS));
            getSessionService().removeAttribute(BRAINTREE_GENERAL_ERROR_SHIPPING_ADDRESS);
        } else if (BraintreeControllerConstants.Views.Pages.MultiStepCheckout.FallbackPage.equals(viewName)) {
            model.addAttribute(CLIENT_TOKEN, brainTreeCheckoutFacade.generateClientToken(Boolean.TRUE));
            return FallbackPage;
        }
        return viewName;
    }

    private void fillPaymentMethodsInfo(final ModelMap model, BraintreePageType pageType) {
        final BraintreeCheckoutData braintreeCheckoutData = brainTreeCheckoutFacade.getBraintreeCheckoutData(pageType);
        model.addAttribute(BRAINTREE_CHECKOUT_DATA, braintreeCheckoutData);
        List<CCPaymentInfoData> brainTreeCCPaymentInfos = brainTreeUserFacade.getBrainTreeCCPaymentInfos(true);
        model.addAttribute("payPalShouldBeSaved", !isPayPalAccountSaved(brainTreeCCPaymentInfos));
        model.addAttribute(PAYMENT_INFOS, brainTreeCCPaymentInfos);
        if (pageType.equals(BraintreePageType.MARK)) {
            List<String> lpmids = new ArrayList<>();
            for (BraintreeLocalPaymentMethodData lpm :
                    braintreeCheckoutData.getLocalPaymentMethod().getLocalPaymentMethods()) {
                lpmids.add(lpm.getCode());
            }
            try {
                model.addAttribute(LPM_IDS, objectMapper.writeValueAsString(lpmids));
            } catch (IOException e) {
                LOG.error("Error during write value", e);
            }
        }
    }

    private boolean isPayPalAccountSaved(List<CCPaymentInfoData> savedPayments) {
        for (CCPaymentInfoData payment : savedPayments) {
            if (BrainTreeUtils.isPayPalPayment(payment.getSubscriptionId())) {
                return true;
            }
        }
        return false;
    }


    private boolean areAllPaymentsOnLimit(PaymentMethodsLimitationData limitationData, boolean isDropInEnabled) {
    return limitationData.isPayPalOnLimit()
            && limitationData.isGooglePayOnLimit()
            && limitationData.isCreditCardOnLimit()
            && limitationData.isVenmoOnLimit()
            && limitationData.isSRCOnLimit()
            && !isDropInEnabled;
    }

    private String serializeToJson(Object object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            LOG.error("Error during serializing styles configuration object", e);
            return null;
        }
    }

    private boolean isPaymentMethodsAvailable(String viewName) {
        if (B2C_CART_POPUP_PAGE.equals(viewName) || B2C_ADD_TO_CART_POPUP_PAGE.equals(viewName)) {
            return isPaymentMethodsAvailableForCartPopup();
        } else if (ACCOUNT_LAYOUT_PAGE.equals(viewName)) {
            if (brainTreeConfigService.isDropInEnabled()) {
                return brainTreeConfigService.isPayPalEnabled() || brainTreeConfigService.isHostedFieldEnabled();
            } else {
                return isPaymentMethodsAvailableForAccountLayout();
            }
        } else {
            return brainTreeConfigService.isOneOfPaymentMethodsEnabled();
        }
    }

    private boolean isPaymentMethodsAvailableForAccountLayout() {
        return brainTreeConfigService.isHostedFieldEnabled() || brainTreeConfigService.isSrcEnabled()
                || isAvailableAccountPaymentMethods();
    }

    private boolean isAvailableAccountPaymentMethods() {
        return brainTreeConfigService.isPayPalEnabled() || brainTreeConfigService.isVenmoEnabled()
                || brainTreeConfigService.isGooglePayEnabled();
    }

    private boolean isPaymentMethodsAvailableForCartPopup() {
        return brainTreeConfigService.isPayPalExpressEnabled() || brainTreeConfigService.isApplePayOnExpressCheckoutEnabled()
                || brainTreeConfigService.isGooglePayOnExpressCheckoutEnabled() || brainTreeConfigService.isPayLaterEnabled();
    }

    private void registerConfigChangeLister() {
        final ConfigIntf config = Registry.getMasterTenant().getConfig();
        configurationChangeListener = new BrainTreeConfigurationListener();
        config.registerConfigChangeListener(configurationChangeListener);
    }

    public SessionService getSessionService() {
        return sessionService;
    }

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