package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeSubmitForSettlementTransactionRequest;
import com.braintree.command.result.BrainTreeSubmitForSettlementTransactionResult;
import com.braintree.commands.BrainTreeSubmitForSettlementCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCaptureTransactionInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCaptureTransactionOptionsInput;
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.service.BrainTreeDescriptorService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import de.hybris.platform.payment.AdapterException;
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 de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;

/**
 * This class extends AbstractCommand, implements BrainTreeSubmitForSettlementCommand and is used in GraphQL API.
 */
public class DefaultBrainTreeGraphQLSubmitForSettlementTransactionCommand extends AbstractCommand implements
        BrainTreeSubmitForSettlementCommand {

    private static final String DEFINITION_FILE_NAME = "captureTransaction";
    private static final String MUTATION_NAME = "captureTransaction";
    private static final String SUBMIT_FOR_SETTLEMENT_TRANSACTION_REQUEST_PARAMETER = "Submit For Settlement transaction Request";
    private static final String SUBMIT_FOR_SETTLEMENT_TRANSACTION_LOG_TYPE = "[SUBMIT FOR SETTLEMENT TRANSACTION] ";
    private static final String SUBMIT_FOR_SETTLEMENT_WITH_ERROR_MESSAGE = "BT transaction id(%s) submit for settlement with error: %s %s";

    private BrainTreeTransactionService brainTreeTransactionService;
    private BraintreePaymentInfoService paymentInfoService;
    private BrainTreeDescriptorService brainTreeDescriptorService;

    @Override
    public BrainTreeSubmitForSettlementTransactionResult perform(
            BrainTreeSubmitForSettlementTransactionRequest request) {
        validateParameterNotNullStandardMessage(SUBMIT_FOR_SETTLEMENT_TRANSACTION_REQUEST_PARAMETER, 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);

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

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

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

                BrainTreePaymentMethodSnapshot paymentMethodSnapshot = transaction.getPaymentMethodSnapshot();
                result.setCaptureId(paymentMethodSnapshot.getCaptureId());
                result.setPaymentInstrumentType(
                        paymentInfoService.getPaymentInstrumentTypeBySnapshotAndOrigin(paymentMethodSnapshot));

                getLoggingHandler().handleResult(SUBMIT_FOR_SETTLEMENT_TRANSACTION_LOG_TYPE, transaction);
            }
        }
        return result;
    }

    private BrainTreeSubmitForSettlementTransactionResult translateErrorResponse(final String transactionId,
                                                                                 List<BrainTreeErrorDefinition> errors) {
        final BrainTreeSubmitForSettlementTransactionResult response = new BrainTreeSubmitForSettlementTransactionResult(
                false);

        response.setTransactionStatus(TransactionStatus.REJECTED);

        if (errors != null && errors.size() > 0) {
            BrainTreeErrorDefinition errorDefinition = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(SUBMIT_FOR_SETTLEMENT_WITH_ERROR_MESSAGE, transactionId,
                            errorDefinition.getExtensions().getLegacyCode(),
                            errorDefinition.getMessage()));

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

        }

        return response;
    }

    private Map<String, Object> createVariablesMap(final BrainTreeSubmitForSettlementTransactionRequest request) {
        Map<String, Object> map = new HashMap<>();
        final BrainTreeCaptureTransactionInput input = new BrainTreeCaptureTransactionInput();
        input.setTransactionId(brainTreeTransactionService.getGraphQLIdFromTransaction(request.getTransactionId()));
        final BrainTreeCaptureTransactionOptionsInput transactionOptionsInput = new BrainTreeCaptureTransactionOptionsInput();

        if (request.getOrderId() != null) {
            transactionOptionsInput.setOrderId(request.getOrderId());
        }
        if (request.getAmount() != null) {
            transactionOptionsInput.setAmount(request.getAmount());
        }

        String paymentType = request.getPaymentProvider();
        if (brainTreeDescriptorService.isDescriptorAvailable(paymentType)) {
            transactionOptionsInput.setDescriptor(brainTreeDescriptorService.getTransactionDescriptor(paymentType));
        }

        input.setTransaction(transactionOptionsInput);
        map.put(INPUT_PARAMETER, input);
        return map;
    }

    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;
    }

    public void setBrainTreeDescriptorService(BrainTreeDescriptorService brainTreeDescriptorService) {
        this.brainTreeDescriptorService = brainTreeDescriptorService;
    }
}
