package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeAdjustAuthorizationRequest;
import com.braintree.command.result.BrainTreeAdjustedAuthorizationResult;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.graphql.commands.BraintreeGraphQLAdjustAuthorizationCommand;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeUpdateTransactionAmountInput;
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.hybris.data.graphql.commands.response.BrainTreeTransactionPayload;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintreegateway.Transaction;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
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;

public class DefaultBraintreeGraphQLAdjustAuthorizationCommand extends AbstractCommand
        implements BraintreeGraphQLAdjustAuthorizationCommand {

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

    private static final String DEFINITION_FILE_NAME = "updateTransactionAmount";
    private static final String TRANSACTION_UPDATED_WITH_ERROR_MESSAGE = "BT transaction id(%s) update failed with error: %s %s";
    private static final String ERROR_DURING_ADJUST_AUTHORIZATION_MESSAGE = "Error during adjust authorization";
    private static final String LOAD_MUTATION_FROM_FILE_ERROR = "Can't load mutation from file. %s";

    private BraintreePaymentInfoService paymentInfoService;
    private BrainTreeTransactionService brainTreeTransactionService;

    @Override
    public BrainTreeAdjustedAuthorizationResult perform(BrainTreeAdjustAuthorizationRequest adjustAuthorizationRequest) {
        try {
            Map<String, Object> requestVariablesMap = createVariablesMap(adjustAuthorizationRequest);
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME, requestVariablesMap);
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            final BrainTreeAdjustedAuthorizationResult adjustAuthorizationResult;

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

            adjustAuthorizationResult.setRequestBody(parseToJson(requestVariablesMap));
            adjustAuthorizationResult.setResponseBody(parseToJson(result));

            return adjustAuthorizationResult;
        } catch (IOException e) {
            LOG.error(String.format(LOAD_MUTATION_FROM_FILE_ERROR, e));
            throw new AdapterException(ERROR_DURING_ADJUST_AUTHORIZATION_MESSAGE);
        }
    }

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

        BrainTreeUpdateTransactionAmountInput input = new BrainTreeUpdateTransactionAmountInput();

        input.setTransactionId(brainTreeTransactionService.getGraphQLIdFromTransaction(request.getTransactionId()));
        input.setAmount(request.getAmount());

        map.put(INPUT_PARAMETER, input);

        return map;
    }

    private BrainTreeAdjustedAuthorizationResult translateResponse(Map<String, Object> data) {
        final BrainTreeAdjustedAuthorizationResult result = new BrainTreeAdjustedAuthorizationResult();
        BrainTreeTransactionPayload payload = objectMapper
                .convertValue(data.get(DEFINITION_FILE_NAME), BrainTreeTransactionPayload.class);
        if (payload != null && payload.getTransaction() != null) {
            BrainTreeTransaction transaction = payload.getTransaction();
            if (transaction.getStatus() != null) {
                if (!transaction.getStatus().isSuccess()) {
                    result.setTransactionStatus(TransactionStatus.REJECTED);
                    result.setErrorMessage(Transaction.Status.GATEWAY_REJECTED.toString());
                    result.setErrorMessage(getMessageByStatus(transaction.getStatusHistory(), transaction.getStatus()));
                    result.setErrorCode(getErrorCodeFromStatusHistory(transaction.getStatusHistory()));
                } else {
                    result.setSuccess(true);
                    result.setStatus(transaction.getStatus().name());
                    result.setAmount(new BigDecimal(transaction.getAmount().getValue()));
                    result.setCurrencyIsoCode(transaction.getAmount().getCurrencyCode());
                    result.setTransactionGraphQLId(transaction.getId());
                    result.setTransactionId(transaction.getLegacyId());
                    result.setTransactionStatus(TransactionStatus.ACCEPTED);
                    result.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL);
                    result.setCreatedAt(transaction.getCreatedAt().getTime());

                    BrainTreePaymentMethodSnapshot paymentMethodSnapshot = transaction.getPaymentMethodSnapshot();
                    result.setPaymentInstrumentType(
                            paymentInfoService.getPaymentInstrumentTypeBySnapshotAndOrigin(paymentMethodSnapshot));
                }
            }
        }
        return result;
    }

    private BrainTreeAdjustedAuthorizationResult translateErrorResponse(final BrainTreeAdjustAuthorizationRequest request,
                                                                        final List<BrainTreeErrorDefinition> errors) {
        final BrainTreeAdjustedAuthorizationResult result = new BrainTreeAdjustedAuthorizationResult();
        result.setSuccess(false);
        if (CollectionUtils.isNotEmpty(errors)) {
            BrainTreeErrorDefinition errorDefinition = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(TRANSACTION_UPDATED_WITH_ERROR_MESSAGE, request.getTransactionId(),
                            errorDefinition.getExtensions().getLegacyCode(), errorDefinition.getMessage()));

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

        }
        return result;
    }

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

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

}
