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

import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS;
import static com.braintree.controllers.BraintreeaddonControllerConstants.PAY_PAL_ADDRESS_ERROR;
import static com.braintree.controllers.BraintreeaddonControllerConstants.PAY_PAL_GUEST_REGISTER_ERROR;
import static com.braintree.controllers.BraintreeaddonControllerConstants.PAY_PAL_HAED_ERROR;
import static com.braintree.controllers.BraintreeaddonControllerConstants.Views.Pages.MultiStepCheckout.CheckoutOrderPageErrorPage;
import static de.hybris.platform.util.localization.Localization.getLocalizedString;

import com.braintree.commands.impl.DefaultBraintreeErrorTranslator;
import com.braintree.constants.BraintreeaddonWebConstants;
import com.braintree.exceptions.BrainTreePaymentMethodLimitedException;
import com.braintree.controllers.handler.BraintreePayPalResponseExpressCheckoutHandler;
import com.braintree.controllers.handler.BraintreePayPalUserLoginHandler;
import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.enums.BraintreePageType;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.PayPalAddressData;
import com.braintree.hybris.data.BraintreeCheckoutData;
import com.braintree.hybris.data.PayPalDetails;
import com.braintree.hybris.data.PayPalExpressResponse;
import com.braintree.hybris.data.PayPalMiniCartResponse;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.security.BraintreePayPalGUIDCookieStrategy;
import com.braintree.util.BrainTreeUtils;
import com.braintree.util.GenericBuilder;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hybris.platform.acceleratorstorefrontcommons.annotations.RequireHardLogIn;
import de.hybris.platform.acceleratorstorefrontcommons.breadcrumb.ResourceBreadcrumbBuilder;
import de.hybris.platform.acceleratorstorefrontcommons.constants.WebConstants;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.AbstractCheckoutController;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.util.GlobalMessages;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.commercefacades.user.UserFacade;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.core.model.order.delivery.DeliveryModeModel;
import de.hybris.platform.order.CartService;
import de.hybris.platform.servicelayer.session.SessionService;
import java.io.IOException;
import java.util.Optional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


@Controller
@RequestMapping(value = "/braintree/paypal/checkout")
public class BraintreePayPalPaymentController extends AbstractCheckoutController {

    protected static final String REDIRECT_URL_CART = REDIRECT_PREFIX + BraintreeaddonWebConstants.CART_URL;
    private static final String REDIRECT_TO_PAYMENT_INFO_PAGE = REDIRECT_PREFIX + "/my-account/payment-details";
    private static final String ANONYMOUS_USER = "anonymous";
    private static final String SAVE_PAYMENT_INFO = "isSaved";
    private static final String DEVICE_DATA = "device_data";

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

    @Resource(name = "braintreePayPalUserLoginHandler")
    BraintreePayPalUserLoginHandler braintreePayPalUserLoginHandler;

    @Resource(name = "braintreePayPalResponseExpressCheckoutHandler")
    BraintreePayPalResponseExpressCheckoutHandler braintreePayPalResponseExpressCheckoutHandler;

    @Resource(name = "payPalGUIDCookieStrategy")
    private BraintreePayPalGUIDCookieStrategy payPalGUIDCookieStrategy;

    @Resource(name = "brainTreePaymentFacadeImpl")
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;

    @Resource(name = "userFacade")
    private UserFacade userFacade;

    @Resource(name = "brainTreeCheckoutFacade")
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;

    @Resource(name = "brainTreePaymentService")
    private BrainTreePaymentService brainTreePaymentService;
    @Resource(name = "multiStepCheckoutBreadcrumbBuilder")
    private ResourceBreadcrumbBuilder resourceBreadcrumbBuilder;

    @Resource
    private SessionService sessionService;

    @Resource
    private CartService cartService;

