package com.braintree.graphql.commands.impl;

import com.braintree.command.result.BrainTreeVoidResult;
import com.braintree.commands.BrainTreeVoidCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodSnapshot;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransaction;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.commands.request.VoidRequest;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;

import java.math.BigDecimal;
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 com.braintree.constants.BraintreeConstants.TRANSACTION_ID_PARAMETER;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;
import static java.util.Optional.ofNullable;

public class DefaultBrainTreeGraphQLVoidCommand extends AbstractCommand implements BrainTreeVoidCommand {

    private static final String DEFINITION_FILE_NAME = "reverseTransaction";
    private static final String MUTATION_NAME = "reverseTransaction";
    private static final String REVERSAL_PARAMETER = "reversal";
    private static final String VOID_TRANSACTION_REQUEST_PARAMETER = "Void transaction Request";
    private static final String BT_TRANSACTION_LEGACY_ID_VOID_WITH_ERROR_MESSAGE = "BT transaction legacy id(%s) void with error: %s %s";
    private static final String VOID_TRANSACTION_LOG_TYPE = "[VOID TRANSACTION] ";

    private BrainTreeTransactionService brainTreeTransactionService;
    private BraintreePaymentInfoService paymentInfoService;

    @Override
    public BrainTreeVoidResult perform(final VoidRequest voidRequest) {
        validateParameterNotNullStandardMessage(VOID_TRANSACTION_REQUEST_PARAMETER, voidRequest);
        try {
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME, createVariablesMap(voidRequest));
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

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

            braintreeResult.setRequestBody(parseToJson(voidRequest));
            braintreeResult.setResponseBody(parseToJson(result));
            return braintreeResult;
        } catch (final Exception exception) {
            throw new AdapterException(exception.getMessage(), exception);
        }
    }

    private Map<String, Object> createVariablesMap(final VoidRequest voidRequest) {
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> input = new HashMap<>();
        input.put(TRANSACTION_ID_PARAMETER,
                brainTreeTransactionService.getGraphQLIdFromTransaction(voidRequest.getRequestId()));
        map.put(INPUT_PARAMETER, input);
        return map;
    }

    private BrainTreeVoidResult translateErrorResponse(final String transactionId,
                                                       List<BrainTreeErrorDefinition> errors) {
        final BrainTreeVoidResult voidResult = new BrainTreeVoidResult(false);
        if (errors != null && errors.size() > 0) {
            BrainTreeErrorDefinition errorDefinition = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(BT_TRANSACTION_LEGACY_ID_VOID_WITH_ERROR_MESSAGE, transactionId,
                            errorDefinition.getExtensions().getLegacyCode(),
                            errorDefinition.getMessage()));

            if (errorDefinition.getExtensions().getLegacyCode() != null) {
                voidResult.setErrorCode(errorDefinition.getExtensions().getLegacyCode());
            }
            voidResult.setErrorMessage(errorDefinition.getMessage());
            voidResult.setTransactionStatus(TransactionStatus.ERROR);
            voidResult.setRequestId(transactionId);

        }
        return voidResult;
    }

    private BrainTreeVoidResult translateResponse(Map<String, Object> data, final VoidRequest request) {
        final BrainTreeVoidResult result = new BrainTreeVoidResult(false);
        Map<String, Object> reverseTransaction = (Map<String, Object>) data.get(MUTATION_NAME);
        BrainTreeTransaction reversal = objectMapper
                .convertValue(reverseTransaction.get(REVERSAL_PARAMETER), BrainTreeTransaction.class);

        if (reversal.getStatus() != null) {
            if (!reversal.getStatus().isSuccess()) {
                result.setErrorMessage(getMessageByStatus(reversal.getStatusHistory(), reversal.getStatus()));
                result.setErrorCode(getErrorCodeFromStatusHistory(reversal.getStatusHistory()));
                result.setTransactionStatus(TransactionStatus.ERROR);
                result.setRequestId(request.getRequestId());
            } else {
                result.setSuccess(true);
                result.setStatus(reversal.getStatus().name());
                result.setMerchantTransactionCode(request.getMerchantTransactionCode());
                result.setRequestId(request.getRequestId());
                result.setRequestToken(request.getRequestToken());
                result.setTransactionStatus(TransactionStatus.ACCEPTED);
                result.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL);
                result.setMerchantTransactionCode(request.getMerchantTransactionCode());

                result.setAmount(new BigDecimal(reversal.getAmount().getValue()));
                result.setCurrencyIsoCode(reversal.getAmount().getCurrencyCode());
                result.setTransactionId(reversal.getLegacyId());
                result.setTransactionGraphQLId(reversal.getId());
                setCreatedAt(result, reversal);

                BrainTreePaymentMethodSnapshot paymentMethodSnapshot = reversal.getPaymentMethodSnapshot();
                result.setPaymentInstrumentType(
                        paymentInfoService.getPaymentInstrumentTypeBySnapshotAndOrigin(paymentMethodSnapshot));

                getLoggingHandler().handleResult(VOID_TRANSACTION_LOG_TYPE, reversal);
            }
        }
        return result;
    }

    private void setCreatedAt(BrainTreeVoidResult result, BrainTreeTransaction reversal) {
        ofNullable(reversal.getCreatedAt())
                .ifPresent(createdAt -> result.setCreatedAt(createdAt.getTime()));
    }

    public BrainTreeTransactionService getBrainTreeTransactionService() {
        return brainTreeTransactionService;
    }

    public void setBrainTreeTransactionService(
            BrainTreeTransactionService brainTreeTransactionService) {
        this.brainTreeTransactionService = brainTreeTransactionService;
    }

    public BraintreePaymentInfoService getPaymentInfoService() {
        return paymentInfoService;
    }

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

}
