/**
 *
 */
package com.braintree.commands.impl;

import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_ECVZ_ACEESS_TOKEN;
import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_MERCHANT_ID;
import static com.braintree.constants.BraintreeConstants.BRAINTREE_PARSE_TO_JSON_EXCEPTION;
import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_PRIVATE_KEY;
import static com.braintree.constants.BraintreeConstants.PropertyConfiguration.BRAINTREE_PUBLIC_KEY;
import static de.hybris.platform.util.Config.getParameter;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.graphql.BrainTreeGraphQLClient;
import com.braintree.graphql.BrainTreeGraphQLSchemaReaderUtil;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCustomFieldInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentStatus;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentStatusEvent;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransactionAuthorizationProcessorResponse;
import com.braintreegateway.BraintreeGateway;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import de.hybris.platform.payment.commands.request.AbstractRequest;
import de.hybris.platform.util.localization.Localization;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;


public abstract class AbstractCommand<RequestType, ResponseType> {

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

    protected AbstractRequest request;
    protected ObjectMapper objectMapper;
    private DefaultBrainTreeLoggingHandler loggingHandler;
    private DefaultBraintreeCodeTranslator codeTranslator;
    private DefaultBraintreeErrorTranslator errorTranslator;
    private BrainTreeConfigService brainTreeConfigService;

    public AbstractCommand() {
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public BraintreeGateway getBraintreeGateway() {
        final BraintreeGateway gateway;
        if (StringUtils.isNotEmpty(getParameter(BRAINTREE_ECVZ_ACEESS_TOKEN))) {
            gateway = new BraintreeGateway(getParameter(BRAINTREE_ECVZ_ACEESS_TOKEN));

        } else {
            gateway = new BraintreeGateway(getBrainTreeConfigService().getEnvironmentType(),
                getParameter(BRAINTREE_MERCHANT_ID), getParameter(BRAINTREE_PUBLIC_KEY),
                getParameter(BRAINTREE_PRIVATE_KEY));
        }
        gateway.graphQLClient = new BrainTreeGraphQLClient(gateway.getConfiguration());
        return gateway;
    }


    protected String getLocalizedErrorMessage(String errorMessageCode) {
        return Localization.getLocalizedString(errorMessageCode);
    }

    public String parseToJson(Object object) {
        if (object != null) {
            return new Gson().toJson(object);
        } else {
            LOG.error("[BT Transaction] Error during parsing response object into JSON.");
            return BRAINTREE_PARSE_TO_JSON_EXCEPTION;
        }
    }

    public Map<String, Object> makeGraphQlCall(String fileDefinition, Map<String, Object> variables)
        throws IOException {
        String definition = BrainTreeGraphQLSchemaReaderUtil.getSchemaFromFileName(fileDefinition);
        return getBraintreeGateway().graphQLClient.query(definition, variables);
    }

    public Map<String, Object> makeGraphQlCall(String fileDefinition)
            throws IOException {
        String definition = BrainTreeGraphQLSchemaReaderUtil.getSchemaFromFileName(fileDefinition);
        return getBraintreeGateway().graphQLClient.query(definition);
    }

    protected String getFistErrorMessage(List<Map<String, Object>> mapErrors) {
        String message = "";
        List<BrainTreeErrorDefinition> errors = mapErrors.stream()
            .map(stringObjectMap -> objectMapper.convertValue(stringObjectMap, BrainTreeErrorDefinition.class))
            .collect(Collectors.toList());
        if (errors.size() > 0) {
            message += errors.get(0).getMessage();
        }
        return message;
    }

    protected String getMessageByStatus(List<BrainTreePaymentStatusEvent> statusHistory, BrainTreePaymentStatus status) {
        return Optional.ofNullable(statusHistory)
                .filter(list -> !list.isEmpty())
                .map(list -> list.get(0))
                .map(BrainTreePaymentStatusEvent::getProcessorResponse)
                .map(BrainTreeTransactionAuthorizationProcessorResponse::getMessage)
                .orElse(status.getDefaultMessage());
    }

    protected String getCodeByStatus(List<BrainTreePaymentStatusEvent> statusHistory) {
        return Optional.ofNullable(statusHistory)
                .filter(list -> !list.isEmpty())
                .map(list -> list.get(0))
                .map(BrainTreePaymentStatusEvent::getProcessorResponse)
                .map(BrainTreeTransactionAuthorizationProcessorResponse::getLegacyCode)
                .orElse(StringUtils.EMPTY);
    }

    protected String getErrorCodeFromStatusHistory(List<BrainTreePaymentStatusEvent> statusHistory) {
        String code = null;
        if (statusHistory != null && statusHistory.size() > 0) {
            code = Optional.ofNullable(statusHistory.get(0))
                .map(BrainTreePaymentStatusEvent::getProcessorResponse)
                .map(BrainTreeTransactionAuthorizationProcessorResponse::getLegacyCode)
                .orElse(null);
        }
        return code;
    }

    protected void setCustomFields(BrainTreeTransactionInput transactionInput, Map<String, String> customFields) {
        if (customFields != null && !customFields.isEmpty()) {
            List<BrainTreeCustomFieldInput> customFieldInputs = new ArrayList<>();
            for (final Map.Entry<String, String> entry : customFields.entrySet()) {
                BrainTreeCustomFieldInput customFieldInput = new BrainTreeCustomFieldInput();
                customFieldInput.setName(entry.getKey());
                customFieldInput.setValue(customFields.get(entry.getKey()));
                customFieldInputs.add(customFieldInput);
            }
            transactionInput.setCustomFields(customFieldInputs);
        }
    }

    /**
     * @return the loggingHandler
     */
    public DefaultBrainTreeLoggingHandler getLoggingHandler() {
        return loggingHandler;
    }

    /**
     * @param loggingHandler
     *           the loggingHandler to set
     */
    public void setLoggingHandler(final DefaultBrainTreeLoggingHandler loggingHandler) {
        this.loggingHandler = loggingHandler;
    }

    /**
     * @return the codeTranslator
     */
    public DefaultBraintreeCodeTranslator getCodeTranslator() {
        return codeTranslator;
    }

    /**
     * @param codeTranslator
     *           the codeTranslator to set
     */
    public void setCodeTranslator(final DefaultBraintreeCodeTranslator codeTranslator) {
        this.codeTranslator = codeTranslator;
    }

    /**
     * @return the errorTranslator
     */
    public DefaultBraintreeErrorTranslator getErrorTranslator() {
        return errorTranslator;
    }

    /**
     * @param errorTranslator
     *           the errorTranslator to set
     */
    public void setErrorTranslator(final DefaultBraintreeErrorTranslator errorTranslator) {
        this.errorTranslator = errorTranslator;
    }

    /**
     * @return the brainTreeConfigService
     */
    public BrainTreeConfigService getBrainTreeConfigService() {
        return brainTreeConfigService;
    }

    /**
     * @param brainTreeConfigService
     *           the brainTreeConfigService to set
     */
    public void setBrainTreeConfigService(final BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }
}