    @RequestMapping(value = "/express", method = RequestMethod.POST)
    public String doHandleHopResponse(final Model model,
        final HttpServletRequest request, final HttpServletResponse response) throws CMSItemNotFoundException {

        PayPalExpressResponse payPalExpressResponse;
        boolean isSavePaymentInfo;
        String deviceData;

        try {
            payPalExpressResponse = braintreePayPalResponseExpressCheckoutHandler.handlePayPalResponse(request);
            isSavePaymentInfo = Boolean.parseBoolean(request.getParameter(SAVE_PAYMENT_INFO));
            deviceData = request.getParameter(DEVICE_DATA);
        } catch (final IllegalArgumentException exception) {
            LOG.error(exception.getMessage(), exception);
            handleErrors(exception.getMessage(), model);
            return CheckoutOrderPageErrorPage;
        }

        if (ANONYMOUS_USER.equals(getSessionCartUserUid())) {
            if (StringUtils.isEmpty(payPalExpressResponse.getDetails().getEmail())) {
                LOG.error("Pal pal email is empty!");
                handleErrors(getLocalizedString(DefaultBraintreeErrorTranslator.DEFAULT_MESSAGE_KEY, new Object[]
                    {StringUtils.EMPTY}), model);
                return CheckoutOrderPageErrorPage;
            }
            try {

                final String email = payPalExpressResponse.getDetails().getEmail();
                final DeliveryModeModel selectedDeliveryMode = cartService.getSessionCart().getDeliveryMode();
                final String name = Optional.ofNullable(payPalExpressResponse.getDetails().getFirstName())
                    .orElse(BrainTreePaymentMethod.PAYPALACCOUNT.getCode());

                getCustomerFacade().createGuestUserForAnonymousCheckout(email, name);
                payPalGUIDCookieStrategy.setCookie(request, response);
                sessionService.setAttribute(WebConstants.ANONYMOUS_CHECKOUT_GUID,
                    StringUtils.substringBefore(getSessionCartUserUid(), "|"));
                cartService.getSessionCart().setDeliveryMode(selectedDeliveryMode);
                cartService.saveOrder(cartService.getSessionCart());

            } catch (final DuplicateUidException exception) {
                LOG.error("Guest registration failed: " + exception);
                handleErrors(getLocalizedString(PAY_PAL_GUEST_REGISTER_ERROR), model);
                return CheckoutOrderPageErrorPage;
            }
            braintreePayPalUserLoginHandler.isHardLogin(model);
        }

        final PayPalAddressData payPalShippingAddress = payPalExpressResponse.getDetails().getShippingAddress();
        AddressData hybrisShippingAddress = null;
        if (isPayPalAddressInsteadOfDeliveryAddress(payPalShippingAddress)) {
            hybrisShippingAddress = braintreePayPalResponseExpressCheckoutHandler
                .getPayPalAddress(payPalExpressResponse.getDetails(), payPalShippingAddress);
            hybrisShippingAddress.setShippingAddress(true);
            hybrisShippingAddress.setVisibleInAddressBook(true);
            userFacade.addAddress(hybrisShippingAddress);
            if (hybrisShippingAddress.isDefaultAddress()) {
                userFacade.setDefaultAddress(hybrisShippingAddress);
            }
            brainTreeCheckoutFacade.setDeliveryAddress(hybrisShippingAddress);
        } else if (isAddressFromPayPalIsEmpty()) {
            LOG.error("Shipping address from pay pal is empty!");
            final String errorMessage = getLocalizedString(PAY_PAL_ADDRESS_ERROR);
            handleErrors(errorMessage, model);
            return CheckoutOrderPageErrorPage;
        }

        final PayPalAddressData payPalBillingAddress = payPalExpressResponse.getDetails().getBillingAddress();
        AddressData hybrisBillingAddress = hybrisShippingAddress;
        if (payPalBillingAddress != null) {
            hybrisBillingAddress = braintreePayPalResponseExpressCheckoutHandler.getPayPalAddress(
                payPalExpressResponse.getDetails(), payPalBillingAddress);
            hybrisBillingAddress.setEmail(payPalExpressResponse.getDetails().getEmail());
        } else {
            LOG.info("No billing address provide by Pay Pal. Use billing address as shipping...");
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setEmail, payPalExpressResponse.getDetails().getEmail())
            .with(BrainTreeSubscriptionInfoData::setNonce, payPalExpressResponse.getNonce())
            .with(BrainTreeSubscriptionInfoData::setShouldBeSaved, isSavePaymentInfo)
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, hybrisBillingAddress)
            .build();

