package com.braintreeb2bocc.controllers;

import static com.braintree.constants.BraintreeConstants.PAYPAL_FUNDING_SOURCE;

import com.braintree.hybris.data.ws.PayPalCheckoutB2BWsDTO;
import com.braintree.facade.BraintreeUrlResolutionFacade;
import com.braintreeocc.controllers.BraintreeBaseController;
import com.braintreeocc.converters.BraintreePayPalAddressConverterForOCC;
import com.braintree.exceptions.PaypalAccessTokenNotFoundException;
import com.braintree.facade.BrainTreeConnectWithPayPalFacade;
import com.braintree.facade.BrainTreeRegistrationUserFacade;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.facade.BraintreeConnectWithPayPalNotificationFacade;
import com.braintree.facade.BraintreeB2BRegistrationUserFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserData;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserExistB2BData;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.BraintreeConnectWithPayPalConnectB2BRegisterData;
import com.braintree.hybris.data.ws.PayPalAddressWsDTO;
import com.braintree.hybris.data.ws.PayPalConnectB2BRegisterWsDTO;
import com.braintree.hybris.data.ws.PayPalConnectB2BWsDTO;
import com.braintree.hybris.data.ws.PayPalConnectTokenWsDTO;
import com.braintree.hybris.data.ws.PayPalDetailsWsDTO;
import com.braintree.hybris.data.ws.PayPalPaymentRequestWsDTO;
import com.braintree.model.PaypalAccessTokenModel;
import com.braintree.service.BrainTreeAccessTokenService;
import com.braintree.util.GenericBuilder;
import com.paypal.hybris.data.ws.AddingPaymentMethodAndAddressStatusWsDTO;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.site.BaseSiteService;
import de.hybris.platform.commerceservices.request.mapping.annotation.RequestMappingOverride;
import de.hybris.platform.webservicescommons.cache.CacheControl;
import de.hybris.platform.webservicescommons.cache.CacheControlDirective;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdAndUserIdParam;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.util.Optional;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.Validator;
import com.braintree.hybris.data.BraintreeCheckoutData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping(value = "/{baseSiteId}/users/{userId}/connect")
@CacheControl(directive = CacheControlDirective.NO_CACHE)
@Api(tags = "Loading Connect with PayPal button")
public class BraintreeB2BConnectWithPaypalController extends BraintreeBaseController {
    private static final String CUNNECT_RETURN_URL = "/connect/additional";

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

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

    @Resource(name = "brainTreeConnectWithPayPalFacade")
    private BrainTreeConnectWithPayPalFacade brainTreeConnectWithPayPalFacade;
    
    @Resource(name = "baseSiteService")
    private BaseSiteService baseSiteService;
    
    @Resource(name = "braintreeConnectWithPayPalNotificationFacade")
    private BraintreeConnectWithPayPalNotificationFacade braintreeConnectWithPayPalNotificationFacade;

    @Resource(name = "braintreeB2BRegistrationUserFacade")
    private BraintreeB2BRegistrationUserFacade braintreeB2BRegistrationUserFacade;

    @Resource(name = "brainTreeRegistrationUserFacade")
    private BrainTreeRegistrationUserFacade brainTreeRegistrationUserFacade;

    @Resource(name = "userFacade")
    private BrainTreeUserFacade brainTreeUserFacade;

    @Resource(name = "payPalPaymentRequestValidator")
    private Validator payPalPaymentRequestValidator;

    @Resource(name = "payPalDetailsValidator")
    private Validator payPalDetailsValidator;

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

    @Resource(name = "payPalOCCAddressConverter")
    private BraintreePayPalAddressConverterForOCC payPalOCCAddressConverter;
    @Resource
    private BrainTreeAccessTokenService braintreeAccessTokenService;
    @Resource(name = "braintreeUrlResolutionFacade")
    private BraintreeUrlResolutionFacade braintreeUrlResolutionFacade;

    @GetMapping("/paypal")
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    @ApiBaseSiteIdParam
    @ApiOperation(value = " Get the Braintree data for client-side payment initialization",
            notes = "Get a config data for Connect with PayPal button")
    @RequestMappingOverride
    public PayPalCheckoutB2BWsDTO getPayPalCheckoutData(
            @ApiParam(value = "Page type", defaultValue = "LOGIN")
            @RequestParam(defaultValue = "LOGIN", required = false) final String pageType) {
        final BraintreeCheckoutData braintreeCheckoutData = brainTreeCheckoutFacade.getBraintreeConfigurationDataForConnectWithPayPalButton();
        braintreeCheckoutData.getConfigurationData().setPayPalConnectReturnUrl(braintreeUrlResolutionFacade.buildReturnUrl(CUNNECT_RETURN_URL));
        return getDataMapper().map(braintreeCheckoutData, PayPalCheckoutB2BWsDTO.class, "configData");
    }

