package com.braintreeb2bocc.controllers;

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


import com.braintree.enums.BraintreePageType;
import com.braintree.exceptions.BrainTreeDeliveryAddressNotFoundException;
import com.braintree.exceptions.InvalidPaymentInfoException;
import com.braintree.exceptions.BraintreeBillingAddressException;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.hybris.data.BrainTreeSubscriptionInfoData;
import com.braintree.hybris.data.ws.BraintreeUsBankAccountPaymentDetailsWsDTO;
import com.braintree.hybris.data.ws.BraintreeTokenizedUsBankAccountWsDTO;
import com.braintree.hybris.data.ws.LocalPaymentRequestWsDTO;
import com.braintree.hybris.data.ws.BraintreeUsBankAccountBillingAddressWsDTO;
import com.braintree.transaction.service.impl.DefaultBrainTreeTransactionService;
import com.braintree.util.GenericBuilder;
import com.braintreeocc.controllers.BraintreeBaseCommerceController;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.request.mapping.annotation.RequestMappingOverride;
import de.hybris.platform.commercewebservicescommons.dto.order.OrderWsDTO;
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 com.braintree.exceptions.BrainTreePaymentMethodLimitedException;
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.swagger.ApiBaseSiteIdAndUserIdParam;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdUserIdAndCartIdParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.Validator;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/{baseSiteId}/users/{userId}")
@CacheControl(directive = CacheControlDirective.NO_CACHE)
@Api(tags = "Braintree Payment Details")
public class BraintreeB2BCreatePaymentDetailsController extends BraintreeBaseCommerceController
{
    private static final Logger LOG = Logger.getLogger(BraintreeB2BCreatePaymentDetailsController.class);
    private static final String DELIVERY_ADDRESS_NOT_SET = "Delivery address is not set ";

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

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

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

    @Resource(name = "usBankAccountAddressConverter")
    private Converter<BraintreeUsBankAccountBillingAddressWsDTO, AddressData> usBankAccountAddressConverter;


    @Resource(name = "brainTreeTransactionService")
    private DefaultBrainTreeTransactionService brainTreeTransactionService;


    @Secured({ "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS" })
    @PostMapping(value = "/braintree/paymentInfo/usBankAccount")
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(nickname = "createUsBankAccountPaymentInfo", value = "Save new US Bank Account payment .",
            notes = "Defines the details of a new US Bank Account payment option and add into customer wallet.")
    @ApiBaseSiteIdAndUserIdParam
    public PaymentDetailsWsDTO addUsBankAccountPaymentInfo(
            @ApiParam(value = "Defines if payment info should be saved")
            @RequestParam(defaultValue = "false") boolean shouldBeSaved,
            @RequestParam(required = false) String deviceData,
            @ApiParam(value = "Request body parameter that contains details such as routingNumber, accountNumber, accountType"
                            + "whether the payment details should be saved (saved), whether the payment details "
                            + "and the billing address (billingAddress.firstName, billingAddress.lastName"
                            + "billingAddress.line1, billingAddress.line2, billingAddress.town, billingAddress.postalCode, billingAddress.region.isocode)"
                            + "The DTO is in .json format.", required = true) @RequestBody final BraintreeUsBankAccountPaymentDetailsWsDTO paymentDetails,
            @ApiParam(value = "Response configuration. This is the list of fields that should be returned in the response body.", allowableValues = "BASIC, DEFAULT, FULL")
            @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
            @RequestParam(defaultValue = BRAINTREE_PAGE_TYPE_OTHER) final String pageType)
            throws InvalidPaymentInfoException
    {
        final AddressData addressData = usBankAccountAddressConverter.convert(paymentDetails.getBillingAddress());
        final BraintreeTokenizedUsBankAccountWsDTO tokenizedUsBankAccountData = paymentDetails.getTokenizedUsBankAccount();
        final BrainTreeSubscriptionInfoData paymentInfoData = GenericBuilder.of(BrainTreeSubscriptionInfoData::new)
                .with(BrainTreeSubscriptionInfoData::setNonce, tokenizedUsBankAccountData.getNonce())
                .with(BrainTreeSubscriptionInfoData::setLast4,  paymentDetails.getAccountNumber().substring(paymentDetails.getAccountNumber().length() - 4))
                .with(BrainTreeSubscriptionInfoData::setRoutingNumber, paymentDetails.getRoutingNumber())
                .with(BrainTreeSubscriptionInfoData::setSavePaymentInfo, shouldBeSaved)
                .with(BrainTreeSubscriptionInfoData::setDeviceData, deviceData)
                .with(BrainTreeSubscriptionInfoData::setAddressData, addressData)
                .build();
        setSavePaymentInfoOrShouldBeSaved(pageType, paymentInfoData, shouldBeSaved);
        CCPaymentInfoData ccPaymentInfoData = null;
        if (BraintreePageType.ACCOUNT.equals(convertPageTypeValue(pageType))) {
            ccPaymentInfoData = brainTreePaymentFacade.completeCreateSubscriptionForMyAccount(paymentInfoData);
        } else {
            ccPaymentInfoData = brainTreePaymentFacade.completeCreateSubscription(paymentInfoData);
        }
        return getDataMapper().map(ccPaymentInfoData, PaymentDetailsWsDTO.class, fields);
    }

    @Secured({ "ROLE_CUSTOMERGROUP", "ROLE_GUEST", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT" })
    @PostMapping(value = "/carts/{cartId}/braintree/paymentInfo/localPayment")
    @ResponseStatus(HttpStatus.CREATED)
    @ApiOperation(nickname = "createLocalPaymentPaymentDetails", value = "Save new LocalPayment info.", notes = "Adding LocalPayment payment info into customer wallet")
    @ApiBaseSiteIdUserIdAndCartIdParam
    @RequestMappingOverride
    public OrderWsDTO processLocalPayment(
            @ApiParam(value =
                    "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,
            @ApiParam(value = "Response configuration. This is the list of fields that should be returned in the response body.", 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.getOrderByPaymentId(paymentMethodNonce) != null){
            return getDataMapper().map(brainTreePaymentFacade.getOrderByPaymentId(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();
        }

        OrderData orderData;

            orderData = getCheckoutFacade().placeOrder();

            LOG.info("Order has been placed, number/code: " + orderData.getCode());
            return getDataMapper().map(orderData, OrderWsDTO.class, fields);
    }

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

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