package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeUpdateCreditCardRequest;
import com.braintree.command.result.BrainTreeBillingAddressResult;
import com.braintree.command.result.BrainTreeUpdateCreditCardBillingAddressResult;
import com.braintree.commands.BrainTreeUpdateCreditCardBillingAddressCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeAddressInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeUpdateCreditCardBillingAddressInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeAddress;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeUpdatePaymentMethodPayLoad;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintreegateway.ValidationError;
import com.braintreegateway.exceptions.NotFoundException;
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.log4j.Logger;

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.INPUT_PARAMETER;
import static com.braintree.constants.BraintreeConstants.RESULT_DATA;
import static com.braintree.constants.BraintreeConstants.RESULT_ERRORS;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;

public class DefaultBrainTreeGraphQLUpdateCreditCardBillingAddressCommand extends AbstractCommand implements
        BrainTreeUpdateCreditCardBillingAddressCommand {

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

    private static final String DEFINITION_FILE_NAME = "updateCreditCardBillingAddress";
    private static final String MUTATION_NAME = "updateCreditCardBillingAddress";
    private static final String UPDATE_BILLING_ADDRESS_REQUEST_PARAMETER = "Update Billing Address Request";
    private static final String ERROR_DURING_BILLING_ADDRESS_UPDATE_MESSAGE = "[BT Update Billing Address] Error during billing address update!";
    private static final String PAYMENT_BILLING_ADDRESS_UPDATED_WITH_ERROR_LOG_MESSAGE = "BT payment Billing Address(payment method token id=%s) updated with error: %s %s";
    private static final String PAYMENT_METHOD_WITH_TOKEN_NOT_FOUND_ERROR_LOG_MESSAGE = "Payment Method with token=%s not Found! Error %s";
    private static final String PAYMENT_METHOD_WITH_TOKEN_ID_NOT_FOUND_MESSAGE = "Payment Method with token id=%s not Found!";
    private Converter<BillingInfo, BrainTreeAddressInput> braintreeBillingInfoToGraphQLAddressInputConverter;
    private Converter<BrainTreeAddress, BrainTreeBillingAddressResult> graphQLAddressToAddressResult;

    private BraintreePaymentInfoService paymentInfoService;

    @Override
    public BrainTreeUpdateCreditCardBillingAddressResult perform(final BrainTreeUpdateCreditCardRequest request) {
        try {
            validateParameterNotNullStandardMessage(UPDATE_BILLING_ADDRESS_REQUEST_PARAMETER, request);
            final String graphQLTokenForPaymentMethod = paymentInfoService.getGraphQLTokenForPaymentMethod(
                    request.getToken());
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME,
                    createVariablesMap(request, graphQLTokenForPaymentMethod));
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            if (mapErrors == null) {
                return translateResponse((Map<String, Object>) result.get(RESULT_DATA), graphQLTokenForPaymentMethod);
            } else {
                List<ValidationError> errors = mapErrors.stream()
                        .map(stringObjectMap -> objectMapper.convertValue(stringObjectMap, ValidationError.class))
                        .collect(Collectors.toList());
                getLoggingHandler().handleValidationErrors(errors);
                return translateErrorResponse(request.getToken(), errors);
            }
        } catch (final NotFoundException notFoundException) {
            return translateNotFoundResponse(request, notFoundException);
        } catch (final Exception exception) {
            LOG.error(ERROR_DURING_BILLING_ADDRESS_UPDATE_MESSAGE);
            throw new AdapterException(exception.getMessage(), exception);
        }
    }

    private Map<String, Object> createVariablesMap(final BrainTreeUpdateCreditCardRequest request,
                                                   final String graphQLTokenForPaymentMethod) {
        Map<String, Object> map = new HashMap<>();

        BrainTreeUpdateCreditCardBillingAddressInput input = new BrainTreeUpdateCreditCardBillingAddressInput();
        input.setPaymentMethodId(graphQLTokenForPaymentMethod);
        BillingInfo billingAddress = request.getBillingInfo();

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

        map.put(INPUT_PARAMETER, input);

        return map;
    }

    private BrainTreeUpdateCreditCardBillingAddressResult translateResponse(Map<String, Object> data,
                                                                            String graphQLTokenForPaymentMethod) {
        final BrainTreeUpdateCreditCardBillingAddressResult result = new BrainTreeUpdateCreditCardBillingAddressResult();
        BrainTreeUpdatePaymentMethodPayLoad payload = objectMapper
                .convertValue(data.get(MUTATION_NAME), BrainTreeUpdatePaymentMethodPayLoad.class);
        if (payload != null) {
            BrainTreeAddress billingAddress = payload.getBillingAddress();
            if (billingAddress != null) {
                BrainTreeBillingAddressResult brainTreeBillingAddressResult = graphQLAddressToAddressResult.convert(
                        billingAddress);
                result.setBillingAddressResult(brainTreeBillingAddressResult);
                result.setPaymentMethodToken(graphQLTokenForPaymentMethod);
                result.setSuccess(true);
                result.setTransactionStatus(TransactionStatus.ACCEPTED);
                result.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL);
                getLoggingHandler().handleResult(graphQLTokenForPaymentMethod);
            }
        }
        return result;
    }

    private BrainTreeUpdateCreditCardBillingAddressResult translateErrorResponse(final String token,
                                                                                 List<ValidationError> errors) {
        final BrainTreeUpdateCreditCardBillingAddressResult brainTreeUpdateBillingAddressResult = new BrainTreeUpdateCreditCardBillingAddressResult();
        brainTreeUpdateBillingAddressResult.setSuccess(false);

        if (errors != null && errors.size() > 0) {
            final ValidationError validationError = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(PAYMENT_BILLING_ADDRESS_UPDATED_WITH_ERROR_LOG_MESSAGE, token, validationError.getCode(),
                            validationError.getMessage()));

            if (validationError.getCode() != null) {
                brainTreeUpdateBillingAddressResult.setErrorCode(validationError.getCode().toString());
            }
            brainTreeUpdateBillingAddressResult.setErrorMessage(validationError.getMessage());
        }

        return brainTreeUpdateBillingAddressResult;
    }

    private BrainTreeUpdateCreditCardBillingAddressResult translateNotFoundResponse(final BrainTreeUpdateCreditCardRequest request,
                                                                                    final NotFoundException notFoundException) {
        getLoggingHandler().getLogger()
                .info(String.format(PAYMENT_METHOD_WITH_TOKEN_NOT_FOUND_ERROR_LOG_MESSAGE, request.getToken(),
                        notFoundException.getMessage()));

        final BrainTreeUpdateCreditCardBillingAddressResult brainTreeUpdateBillingAddressResult = new BrainTreeUpdateCreditCardBillingAddressResult();
        brainTreeUpdateBillingAddressResult.setSuccess(false);
        brainTreeUpdateBillingAddressResult
                .setErrorMessage(String.format(PAYMENT_METHOD_WITH_TOKEN_ID_NOT_FOUND_MESSAGE, request.getToken()));
        return brainTreeUpdateBillingAddressResult;
    }

    public void setPaymentInfoService(BraintreePaymentInfoService paymentInfoService) {
        this.paymentInfoService = paymentInfoService;
    }

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

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

}