    @Secured({"ROLE_CLIENT", "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT",
        "ROLE_ANONYMOUS"})
    @PostMapping(value = "/exchange")
    @ResponseStatus(HttpStatus.OK)
    @ApiOperation(nickname = "exchange authorization code", value = "exchange authorization code to access token")
    @ApiBaseSiteIdAndUserIdParam
    @RequestMappingOverride
    public PayPalConnectB2BWsDTO exchangeAuthorizationCode(
            @ApiParam(value = "JSON object which contains authorization code", required = true)
        @RequestBody PayPalConnectTokenWsDTO payPalConnectTokenWsDTO) {
        final PayPalConnectB2BWsDTO connectWsData = new PayPalConnectB2BWsDTO();
        final String accessToken = brainTreeConnectWithPayPalFacade
            .exchangeAuthorizationCodeToAccessToken(payPalConnectTokenWsDTO.getAuthorizationCode());
        final BrainTreeConnectWithPayPalUserData userData = brainTreeConnectWithPayPalFacade.getUserDataByAccessToken(accessToken);
        final String email = brainTreeRegistrationUserFacade.getEmailFromPayPalUserData(userData);
        final BrainTreeConnectWithPayPalUserExistB2BData existB2BData = braintreeB2BRegistrationUserFacade.isCustomerExistAndApprovedByUID(email);
        if(existB2BData.isExist()){
            connectWsData.setShouldSaveAddress(!brainTreeConnectWithPayPalFacade.isPayPalAddressPresent(email, userData.getAddress()));
            connectWsData.setShouldSavePaymentInfo(!brainTreeConnectWithPayPalFacade.isPayPalPaymentMethodPresentForConnectWithPayPal(email));
            connectWsData.setEmail(email);
            connectWsData.setBaseSiteName(baseSiteService.getCurrentBaseSite().getName());
        }else {
            final boolean shutSavePaymentInfo = brainTreeConnectWithPayPalFacade.shouldSavePayPalPaymentInfo();
            connectWsData.setShouldSaveAddress(true);
            connectWsData.setShouldSavePaymentInfo(shutSavePaymentInfo);
        }

        boolean isLoginFlowAndUserApproved = existB2BData.isApproved() && existB2BData.isExist();
        boolean isRegistrationFlow = !existB2BData.isExist();
        if (isLoginFlowAndUserApproved || isRegistrationFlow) {
            String accessTokenGuid = braintreeAccessTokenService.savePaypalAccessToken(accessToken);
            connectWsData.setAccessTokenGuid(accessTokenGuid);
        }

        connectWsData.setIsUserAlreadyConnectPayPalAccount(brainTreeUserFacade.isCustomerWithPayerIdExist(userData.getPayerId()));
        connectWsData.setPayerId(userData.getPayerId());
        connectWsData.setIsRegistered(existB2BData.isExist());
        connectWsData.setIsApproved(existB2BData.isApproved());
        return connectWsData;
    }

    @Secured({"ROLE_CLIENT", "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT",
        "ROLE_ANONYMOUS"})
    @PostMapping(value = "/register")
    @ResponseStatus(HttpStatus.OK)
    @ApiOperation(nickname = "register endpoint for connect with PayPal", value = "register connect with paypal user")
    @ApiBaseSiteIdAndUserIdParam
    @RequestMappingOverride
    public ResponseEntity<String> doRegister(
        @ApiParam(value = "Registration data", required = true) @RequestBody final PayPalConnectB2BRegisterWsDTO registerData)
        throws DuplicateUidException {
        String accessToken = braintreeAccessTokenService.getPaypalAccessToken(registerData.getAccessTokenGuid())
                .map(PaypalAccessTokenModel::getAccessToken)
                .orElseThrow(PaypalAccessTokenNotFoundException::new);
        final BrainTreeConnectWithPayPalUserData userData = brainTreeConnectWithPayPalFacade
            .getUserDataByAccessToken(accessToken);
        final BraintreeConnectWithPayPalConnectB2BRegisterData data =
            getDataMapper().map(registerData, BraintreeConnectWithPayPalConnectB2BRegisterData.class);
        data.setEmail(brainTreeRegistrationUserFacade.getEmailFromPayPalUserData(userData));
        data.setName(userData.getName());

        String payerId = braintreeB2BRegistrationUserFacade.registerB2BPayPalUser(data);
        return new ResponseEntity<>(payerId, HttpStatus.OK);
    }

