package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeSaleTransactionRequest;
import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.commands.BrainTreePartialCaptureCommand;
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.BrainTreePartialCaptureTransactionPayload;
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.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 BrainTreePartialCaptureCommand and is used in GraphQL API.
 */
public class DefaultBrainTreeGraphQLPartialCaptureCommand extends AbstractCommand implements
        BrainTreePartialCaptureCommand {

    private static final String DEFINITION_FILE_NAME = "partialCaptureTransaction";
    private static final String MUTATION_NAME = "partialCaptureTransaction";
    private static final String BT_RESPONSE_ERROR_LOG_MESSAGE = "BT response error: %s, %s";
    private static final String PARTIAL_CAPTURE_REQUEST_PARAMETER = "Partial Capture request";

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

    @Override
    public BrainTreeSaleTransactionResult perform(BrainTreeSaleTransactionRequest saleRequest) {
        validateParameterNotNullStandardMessage(PARTIAL_CAPTURE_REQUEST_PARAMETER, saleRequest);
        try {
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME,
                    createVariablesMap(saleRequest));
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            BrainTreeSaleTransactionResult 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(errors);
            }

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

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

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

    }

    private BrainTreeSaleTransactionResult translateErrorResponse(List<BrainTreeErrorDefinition> errors) {
        final BrainTreeSaleTransactionResult response = new BrainTreeSaleTransactionResult(false);

        response.setTransactionStatus(TransactionStatus.REJECTED);

        if (errors != null && errors.size() > 0) {
            BrainTreeErrorDefinition errorDefinition = errors.get(0);
            getLoggingHandler().getLogger().info(
                    String.format(BT_RESPONSE_ERROR_LOG_MESSAGE, 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 BrainTreeSaleTransactionRequest request) {
        Map<String, Object> map = new HashMap<>();
        final BrainTreeCaptureTransactionInput input = new BrainTreeCaptureTransactionInput();
        input.setTransactionId(brainTreeTransactionService.getGraphQLIdFromTransaction(request.getRequestId()));
        final BrainTreeCaptureTransactionOptionsInput transaction = new BrainTreeCaptureTransactionOptionsInput();
        transaction.setAmount(request.getTotalAmount());

        String paymentType = request.getPaymentType();
        if (brainTreeDescriptorService.isDescriptorAvailable(paymentType)) {
            transaction.setDescriptor(brainTreeDescriptorService.getTransactionDescriptor(paymentType));
        }
        input.setTransaction(transaction);
        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;
    }
}
