package com.braintreeocc.controllers;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.enums.BraintreePageType;
import com.braintree.exceptions.BrainTreeCardVerifyException;
import com.braintree.exceptions.BrainTreeDeliveryAddressNotFoundException;
import com.braintree.exceptions.BrainTreePaymentMethodLimitedException;
import com.braintree.exceptions.BraintreeStoreInVaultException;
import com.braintree.exceptions.InvalidPaymentInfoException;
import com.braintree.exceptions.BraintreeAddPaymentMethodException;
import com.braintree.exceptions.BraintreeBillingAddressException;
import com.braintree.facade.BrainTreeUserFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.facade.impl.DefaultBrainTreeUserFacade;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.BraintreePaymentMethodNonceData;
import com.braintree.hybris.data.ws.ApplePayAddressWsDTO;
import com.braintree.hybris.data.ws.ApplePayPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.BraintreeCreditCardPaymentDetailsWsDTO;
import com.braintree.hybris.data.ws.BraintreeTokenizedCreditCardWsDTO;
import com.braintree.hybris.data.ws.GooglePayAddressWsDTO;
import com.braintree.hybris.data.ws.GooglePayPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.LocalPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.PayPalAddressWsDTO;
import com.braintree.hybris.data.ws.PayPalDetailsWsDTO;
import com.braintree.hybris.data.ws.PayPalPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.SrcAddressWsDTO;
import com.braintree.hybris.data.ws.SrcPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.VenmoPaymentRequestWsDTO;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.transaction.service.impl.DefaultBrainTreeTransactionService;
import com.braintree.util.GenericBuilder;
import com.braintreeocc.converters.BraintreePayPalAddressConverterForOCC;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoDatas;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercewebservicescommons.dto.order.OrderWsDTO;
import de.hybris.platform.commercewebservicescommons.dto.order.PaymentDetailsListWsDTO;
import de.hybris.platform.commercewebservicescommons.dto.order.PaymentDetailsWsDTO;
import de.hybris.platform.commercewebservicescommons.dto.user.AddressWsDTO;
import de.hybris.platform.commercewebservicescommons.errors.exceptions.PaymentAuthorizationException;
import de.hybris.platform.commercewebservicescommons.errors.exceptions.RequestParameterException;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import de.hybris.platform.webservicescommons.cache.CacheControl;
import de.hybris.platform.webservicescommons.cache.CacheControlDirective;
import de.hybris.platform.webservicescommons.errors.exceptions.NotFoundException;
import de.hybris.platform.webservicescommons.errors.exceptions.WebserviceValidationException;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdAndUserIdParam;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdUserIdAndCartIdParam;
import de.hybris.platform.webservicescommons.swagger.ApiFieldsParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;

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


@RestController
@RequestMapping(value = "/{baseSiteId}/users/{userId}")
@CacheControl(directive = CacheControlDirective.NO_CACHE)
@Tag(name = "Braintree Payment Details")
public class BraintreeCreatePaymentDetailsController extends BraintreeBaseCommerceController {
    private static final Logger LOG = Logger.getLogger(BraintreeCreatePaymentDetailsController.class);
    private static final String ADDRESS_MAPPING = "firstName,lastName,titleCode,phone,cellphone,line1,line2,town,postalCode,region(isocode),district,country(isocode),defaultAddress";
    private static final String ADDRESS_DOES_NOT_EXIST = "Address with given id: '%s' doesn't exist or belong to another user";
    private static final String DELIVERY_ADDRESS_NOT_SET = "Delivery address is not set ";
    private static final String OBJECT_NAME_SELECTED_ADDRESS_ID = "selectedAddressCode";