    @Secured({"ROLE_CLIENT", "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT",
        "ROLE_ANONYMOUS"})
    @PostMapping(value = "/createPaymentDetails")
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(nickname = "addPayPalPayment", value = "Save new PayPal payment info", notes = "Adding PayPal payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    @RequestMappingOverride
    public AddingPaymentMethodAndAddressStatusWsDTO addPayPalPaymentAndAddressForNewUser(
        @ApiParam(value = "Request body parameter that contains" +
            "payPalPaymentResponse(payment method nonce (nonce), payment type (type), email, " +
            "countryCode, payerId, phone, firstName, lastName " +
            "and the billing address (First line of the address, " +
            "billingAddress.lastName, billingAddress.titleCode, billingAddress.country.isocode, " +
            "\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final PayPalPaymentRequestWsDTO payPalRequest,
        @ApiParam(value = "Data collected from client.") @RequestParam(required = false) final String deviceData,
        @ApiParam(value = "Connect with PP access token guid.") @RequestParam final String accessTokenGuid,
        @ApiParam(value = "Funding source. Detect type of paypal payment")
        @RequestParam(defaultValue = PAYPAL_FUNDING_SOURCE) final String fundingSource,
        @RequestParam final boolean shouldSaveAddressInfo,
        @RequestParam final boolean shouldSavePaymentInfo) {
        AddingPaymentMethodAndAddressStatusWsDTO result = new AddingPaymentMethodAndAddressStatusWsDTO();
        String accessToken = braintreeAccessTokenService.getPaypalAccessToken(accessTokenGuid)
                .map(PaypalAccessTokenModel::getAccessToken)
                .orElseThrow(PaypalAccessTokenNotFoundException::new);
        final BrainTreeConnectWithPayPalUserData userData = brainTreeConnectWithPayPalFacade.getUserDataByAccessToken(
                accessToken);
        final String email = brainTreeRegistrationUserFacade.getEmailFromPayPalUserData(userData);
        result.setIsAddressAdded(false);
        result.setIsPaymentMethodAdded(false);

        if (shouldSavePaymentInfo) {
            validate(payPalRequest, "payPalRequest", payPalPaymentRequestValidator);
            final String payPalEmail = payPalRequest.getDetails().getEmail();
            final PayPalDetailsWsDTO brainTreeDetails = payPalRequest.getDetails();
            final AddressData addressData = resolveAddress(brainTreeDetails);

            validate(brainTreeDetails, "brainTreeDetails", payPalDetailsValidator);

            final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
                .with(BrainTreeSubscriptionInfoData::setPaymentProvider, payPalRequest.getType())
                .with(BrainTreeSubscriptionInfoData::setEmail, payPalEmail)
                .with(BrainTreeSubscriptionInfoData::setNonce, payPalRequest.getNonce())
                .with(BrainTreeSubscriptionInfoData::setSavePaymentInfo, brainTreeConnectWithPayPalFacade.shouldSavePayPalPaymentInfo())
                .with(BrainTreeSubscriptionInfoData::setPaypalFundingSource, fundingSource)
                .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
                .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
                .build();
            try {
                brainTreePaymentFacade.createPaymentInfoForLogin(paymentInfoData, email);
                result.setIsPaymentMethodAdded(true);
            } catch (Exception ex) {
                LOG.error("Error during adding payment method for new customer", ex);
                result.setErrorMessagePaymentMethod(ex.getMessage());
            }
        }

        if (shouldSaveAddressInfo && userData.getAddress() != null) {
            try {
                brainTreeConnectWithPayPalFacade.addAddressForNewUser(userData.getAddress(), email);
                result.setIsAddressAdded(true);
            } catch (Exception ex) {
                LOG.error("Error during adding address for new customer", ex);
                result.setErrorMessageAddress(ex.getMessage());
            }
        }

        if (!braintreeB2BRegistrationUserFacade.isCustomerExistAndApprovedByUID(email).isApproved()) {
            braintreeAccessTokenService.removePaypalAccessToken(accessTokenGuid);
        }

        return result;
    }

    private AddressData resolveAddress(PayPalDetailsWsDTO brainTreeDetails) {
        final PayPalAddressWsDTO billingAddress = brainTreeDetails.getBillingAddress();

        AddressData addressData = Optional.ofNullable(billingAddress)
            .map(payPalOCCAddressConverter::convert)
            .or(() -> Optional.ofNullable(payPalOCCAddressConverter.convert(brainTreeDetails.getShippingAddress())))
            .get();

        addressData.setEmail(brainTreeDetails.getEmail());

        if (StringUtils.isBlank(brainTreeDetails.getFirstName()) && StringUtils.isNotBlank(
            addressData.getFirstName())) {
            brainTreeDetails.setFirstName(addressData.getFirstName());
            brainTreeDetails.setLastName(addressData.getLastName());
            brainTreeDetails.setCountryCode(addressData.getPostalCode());
        }

        if (StringUtils.isBlank(addressData.getFirstName())) {
            addressData.setFirstName(brainTreeDetails.getFirstName());
            addressData.setLastName(brainTreeDetails.getLastName());
            addressData.setPhone(brainTreeDetails.getPhone());
        }
        return addressData;
    }
}