        if (cartService.getSessionCart().getDeliveryMode() == null) {
            brainTreeCheckoutFacade.setCheapestDeliveryModeForCheckout();
            LOG.info("Set default cheapest delivery mode for PayPal simple flow");
        }

        if (cartService.getSessionCart().getDeliveryMode() == null) {
            brainTreeCheckoutFacade.setDeliveryAddress(null);
            userFacade.removeAddress(hybrisShippingAddress);
        }

        try {
            brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        } catch (final Exception exception) {
            LOG.error(exception.getMessage(), exception);
            final String errorMessage = getLocalizedString("braintree.billing.general.error");
            handleErrors(errorMessage, model);
            return CheckoutOrderPageErrorPage;
        }
        return REDIRECT_PREFIX + "/checkout/multi/summary/braintree/view";

    }

    @RequestMapping(value = "/add-payment-method", method = RequestMethod.POST)
    public String addPaymentMethod(final Model model, final RedirectAttributes redirectAttributes,
        final HttpServletRequest request, final HttpServletResponse response) throws CMSItemNotFoundException {
        PayPalExpressResponse payPalExpressResponse;
        AddressData hybrisBillingAddress = null;

        try {
            payPalExpressResponse = braintreePayPalResponseExpressCheckoutHandler.handlePayPalResponse(request);
        } catch (final IllegalArgumentException exeption) {
            LOG.error(String.format("Illegal argument on PayPal request : %s", exeption.getMessage()), exeption);
            handleErrors(exeption.getMessage(), model);
            return CheckoutOrderPageErrorPage;
        }

        String payPalEmail = payPalExpressResponse.getDetails().getEmail();
        final String paymentProvider = Optional.ofNullable(payPalExpressResponse.getType())
            .orElse(BrainTreePaymentMethod.BRAINTREEPAYPALEXPRESS.getCode());

        if (BrainTreeUtils.isVenmoPayment(paymentProvider)) {
            payPalEmail = payPalExpressResponse.getDetails().getUsername();
        } else if (Optional.ofNullable(payPalExpressResponse.getDetails()).map(PayPalDetails::getBillingAddress)
            .isPresent()) {
            hybrisBillingAddress = braintreePayPalResponseExpressCheckoutHandler.getPayPalAddress(
                payPalExpressResponse.getDetails(), payPalExpressResponse.getDetails().getBillingAddress());
            hybrisBillingAddress.setEmail(payPalExpressResponse.getDetails().getEmail());
        } else {
            hybrisBillingAddress = new AddressData();
            hybrisBillingAddress.setEmail(payPalEmail);
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setPaymentProvider, paymentProvider)
            .with(BrainTreeSubscriptionInfoData::setEmail, payPalEmail)
            .with(BrainTreeSubscriptionInfoData::setNonce, payPalExpressResponse.getNonce())
            .with(BrainTreeSubscriptionInfoData::setSavePaymentInfo, true)
            .with(BrainTreeSubscriptionInfoData::setPaypalFundingSource, payPalExpressResponse.getFundingSource())
            .with(BrainTreeSubscriptionInfoData::setAddressData, hybrisBillingAddress)
            .build();

        try {
            brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } catch (BrainTreePaymentMethodLimitedException ex) {
            LOG.error("Failed to create payment method. The maximum number of payment methods is reached." + ex.getMessage());
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.ERROR_MESSAGES_HOLDER,
                    getLocalizedString("text.account.profile.paymentCart.addPaymentMethod.limitation.error"));
            return REDIRECT_TO_PAYMENT_INFO_PAGE;
        } catch (final Exception exception) {
            LOG.error(String.format("Braintree general billing error : %s", exception.getMessage()), exception);
            final String errorMessage = getLocalizedString("braintree.billing.general.error");
            handleErrors(errorMessage, model);
            return CheckoutOrderPageErrorPage;
        }
        GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.CONF_MESSAGES_HOLDER,
            getLocalizedString("text.account.profile.paymentCart.addPaymentMethod.success"));
        return REDIRECT_TO_PAYMENT_INFO_PAGE;
    }

    @RequestMapping(value = "/mini/express", method = RequestMethod.GET)
    @RequireHardLogIn
    @ResponseBody
    public String doInitializeMiniCartPaypalShortcut() throws CMSItemNotFoundException, JsonGenerationException,
        JsonMappingException, IOException {
        return buildPayPalMiniCartResponse();
    }

    private String buildPayPalMiniCartResponse() throws JsonMappingException, IOException {
        final ObjectMapper mapper = new ObjectMapper();
        final PayPalMiniCartResponse payPalMiniCartResponse = new PayPalMiniCartResponse();
        final BraintreeCheckoutData braintreeCheckoutData = brainTreeCheckoutFacade.generateConfigurationData(
            BraintreePageType.OTHER);
        payPalMiniCartResponse.setCheckoutData(braintreeCheckoutData);
        return mapper.writeValueAsString(payPalMiniCartResponse);
    }

    @ResponseBody
    @RequestMapping(value = "/shippingAddressError", method = RequestMethod.POST)
    public void handleShippingAddressError(final Model model,
        final @RequestParam(value = "errorMessage", required = false) String errorMessage)
        throws CMSItemNotFoundException {
        LOG.error("Not correct shipping address. Error message: " + errorMessage);
        GlobalMessages
            .addMessage(model, "accErrorMsgs", "braintree.general.error.shippingAddress", new String[]{errorMessage});
        getSessionService().getCurrentSession().setAttribute("braintree.general.error.shippingAddress",
            model.asMap().get("accErrorMsgs"));
    }

    private void handleErrors(final String errorsDetail, final Model model) throws CMSItemNotFoundException {
        model.addAttribute("errorsDetail", errorsDetail);
        model.addAttribute("redirectUrl", REDIRECT_URL_CART.replace(REDIRECT_PREFIX, ""));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
            getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.hostedOrderPageError.breadcrumb"));
        storeCmsPageInModel(model,
            getContentPageForLabelOrId(BraintreeaddonWebConstants.MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model,
            getContentPageForLabelOrId(BraintreeaddonWebConstants.MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));

        GlobalMessages.addErrorMessage(model, getLocalizedString(PAY_PAL_HAED_ERROR));
    }

    private boolean isPayPalShippingAddressAndItsFieldsNotNull(PayPalAddressData payPalShippingAddress) {
        return payPalShippingAddress != null
                && StringUtils.isNotBlank(payPalShippingAddress.getCountryCode())
                && StringUtils.isNotBlank(payPalShippingAddress.getLine1())
                && StringUtils.isNotBlank(payPalShippingAddress.getCity())
                && StringUtils.isNotBlank(payPalShippingAddress.getPostalCode());
    }

    protected ResourceBreadcrumbBuilder getResourceBreadcrumbBuilder() {
        return resourceBreadcrumbBuilder;
    }

    private String getSessionCartUserUid() {
        return cartService.getSessionCart().getUser().getUid();
    }

    private boolean isPayPalAddressInsteadOfDeliveryAddress(PayPalAddressData payPalShippingAddress){
        return isPayPalShippingAddressAndItsFieldsNotNull(payPalShippingAddress)
                && brainTreeCheckoutFacade.getCheckoutCart().getDeliveryAddress() == null;
    }

    private boolean isAddressFromPayPalIsEmpty(){
        return brainTreeCheckoutFacade.getCheckoutCart().getDeliveryAddress() == null
                && brainTreeCheckoutFacade.getCheckoutCart().getPickupOrderGroups().isEmpty();
    }
}