    @Resource(name = "brainTreePaymentFacadeImpl")
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Resource(name = "brainTreeUserFacade")
    private DefaultBrainTreeUserFacade brainTreeUserFacade;
    @Resource(name = "brainTreeCheckoutFacade")
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Resource(name = "brainTreeConfigService")
    private BrainTreeConfigService brainTreeConfigService;
    @Resource(name = "brainTreePaymentService")
    private BrainTreePaymentService brainTreePaymentService;
    @Resource(name = "payPalPaymentRequestValidator")
    private Validator payPalPaymentRequestValidator;
    @Resource(name = "payPalDetailsValidator")
    private Validator payPalDetailsValidator;
    @Resource(name = "googlePayPaymentRequestValidator")
    private Validator googlePayPaymentRequestValidator;
    @Resource(name = "srcPaymentRequestValidator")
    private Validator srcPaymentRequestValidator;
    @Resource(name = "applePayPaymentRequestValidator")
    private Validator applePayPaymentRequestValidator;
    @Resource(name = "creditCardDataValidator")
    private Validator creditCardDataValidator;
    @Resource(name = "venmoRequestValidator")
    private Validator venmoRequestValidator;
    @Resource(name = "localPaymentRequestValidator")
    private Validator localPaymentRequestValidator;
    @Resource(name = "payPalOCCAddressConverter")
    private BraintreePayPalAddressConverterForOCC payPalOCCAddressConverter;
    @Resource(name = "googlePayAddressConverter")
    private Converter<GooglePayAddressWsDTO, AddressData> googlePayAddressConverter;
    @Resource(name = "srcAddressConverter")
    private Converter<SrcAddressWsDTO, AddressData> srcAddressConverter;
    @Resource(name = "applePayAddressConverter")
    private Converter<ApplePayAddressWsDTO, AddressData> applePayAddressConverter;
    @Resource(name = "userFacade")
    protected BrainTreeUserFacade userFacade;
    @Resource(name = "brainTreeTransactionService")
    private DefaultBrainTreeTransactionService brainTreeTransactionService;


