package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeCreateCreditCardPaymentMethodRequest;
import com.braintree.command.result.BrainTreeBillingAddressResult;
import com.braintree.command.result.BrainTreePaymentMethodResult;
import com.braintree.commands.BrainTreeCreateCreditCardPaymentMethodCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.constants.BraintreeConstants.PropertyConfiguration;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeAddressInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreePaymentMethodVerificationOptionsInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeRiskDataInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeVaultCreditCardPaymentMethodInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeAddress;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodDetails;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodOrigin;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeVaultPaymentMethodPayload;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeVerification;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.braintree.constants.BraintreeConstants.CARD_NUMBER_MASK;
import static com.braintree.constants.BraintreeConstants.INPUT_PARAMETER;
import static com.braintree.constants.BraintreeConstants.RESULT_DATA;
import static com.braintree.constants.BraintreeConstants.RESULT_ERRORS;
import static com.braintree.constants.BraintreeConstants.VERIFICATION_STATUS_FAILED;

/**
 * This class extends AbstractCommand, implements BrainTreeCreateCreditCardPaymentMethodCommand
 * and is used in GraphQL API.
 */
public class DefaultBrainTreeGraphQLCreateCreditCardPaymentMethodCommand extends
        AbstractCommand<BrainTreeCreateCreditCardPaymentMethodRequest, BrainTreePaymentMethodResult> implements
        BrainTreeCreateCreditCardPaymentMethodCommand {

    private static final String DEFINITION_FILE_NAME = "vaultCreditCard";
    private static final String CANNOT_CREATE_PAYMENT_METHOD_FOR_USER_WITH_ERROR_MESSAGE =
            "Cannot create Payment method for user(%s) with error: %s %s";

    private BrainTreePaymentService brainTreePaymentService;
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BraintreePaymentInfoService braintreePaymentInfoService;
    private Converter<BrainTreeAddress, BrainTreeBillingAddressResult> graphQLAddressToAddressResult;
    private Converter<BillingInfo, BrainTreeAddressInput> braintreeBillingInfoToGraphQLAddressInputConverter;

    @Override
    public BrainTreePaymentMethodResult perform(final BrainTreeCreateCreditCardPaymentMethodRequest request) {

        try {
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME, createVariablesMap(request));
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            if (mapErrors == null) {
                return translateResult((Map<String, Object>) result.get(RESULT_DATA));
            } else {
                List<BrainTreeErrorDefinition> errors = mapErrors.stream()
                        .map(stringObjectMap -> objectMapper.convertValue(stringObjectMap, BrainTreeErrorDefinition.class))
                        .collect(Collectors.toList());
                return translateErrorResponse(request, errors);
            }

        } catch (Exception ex) {
            throw new AdapterException(ex.getMessage(), ex);
        }
    }

    private BrainTreePaymentMethodResult translateErrorResponse(BrainTreeCreateCreditCardPaymentMethodRequest request,
                                                                List<BrainTreeErrorDefinition> errors) {
        final BrainTreePaymentMethodResult result = new BrainTreePaymentMethodResult(false);
        if (errors != null && errors.size() > 0) {
            BrainTreeErrorDefinition errorDefinition = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(CANNOT_CREATE_PAYMENT_METHOD_FOR_USER_WITH_ERROR_MESSAGE, request.getCustomerId(),
                            errorDefinition.getExtensions().getLegacyCode(), errorDefinition.getMessage()));

            if (errorDefinition.getExtensions().getLegacyCode() != null) {
                result.setErrorCode(errorDefinition.getExtensions().getLegacyCode());
            }
            result.setErrorMessage(errorDefinition.getMessage());

        }
        return result;
    }

    private BrainTreePaymentMethodResult translateResult(Map<String, Object> data) {
        BrainTreePaymentMethodResult result = new BrainTreePaymentMethodResult(true);
        BrainTreeVaultPaymentMethodPayload payload = objectMapper
                .convertValue(data.get(DEFINITION_FILE_NAME), BrainTreeVaultPaymentMethodPayload.class);
        result.setTransactionStatus(TransactionStatus.ACCEPTED);
        result.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL);

        BrainTreeVerification verification = payload.getVerification();
        if (verification != null && VERIFICATION_STATUS_FAILED.equalsIgnoreCase(verification.getStatus())) {
            result.setSuccess(false);
            result.setErrorCode(verification.getProcessorResponse().getLegacyCode());
            result.setErrorMessage(getLocalizedErrorMessage(PropertyConfiguration.GENERAL_VALIDATION_ERROR_MESSAGE));
            getLoggingHandler().handleVerificationError(verification);
            return result;
        }

        BraintreePaymentMethodData paymentMethod = payload.getPaymentMethod();
        if (paymentMethod != null) {
            result.setPaymentMethodToken(paymentMethod.getLegacyId());
            result.setPaymentMethodGraphQLToken(paymentMethod.getId());
            result.setCreatedAt(paymentMethod.getCreatedAt());
            result.setCustomerId(paymentMethod.getCustomer().getLegacyId());
            BrainTreePaymentMethodDetails details = paymentMethod.getDetails();
            if (details != null) {
                result.setCardType(details.getBrandCode());
                result.setLast4(details.getLast4());
                result.setExpirationMonth(details.getExpirationMonth());
                result.setExpirationYear(details.getExpirationYear());
                result.setBin(details.getBin());
                result.setEmail(details.getEmail());
                result.setCardholderName(details.getCardholderName());
                if (details.getLast4() != null) {
                    result.setCardNumber(String.format(CARD_NUMBER_MASK, details.getLast4()));
                }

                if (details.getBillingAddress() != null) {
                    result.setBillingAddressResult(graphQLAddressToAddressResult.convert(details.getBillingAddress()));
                }
                BrainTreePaymentMethodOrigin origin = details.getOrigin();
                String originType = null;
                if (origin != null) {
                    originType = origin.getType();
                }
                String paymentProvider = braintreePaymentInfoService
                        .getPaymentProviderByDetailsAndOrigin(details.get__typename(), originType);
                result.setPaymentProvider(paymentProvider);
                result.setImageSource(braintreePaymentInfoService
                        .getImageSourceByPaymentProviderAndCardType(paymentProvider, details.getBrandCode()));

            }
        }

        return result;
    }

    private Map<String, Object> createVariablesMap(BrainTreeCreateCreditCardPaymentMethodRequest methodRequest) {
        Map<String, Object> map = new HashMap<>();

        BrainTreeVaultCreditCardPaymentMethodInput input = new BrainTreeVaultCreditCardPaymentMethodInput();

        input.setPaymentMethodId(methodRequest.getMethodNonce());
        if (StringUtils.isNotEmpty(methodRequest.getCustomerId())) {
            input.setCustomerId(brainTreeCustomerAccountService.getGraphQLIdForCustomer(methodRequest.getCustomerId()));
        }

        BrainTreePaymentMethodVerificationOptionsInput verification = new BrainTreePaymentMethodVerificationOptionsInput();
        verification.setSkip(!(getBrainTreeConfigService().isVerifyCard() || methodRequest.isThreeDSecureFlow()));
        verification.setMerchantAccountId(getBrainTreeConfigService().getMerchantAccountIdForCurrentSiteAndCurrency());
        input.setVerification(verification);

        BillingInfo billingAddress = methodRequest.getBillingInfo();
        if (billingAddress != null) {
            BrainTreeAddressInput addressInput = braintreeBillingInfoToGraphQLAddressInputConverter
                    .convert(billingAddress);
            input.setBillingAddress(addressInput);
        }

        if (methodRequest.getAdvancedFraudTools()) {
            BrainTreeRiskDataInput riskData = new BrainTreeRiskDataInput();
            riskData.setDeviceData(methodRequest.getDeviceData());
            input.setRiskData(riskData);
        }

        map.put(INPUT_PARAMETER, input);

        return map;
    }

    public BrainTreePaymentService getBrainTreePaymentService() {
        return brainTreePaymentService;
    }

    public void setBrainTreePaymentService(BrainTreePaymentService brainTreePaymentService) {
        this.brainTreePaymentService = brainTreePaymentService;
    }

    public BrainTreeCustomerAccountService getBrainTreeCustomerAccountService() {
        return brainTreeCustomerAccountService;
    }

    public void setBrainTreeCustomerAccountService(
            BrainTreeCustomerAccountService brainTreeCustomerAccountService) {
        this.brainTreeCustomerAccountService = brainTreeCustomerAccountService;
    }

    public Converter<BrainTreeAddress, BrainTreeBillingAddressResult> getGraphQLAddressToAddressResult() {
        return graphQLAddressToAddressResult;
    }

    public void setGraphQLAddressToAddressResult(
            Converter<BrainTreeAddress, BrainTreeBillingAddressResult> graphQLAddressToAddressResult) {
        this.graphQLAddressToAddressResult = graphQLAddressToAddressResult;
    }

    public Converter<BillingInfo, BrainTreeAddressInput> getBraintreeBillingInfoToGraphQLAddressInputConverter() {
        return braintreeBillingInfoToGraphQLAddressInputConverter;
    }

    public void setBraintreeBillingInfoToGraphQLAddressInputConverter(
            Converter<BillingInfo, BrainTreeAddressInput> braintreeBillingInfoToGraphQLAddressInputConverter) {
        this.braintreeBillingInfoToGraphQLAddressInputConverter = braintreeBillingInfoToGraphQLAddressInputConverter;
    }

    public BraintreePaymentInfoService getBraintreePaymentInfoService() {
        return braintreePaymentInfoService;
    }

    public void setBraintreePaymentInfoService(
            BraintreePaymentInfoService braintreePaymentInfoService) {
        this.braintreePaymentInfoService = braintreePaymentInfoService;
    }

}
