package com.braintree.controllers.pages;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeControllerConstants;
import com.braintree.controllers.form.BraintreePlaceOrderForm;
import com.braintree.customfield.service.BraintreeCustomFieldsService;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import de.hybris.platform.acceleratorservices.enums.CheckoutPciOptionEnum;
import de.hybris.platform.acceleratorstorefrontcommons.annotations.PreValidateCheckoutStep;
import de.hybris.platform.acceleratorstorefrontcommons.annotations.RequireHardLogIn;
import de.hybris.platform.acceleratorstorefrontcommons.checkout.steps.CheckoutStep;
import de.hybris.platform.acceleratorstorefrontcommons.constants.WebConstants;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.checkout.steps.AbstractCheckoutStepController;
import de.hybris.platform.acceleratorstorefrontcommons.controllers.util.GlobalMessages;
import de.hybris.platform.acceleratorstorefrontcommons.forms.PlaceOrderForm;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.order.data.OrderEntryData;
import de.hybris.platform.commercefacades.product.ProductOption;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commerceservices.order.CommerceCartModificationException;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.util.localization.Localization;
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.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;


@Controller
@RequestMapping(value = "checkout/multi/summary/braintree")
public class BrainTreeSummaryCheckoutStepController extends AbstractCheckoutStepController {

    private static final Logger LOG = Logger.getLogger(BrainTreeSummaryCheckoutStepController.class);
    private static final String SUMMARY = "summary";
    private static final String REDIRECT_TO_CHECKOUT_ADD_PAYMENT_METHOD = "redirect:/checkout/multi/payment-method/add";

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

    @Resource(name = "customFieldsService")
    private BraintreeCustomFieldsService customFieldsService;

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

    @Resource(name = "brainTreeConfigService")
    private BrainTreeConfigService brainTreeConfigService;

