package com.braintree.controllers.pages;

import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_ACCEPTED_CREDIT_CARD_PAYMENT_METHODS;
import static com.braintree.constants.Braintreeb2baddonWebConstants.ACCEPTED_PAYMENTS_METHODS_IMAGES_URL;
import static com.braintree.constants.Braintreeb2baddonWebConstants.HOSTED_FIELDS_ENABLE;
import static com.braintree.constants.Braintreeb2baddonWebConstants.PAYMENT_INFOS;
import static com.braintree.constants.Braintreeb2baddonWebConstants.PAY_PAL_STANDARD_ENABLE;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.CLIENT_TOKEN;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.GENERAL_HEAD_ERROR;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.GENERAL_HEAD_ERROR_MESSAGE;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.IS_ADDRESS_OPEN;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.PAY_PAL_CHECKOUT_DATA;
import static com.braintree.controllers.Braintreeb2baddonControllerConstants.Views.Pages.MultiStepCheckout.CheckoutOrderPageErrorPage;
import static de.hybris.platform.util.localization.Localization.getLocalizedString;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.Braintreeb2baddonWebConstants;
import com.braintree.controllers.Braintreeb2baddonControllerConstants;
import com.braintree.enums.BraintreePageType;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.BraintreePaymentMethodNonceData;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.payment.validators.BrainTreePaymentDetailsValidator;
import com.braintree.util.BrainTreeUtils;
import com.braintree.util.GenericBuilder;
import com.google.common.collect.Lists;
import de.hybris.platform.acceleratorservices.payment.data.PaymentErrorField;
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.SopPaymentDetailsForm;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.cms2.model.pages.ContentPageModel;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.CardTypeData;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercefacades.user.data.CountryData;
import de.hybris.platform.commercefacades.user.data.RegionData;
import de.hybris.platform.commerceservices.order.CommerceCartModificationException;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
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.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


@Controller
@RequestMapping(value = "/braintree/checkout/hop")
public class BrainTreePaymentController extends AbstractCheckoutStepController {

    private static final Logger LOG = Logger.getLogger(BrainTreePaymentController.class);
    private static final String PAYMENT_METHOD = "payment-method";
    private static final String FAILED_VERIFICATION_ERROR_MESSAGE = "Payment method failed verification.";

    @Resource(name = "brainTreePaymentFacadeImpl")
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Resource(name = "brainTreeCheckoutFacade")
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Resource(name = "brainTreePaymentDetailsValidator")
    private BrainTreePaymentDetailsValidator brainTreePaymentDetailsValidator;
    @Resource(name = "brainTreeUserFacade")
    private BrainTreeUserFacade brainTreeUserFacade;
    @Resource(name = "brainTreeConfigService")
    private BrainTreeConfigService brainTreeConfigService;
    @Resource(name = "brainTreePaymentService")
    private BrainTreePaymentService brainTreePaymentService;