    @Secured({"ROLE_CLIENT", "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT",
        "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/payPal")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "addPayPalPayment", summary = "Save new PayPal payment info", description = "Adding PayPal payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addPayPalPayment(@Parameter(description = "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,
        //NOSONAR
        @Parameter(description = "Defines if payment info goes through credit flow") @RequestParam(defaultValue = "false") boolean credit,
        @Parameter(description = "Defines if payment info should be saved") @RequestParam(defaultValue = "false") boolean shouldBeSaved,
        @Parameter(description = "Data collected from client.") @RequestParam(required = false) final String deviceData,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.",
            schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"})) @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
        @Parameter(description = "Funding source. Detect type of paypal payment")
        @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType) {
        validate(payPalRequest, "payPalRequest", payPalPaymentRequestValidator);
        validateBraintreePaymentDetailsParam(shouldBeSaved, credit);

        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::setEmail, payPalEmail)
            .with(BrainTreeSubscriptionInfoData::setNonce, payPalRequest.getNonce())
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);

        CCPaymentInfoData createdPaymentInfo;
        if(BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } else {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        }

        return getDataMapper().map(createdPaymentInfo, PaymentDetailsWsDTO.class, fields);
    }

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

        AddressData addressData = Optional.ofNullable(billingAddress)
            .map(payPalOCCAddressConverter::convert)
            .or(() -> Optional.ofNullable(brainTreeCheckoutFacade.getCheckoutCart().getDeliveryAddress()))
            .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;
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/googlePay")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "addGooglePayPaymentInfo", summary = "Save new GooglePay payment info",
        description = "Adding GooglePay payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addGooglePayPaymentInfo(
        @Parameter(description = "Request body parameter that contains details such as  " +
            "payment method nonce (nonce), payment type (type), email, and the billing address " +
            "\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final GooglePayPaymentRequestWsDTO googlePayRequest,
//NOSONAR
        @Parameter(description = "Defines if payment info should be saved") @RequestParam(defaultValue = "false") boolean shouldBeSaved,
        @Parameter(description = "Data collected from client.") @RequestParam(required = false) final String deviceData,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.",
            schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"})) @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
        @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType) {
        validate(googlePayRequest, "googlePayRequest", googlePayPaymentRequestValidator);
        validateBraintreePaymentDetailsParam(shouldBeSaved, Boolean.FALSE);

        final AddressData addressData = googlePayAddressConverter.convert(googlePayRequest.getBillingAddress());
        if (addressData != null) {
            addressData.setEmail(googlePayRequest.getEmail());
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setEmail, googlePayRequest.getEmail())
            .with(BrainTreeSubscriptionInfoData::setNonce, googlePayRequest.getNonce())
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);

        CCPaymentInfoData createdPaymentInfo;

        if(BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } else {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        }

        return getDataMapper().map(createdPaymentInfo, PaymentDetailsWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/srcPayment")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "addSrcPaymentInfo", summary = "Save new secure remote commerce payment info",
        description = "Adding secure remote commerce payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addSrcPaymentInfo(
        @Parameter(description = "Request body parameter that contains details such as  " +
            "payment method nonce (nonce), payment type (type), email, and the billing address " +
            "\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final SrcPaymentRequestWsDTO srcRequest,
        @Parameter(description = "Defines if payment info should be saved") @RequestParam(defaultValue = "false") boolean shouldBeSaved,
        @Parameter(description = "Data collected from client.") @RequestParam(required = false) final String deviceData,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.",
            schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"})) @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
        @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType) {
        validate(srcRequest, "srcRequest", srcPaymentRequestValidator);
        validateBraintreePaymentDetailsParam(shouldBeSaved, Boolean.FALSE);

        final AddressData addressData = srcAddressConverter.convert(srcRequest.getBillingAddress());
        if (addressData != null) {
            addressData.setEmail(srcRequest.getUserEmail());
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setEmail, srcRequest.getUserEmail())
            .with(BrainTreeSubscriptionInfoData::setNonce, srcRequest.getNonce())
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);

        CCPaymentInfoData createdPaymentInfo;

        if(BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } else {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        }

        return getDataMapper().map(createdPaymentInfo, PaymentDetailsWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/creditCard")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "createCardPaymentInfo", summary = "Save new credit card payment .",
        description = "Defines the details of a new credit card payment option and add into customer wallet.")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addCreditCardPaymentInfo(
        @Parameter(description = "Defines if payment info should be saved") @RequestParam(defaultValue = "false") boolean shouldBeSaved,
        @Parameter(description = "Billing address code. The address code may be empty if you provide a billing address in request") @RequestParam(value = "selectedAddressCode") final String selectedAddressCode,
        @RequestParam(required = false) String deviceData,
        @Parameter(description =
            "Request body parameter that contains details such as the name on the card (accountHolderName), the card number (cardNumber), the card type (cardType.code), "
                + "the month of the expiry date (expiryMonth), the year of the expiry date (expiryYear), whether the payment details should be saved (saved), whether the payment details "
                + "should be set as default (defaultPaymentInfo), and the billing address (billingAddress.firstName, billingAddress.lastName, billingAddress.titleCode, billingAddress.country.isocode, "
                + "billingAddress.line1, billingAddress.line2, billingAddress.town, billingAddress.postalCode, billingAddress.region.isocode)\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final BraintreeCreditCardPaymentDetailsWsDTO paymentDetails,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.", schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"}))
        @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
        @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType)
        throws InvalidPaymentInfoException {
        final String cardHolderName = paymentDetails.getTokenizedCardData().getDetails().getCardholderName();
        validateBraintreePaymentDetailsParam(shouldBeSaved, Boolean.FALSE);
        if (StringUtils.isBlank(cardHolderName)) {
            throw new InvalidPaymentInfoException(paymentDetails.getId());
        }
        validate(paymentDetails.getTokenizedCardData(), "tokenizedCardData", creditCardDataValidator);

        final AddressData addressData;
        if (StringUtils.isNotEmpty(selectedAddressCode)) {
            addressData = userFacade.getAddressForCode(selectedAddressCode);
        } else {
            addressData = getDataMapper().map(paymentDetails.getBillingAddress(), AddressData.class, ADDRESS_MAPPING);
        }
        if (addressData == null && !isBillingAddressOptional(pageType)) {
            throw new BraintreeBillingAddressException();
        } else if (addressData != null) {
            addressData.setBillingAddress(true);
        }

        final BraintreeTokenizedCreditCardWsDTO cardData = paymentDetails.getTokenizedCardData();
        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setNonce, cardData.getNonce())
            .with(BrainTreeSubscriptionInfoData::setCardholder, cardHolderName)
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);
        CCPaymentInfoData createdPaymentInfo;
        try {
            if(BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
                createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
            } else {
                createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
            }
        } catch (BrainTreeCardVerifyException | BrainTreePaymentMethodLimitedException e) {
            throw e;
        } catch (Exception generalException){
            LOG.error("The general exception was happened " + generalException.getMessage());
            throw new BraintreeAddPaymentMethodException();
        }

        return getDataMapper().map(createdPaymentInfo, PaymentDetailsWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/venmo")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "createVenmoPaymentDetails", summary = "Save new Venmo payment info.", description = "Adding Venmo payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO createVenmoPaymentInfo(
        @Parameter(description = "Data collected from client.") @RequestParam(required = false) String deviceData,
        @Parameter(description = "Defines if payment info should be saved") @RequestParam(defaultValue = "false") boolean shouldBeSaved,
        @Parameter(description = "Code of selected address") @RequestParam(value = "selectedAddressCode") final String selectedAddressCode,
        @Parameter(description =
            "Request body parameter that contains details such as payment method nonce (nonce), payment type (type)\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final VenmoPaymentRequestWsDTO venmoRequest,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.", schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"}))
        @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
        @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType) {
        validate(venmoRequest, "venmoRequest", venmoRequestValidator);
        validateBraintreePaymentDetailsParam(shouldBeSaved, Boolean.FALSE);
        AddressData addressData = null;
        if (StringUtils.isNotEmpty(selectedAddressCode)) {
            addressData = userFacade.getAddressForCode(selectedAddressCode);
            if (addressData == null) {
                throw new BraintreeBillingAddressException();
            }
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setNonce, venmoRequest.getNonce())
            .with(BrainTreeSubscriptionInfoData::setEmail, venmoRequest.getUsername())
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);

        CCPaymentInfoData createdPaymentInfo;

        if(BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } else {
            createdPaymentInfo = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        }

        return getDataMapper().map(createdPaymentInfo, PaymentDetailsWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS"})
    @PostMapping(value = "/braintree/paymentInfo/applePay")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "addApplePayPaymentInfo", summary = "Save new ApplePay payment info",
        description = "Adding ApplePay payment info into customer wallet")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addApplePayPaymentInfo(
        @Parameter(description = "Request body parameter that contains details such as  " +
            "payment method nonce (nonce), payment type (type), email, and the billing address " +
            "\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final ApplePayPaymentRequestWsDTO applePayRequest,
//NOSONAR
        @Parameter(description = "Data collected from client.") @RequestParam(required = false) final String deviceData,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.",
            schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"})) @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields) {
        validate(applePayRequest, "applePayRequest", applePayPaymentRequestValidator);

        final AddressData addressData = applePayAddressConverter.convert(applePayRequest.getBillingContact());
        if (addressData != null) {
            addressData.setEmail(applePayRequest.getEmail());
        }

        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
            .with(BrainTreeSubscriptionInfoData::setNonce, applePayRequest.getNonce())
            .with(BrainTreeSubscriptionInfoData::setEmail, applePayRequest.getEmail())
            .with(BrainTreeSubscriptionInfoData::setSavePaymentInfo, false)
            .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
            .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
            .build();

        return getDataMapper()
            .map(brainTreePaymentFacade.completeCreateSubscription(paymentInfoData),
                PaymentDetailsWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT"})
    @PostMapping(value = "/carts/{cartId}/braintree/paymentInfo/localPayment")
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(operationId = "createLocalPaymentPaymentDetails", summary = "Save new LocalPayment info.", description = "Adding LocalPayment payment info into customer wallet")
    @ApiBaseSiteIdUserIdAndCartIdParam
    public OrderWsDTO processLocalPayment(
        @Parameter(description =
            "Request body parameter that contains details such as payment method nonce (nonce), correlationId, paymentId and local payment details\n\nThe DTO is in XML or .json format.", required = true) @RequestBody final LocalPaymentRequestWsDTO localPaymentRequest,
        @Parameter(description = "Response configuration. This is the list of fields that should be returned in the response body.", schema = @Schema(allowableValues = {"BASIC", "DEFAULT", "FULL"}))
        @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields)
        throws PaymentAuthorizationException, InvalidCartException {
        validate(localPaymentRequest, "localPaymentRequest", localPaymentRequestValidator);

        final String paymentMethodNonce = localPaymentRequest.getNonce();
        if (brainTreePaymentFacade.getOrderByPaymentMethodNonce(paymentMethodNonce) != null) {
            return getDataMapper()
                .map(brainTreePaymentFacade.getOrderByPaymentMethodNonce(paymentMethodNonce), OrderWsDTO.class, fields);
        }

        try {
            brainTreePaymentFacade.updateLocalPaymentMethodSubscription(localPaymentRequest.getNonce(),
                localPaymentRequest.getCorrelationId());
        } catch (BrainTreeDeliveryAddressNotFoundException e) {
            LOG.error(DELIVERY_ADDRESS_NOT_SET, e);
            throw new BraintreeBillingAddressException();
        }

        final boolean isPaymentAuthorized = brainTreeCheckoutFacade.authorizePayment(StringUtils.EMPTY);
        if (!isPaymentAuthorized) {
            throw new PaymentAuthorizationException();
        }
        final OrderData orderData = getCheckoutFacade().placeOrder();
        LOG.info("Order has been placed, number/code: " + orderData.getCode());
        return getDataMapper().map(orderData, OrderWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @GetMapping("/braintree")
    @Operation(operationId = "getPaymentDetailsList", summary = "Get customer's Braintree credit card payment details list.", description = "Return customer's Braintree credit card payment details list.")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsListWsDTO getBraintreePaymentDetailsList(
        @Parameter(description = "Type of payment details.") @RequestParam(defaultValue = "false") final boolean saved,
        @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields) {
        LOG.debug("getBraintreePaymentDetailsList");
        final CCPaymentInfoDatas paymentInfoDataList = new CCPaymentInfoDatas();

        final List<CCPaymentInfoData> brainTreeCCPaymentInfos = brainTreeUserFacade.getBrainTreeCCPaymentInfos(saved);

        paymentInfoDataList.setPaymentInfos(brainTreeCCPaymentInfos);

        return getDataMapper().map(paymentInfoDataList, PaymentDetailsListWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @GetMapping("/braintree/{paymentId}")
    @Operation(operationId = "getBraintreePaymentDetailsById", summary = "Get customer's Braintree credit card payment details bt ID.", description = "Return customer's Braintree credit card payment details by ID.")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO getBraintreePaymentDetailsById(
            @Parameter(description = "Payment method ID") @PathVariable final String paymentId,
            @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields) {
        LOG.debug("getBraintreePaymentDetailsList");

        final CCPaymentInfoData paymentInfoData = brainTreeUserFacade.getCCPaymentInfoForCode(paymentId);
        final BraintreePaymentMethodNonceData paymentMethodNonce = brainTreePaymentFacade.createPaymentMethodNonce(paymentInfoData);
        final PaymentDetailsWsDTO paymentDetails = getDataMapper().map(paymentInfoData, PaymentDetailsWsDTO.class, fields);

        paymentDetails.setPaymentMethodNonce(paymentMethodNonce.getNonce());
        paymentDetails.setBin(paymentMethodNonce.getDetails().getBin());
        paymentDetails.setShouldPerform3dSecure(paymentMethodNonce.isShouldPerform3dSecure());

        return paymentDetails;
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @PutMapping("/carts/{cartId}/braintree/choose-cc")
    @Operation(operationId = "doSelectPaymentMethod", summary = "Get customer's Braintree credit card payment details list.", description = "Return customer's Braintree credit card payment details list.")
    @ApiBaseSiteIdAndUserIdParam
    @ResponseStatus(HttpStatus.OK)
    public void doSelectPaymentMethod(
        @Parameter(description = "Selected payment method id.") @RequestParam(value = "selectedPaymentMethodId", required = true) final String selectedPaymentMethodId,
        @Parameter(description = "Selected payment method nonce.") @RequestParam(value = "selectedPaymentMethodNonce", required = true) final String selectedPaymentMethodNonce,
        @Parameter(description = "Selected payment method CVV nonce.") @RequestParam(value = "selectedPaymentMethodCvvNonce", required = false) final String selectedPaymentMethodCvvNonce,
        @Parameter(description = "Is threeDsecure flow") @RequestParam(value = "is3dSecureFlow", defaultValue = "false") final boolean is3dSecureFlow)
        throws InvalidPaymentInfoException {
        boolean isSuccess = brainTreeCheckoutFacade
            .setPaymentDetails(selectedPaymentMethodId, selectedPaymentMethodNonce, selectedPaymentMethodCvvNonce, is3dSecureFlow);
        if (!isSuccess) {
            throw new InvalidPaymentInfoException(selectedPaymentMethodId);
        }
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @PutMapping("/braintree/update-nonce/{paymentId}")
    @Operation(operationId = "updateNonce", summary = "Update payment method nonce", description = "Update payment method nonce")
    @ApiBaseSiteIdAndUserIdParam
    @ResponseStatus(HttpStatus.OK)
    public void updateNonce(@PathVariable final String paymentId, @RequestParam final String paymentMethodNonce) throws InvalidPaymentInfoException {
        boolean isSuccess = brainTreeCheckoutFacade.updatePaymentMethodNonce(paymentId, paymentMethodNonce);
        if (!isSuccess) {
            throw new InvalidPaymentInfoException(paymentId);
        }
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @GetMapping(value = "/braintree/getBillingAddress")
    @Operation(operationId = "getBillingAddress", summary = "Get billing address by payment method id", description = "Get billing address by payment method id")
    @ApiBaseSiteIdAndUserIdParam
    public AddressWsDTO getBillingAddressByPaymentMethodId(@RequestParam final String paymentMethodId){
        return getDataMapper().map(userFacade.getBillingAddressByPaymentMethodId(paymentMethodId),AddressWsDTO.class);
    }

    private void setSavePaymentInfoOrShouldBeSaved(String pageType,
                                                   BrainTreeSubscriptionInfoData paymentInfoData, boolean shouldBeSaved) {
        if (BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            paymentInfoData.setSavePaymentInfo(shouldBeSaved);
        } else {
            paymentInfoData.setShouldBeSaved(shouldBeSaved);
        }
    }

    private void validateBraintreePaymentDetailsParam(final boolean shouldBeSaved, final boolean credit) {
        final Errors creditErrors = new BeanPropertyBindingResult(credit, "credit");
        if (!brainTreeConfigService.isStoreInVault() && shouldBeSaved) {
            throw new BraintreeStoreInVaultException();
        } else if (credit && !brainTreeConfigService.isPayLaterEnabled()) {
            creditErrors.reject("credit.exception");
        }
        if (creditErrors.hasErrors()) {
            throw new WebserviceValidationException(creditErrors);
        }
    }

    private BraintreePageType convertPageTypeValue(String pageType) {
        try {
            return BraintreePageType.valueOf(pageType);
        } catch (Exception e) {
            LOG.error(e);
            return BraintreePageType.OTHER;
        }
    }

    private boolean isBillingAddressOptional(String pageType) {
        return BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType)) && brainTreeConfigService.isDropInEnabled();
    }
}