    @RequestMapping(value = "/view", method = RequestMethod.GET)
    @RequireHardLogIn
    @Override
    @PreValidateCheckoutStep(checkoutStep = SUMMARY)
    public String enterStep(final Model model, final RedirectAttributes redirectAttributes)
        throws CMSItemNotFoundException, CommerceCartModificationException {
        final CartData cartData = getCheckoutFacade().getCheckoutCart();
        if (cartData.getEntries() != null && !cartData.getEntries().isEmpty()) {
            for (final OrderEntryData entry : cartData.getEntries()) {
                final String productCode = entry.getProduct().getCode();
                final ProductData product = getProductFacade().getProductForCodeAndOptions(productCode,
                    Arrays.asList(ProductOption.BASIC, ProductOption.PRICE));
                entry.setProduct(product);
            }
        }
        model.addAttribute("cartData", cartData);
        model.addAttribute("allItems", cartData.getEntries());
        model.addAttribute("deliveryAddress", cartData.getDeliveryAddress());
        model.addAttribute("deliveryMode", cartData.getDeliveryMode());
        model.addAttribute("paymentInfo", cartData.getPaymentInfo());

        model.addAttribute("shipsFromPostalCode", "");

        // Only request the security code if the SubscriptionPciOption is set to Default.
        final boolean requestSecurityCode = (CheckoutPciOptionEnum.DEFAULT
            .equals(getCheckoutFlowFacade().getSubscriptionPciOption()));
        model.addAttribute("requestSecurityCode", Boolean.valueOf(requestSecurityCode));

        final Map<String, String> defaultCustomFields = customFieldsService.getDefaultCustomFieldsMap();

        final BraintreePlaceOrderForm placeOrderForm = new BraintreePlaceOrderForm();
        placeOrderForm.setCustomFields(defaultCustomFields);
        model.addAttribute("placeOrderForm", placeOrderForm);

        storeCmsPageInModel(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        setUpMetaDataForContentPage(model, getContentPageForLabelOrId(MULTI_CHECKOUT_SUMMARY_CMS_PAGE_LABEL));
        model.addAttribute(WebConstants.BREADCRUMBS_KEY,
            getResourceBreadcrumbBuilder().getBreadcrumbs("checkout.multi.summary.breadcrumb"));
        model.addAttribute("metaRobots", "noindex,nofollow");
        setCheckoutStepLinksForModel(model, getCheckoutStep());
        return BraintreeControllerConstants.Views.Pages.MultiStepCheckout.CheckoutSummaryPage;
    }

    @RequestMapping(value = "/placeOrder")
    @RequireHardLogIn
    public String placeOrder(@ModelAttribute("placeOrderForm") final BraintreePlaceOrderForm placeOrderForm,
        final Model model,
        final HttpServletRequest request, final RedirectAttributes redirectModel)
        throws CMSItemNotFoundException, InvalidCartException, CommerceCartModificationException {
        if (validateOrderForm(placeOrderForm, model)) {
            return enterStep(model, redirectModel);
        }

        //Validate the cart
        if (validateCart(redirectModel)) {
            // Invalid cart. Bounce back to the cart page.
            return REDIRECT_PREFIX + "/cart";
        }

        final boolean isPaymentAuthorized = getCheckoutFacade().authorizePayment(placeOrderForm.getSecurityCode());

        if (!isPaymentAuthorized) {
            GlobalMessages.addFlashMessage(redirectModel, GlobalMessages.ERROR_MESSAGES_HOLDER,
                    Localization.getLocalizedString("checkout.error.authorization.failed"), null);
            return REDIRECT_TO_CHECKOUT_ADD_PAYMENT_METHOD;
        }

        final OrderData orderData;
        try {
            brainTreeCheckoutFacade.storeCustomFieldsToCart(getMergedCustomFields(placeOrderForm.getCustomFields()));
            brainTreeCheckoutFacade.storeShipsFromPostalCodeToCart(placeOrderForm.getShipsFromPostalCode());

            orderData = getCheckoutFacade().placeOrder();
            LOG.info("Order has been placed, number/code: " + orderData.getCode());
        } catch (final Exception e) {
            LOG.error("Failed to place Order, message: " + e.getMessage(), e);
            GlobalMessages.addErrorMessage(model, "checkout.placeOrder.failed");
            return enterStep(model, redirectModel);
        }

        return redirectToOrderConfirmationPage(orderData);
    }

    private Map<String, String> getMergedCustomFields(Map<String, String> customFieldsFromUI) {
        final Map<String, String> customFields = customFieldsService.getDefaultCustomFieldsMap();

        for (final Map.Entry<String, String> entry : customFieldsFromUI.entrySet()) {
            customFields.put(entry.getKey(), entry.getValue());
        }

        return customFields;
    }

    protected boolean validateOrderForm(final PlaceOrderForm placeOrderForm, final Model model) {
        final String securityCode = placeOrderForm.getSecurityCode();
        boolean invalid = false;

        if (getCheckoutFlowFacade().hasNoDeliveryAddress()) {
            GlobalMessages.addErrorMessage(model, "checkout.deliveryAddress.notSelected");
            invalid = true;
        }

        if (getCheckoutFlowFacade().hasNoDeliveryMode()) {
            GlobalMessages.addErrorMessage(model, "checkout.deliveryMethod.notSelected");
            invalid = true;
        }

        if (getCheckoutFlowFacade().hasNoPaymentInfo()) {
            GlobalMessages.addErrorMessage(model, "checkout.paymentMethod.notSelected");
            invalid = true;
        } else {
            // Only require the Security Code to be entered on the summary page if the SubscriptionPciOption is set to Default.
            if (CheckoutPciOptionEnum.DEFAULT.equals(getCheckoutFlowFacade().getSubscriptionPciOption())
                && StringUtils.isBlank(securityCode)) {
                GlobalMessages.addErrorMessage(model, "checkout.paymentMethod.noSecurityCode");
                invalid = true;
            }
        }

        if (!placeOrderForm.isTermsCheck()) {
            GlobalMessages.addErrorMessage(model, "checkout.error.terms.not.accepted");
            invalid = true;
            return invalid;
        }
        final CartData cartData = getCheckoutFacade().getCheckoutCart();

        if (!getCheckoutFacade().containsTaxValues()) {
            LOG.error(String.format(
                "Cart %s does not have any tax values, which means the tax cacluation was not properly done, placement of order can't continue",
                cartData.getCode()));
            GlobalMessages.addErrorMessage(model, "checkout.error.tax.missing");
            invalid = true;
        }

        if (!cartData.isCalculated()) {
            LOG.error(
                String.format("Cart %s has a calculated flag of FALSE, placement of order can't continue",
                    cartData.getCode()));
            GlobalMessages.addErrorMessage(model, "checkout.error.cart.notcalculated");
            invalid = true;
        }

        return invalid;
    }

    protected CheckoutStep getCheckoutStep() {
        return getCheckoutStep(SUMMARY);
    }

    @RequestMapping(value = "/back", method = RequestMethod.GET)
    @RequireHardLogIn
    @Override
    public String back(final RedirectAttributes redirectAttributes) {
        return getCheckoutStep().previousStep();
    }

    @RequestMapping(value = "/next", method = RequestMethod.GET)
    @RequireHardLogIn
    @Override
    public String next(final RedirectAttributes redirectAttributes) {
        return getCheckoutStep().nextStep();
    }

    public BraintreeCustomFieldsService getCustomFieldsService() {
        return customFieldsService;
    }

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

    public DefaultBrainTreeCheckoutFacade getBrainTreeCheckoutFacade() {
        return brainTreeCheckoutFacade;
    }

    public void setBrainTreeCheckoutFacade(DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade) {
        this.brainTreeCheckoutFacade = brainTreeCheckoutFacade;
    }

    public BrainTreeConfigService getBrainTreeConfigService() {
        return brainTreeConfigService;
    }

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