    @RequestMapping(value = "/response", method = RequestMethod.POST)
    @RequireHardLogIn
    public String enterStep(@RequestParam(value = "nonce") final String nonce,
        @RequestParam(value = "use_delivery_address") final String useBillingAddress,
        @RequestParam(value = "paypal_email") final String payPalEmail,
        @RequestParam(value = "deviceData") final String deviceData,
        @RequestParam(value = "cardholder", required = false) final String cardholder,
        @Valid final SopPaymentDetailsForm sopPaymentDetailsForm, final BindingResult bindingResult, final Model model,
        final RedirectAttributes redirectAttributes)
        throws CMSItemNotFoundException, CommerceCartModificationException {
        if (StringUtils.isEmpty(nonce)) {
            handleErrors(GENERAL_HEAD_ERROR_MESSAGE, model);
            return CheckoutOrderPageErrorPage;
        }

        List<PaymentErrorField> errorFields;
        AddressData addressData;
        if (Boolean.TRUE.toString().equals(useBillingAddress)) {
            addressData = brainTreeCheckoutFacade.getDeliveryAddressForCurrentCart();
            if (addressData == null) {
                GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.ERROR_MESSAGES_HOLDER, getLocalizedString("braintree.billing.address.error"));
                return REDIRECT_URL_ADD_PAYMENT_METHOD;
            }
        } else {
            addressData = interpretResponseAddressData(sopPaymentDetailsForm);
            errorFields = brainTreePaymentDetailsValidator.validatePaymentDetails(addressData, bindingResult);

            if (!errorFields.isEmpty()) {
                GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.ERROR_MESSAGES_HOLDER, getLocalizedString("braintree.billing.address.error"));
                return REDIRECT_URL_ADD_PAYMENT_METHOD;
            }
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setNonce, nonce)
            .with(BrainTreeSubscriptionInfoData::setCardholder, cardholder)
            .with(BrainTreeSubscriptionInfoData::setEmail, payPalEmail)
            .with(BrainTreeSubscriptionInfoData::setShouldBeSaved, sopPaymentDetailsForm.isSavePaymentInfo())
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();

        try {
            brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        } catch (final Exception exception) {
            LOG.error(exception.getMessage(), exception);
            String error = "braintree.billing.general.error";
            if (exception.getMessage().equals(FAILED_VERIFICATION_ERROR_MESSAGE)) {
                error = "text.account.profile.paymentCart.addPaymentMethod.failed.verification.error";
            }
            GlobalMessages.addFlashMessage(redirectAttributes, GlobalMessages.ERROR_MESSAGES_HOLDER,
                    getLocalizedString(error));
            return REDIRECT_URL_ADD_PAYMENT_METHOD;
        }

        return getCheckoutStep().nextStep();
    }

    @RequestMapping(value = "/getNewNonce", method = RequestMethod.GET)
    @RequireHardLogIn
    @ResponseBody
    public ResponseEntity<BraintreePaymentMethodNonceData> getNewPaymentMethodNonceByPaymentId (@RequestParam("paymentMethodId") final String paymentMethodId) {
        final CCPaymentInfoData ccPaymentInfoData = brainTreeUserFacade.getCCPaymentInfoForCode(paymentMethodId);
        final BraintreePaymentMethodNonceData paymentMethodNonce = brainTreePaymentFacade.createPaymentMethodNonce(ccPaymentInfoData);
        paymentMethodNonce.setBillingAddress(ccPaymentInfoData.getBillingAddress());
        return ResponseEntity.ok(paymentMethodNonce);
    }

    @RequestMapping(value = "/getAdditionalInfo", method = RequestMethod.GET)
    @RequireHardLogIn
    @ResponseBody
    public ResponseEntity<AddressData> getAdditionalInformationFor3DS () {
        return ResponseEntity.ok(brainTreeCheckoutFacade.getDeliveryAddressForCurrentCart());
    }

    private AddressData interpretResponseAddressData(final SopPaymentDetailsForm sopPaymentDetailsForm) {
        final AddressData address = new AddressData();
        final CountryData country = new CountryData();
        country.setIsocode(sopPaymentDetailsForm.getBillTo_country());
        address.setCountry(country);
        final RegionData region = new RegionData();
        region.setIsocode(sopPaymentDetailsForm.getBillTo_state());
        address.setTitleCode(sopPaymentDetailsForm.getBillTo_titleCode());
        address.setFirstName(sopPaymentDetailsForm.getBillTo_firstName());
        address.setLastName(sopPaymentDetailsForm.getBillTo_lastName());
        address.setTown(sopPaymentDetailsForm.getBillTo_city());
        address.setLine1(sopPaymentDetailsForm.getBillTo_street1());
        address.setLine2(sopPaymentDetailsForm.getBillTo_street2());
        address.setPostalCode(sopPaymentDetailsForm.getBillTo_postalCode());
        return address;
    }

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

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

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

    //replaced by customized method with multi args
    @Override
    public String enterStep(final Model model, final RedirectAttributes redirectAttributes)
        throws CMSItemNotFoundException,
        CommerceCartModificationException {
        return StringUtils.EMPTY;
    }

    @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();
    }

}
