package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeFindTransactionRequest;
import com.braintree.command.result.BrainTreeFindCustomerResult;
import com.braintree.command.result.BrainTreeFindTransactionResult;
import com.braintree.commands.BrainTreeFindRefundCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.BrainTreeGraphQLTransactionData;
import com.braintree.hybris.data.BraintreeTransactionData;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeSearchPaymentCustomerInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeSearchValueInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionSearchInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeRefundConnection;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeSearchRefund;
import com.braintree.transaction.service.BrainTreeTransactionService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static com.braintree.constants.BraintreeConstants.AFTER_PARAMETER;
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 DefaultBrainTreeGraphQLFindRefundCommand extends
        AbstractCommand<BrainTreeFindTransactionRequest, BrainTreeFindCustomerResult>
        implements BrainTreeFindRefundCommand {

    private static final Logger LOG = Logger.getLogger(DefaultBrainTreeGraphQLFindRefundCommand.class);
    private static final String DEFINITION_FILE_NAME = "searchRefund";
    private static final String MUTATION_NAME = "search";
    private static final String ERROR_DUE_SEARCHING_REFUNDS_MESSAGE = "[BT Find Refund] Error due searching refunds. ";
    private static final String REFUND_SEARCH_TOO_MANY_RESULTS_MESSAGE =
            "[BT Refund search]Too many results! Limit is %s. Please type refunds id or customer email.";
    private static final String BRAIN_TREE_FIND_REFUNDS_REQUEST = "brainTreeFindRefundsRequest: ";

    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BrainTreeTransactionService brainTreeTransactionService;
    private Converter<BrainTreeRefundConnection, BrainTreeGraphQLTransactionData> refundGraphQLSearchResultConverter;

    @Override
    public BrainTreeFindTransactionResult perform(BrainTreeFindTransactionRequest request) {
        try {
            int searchLimit = getBrainTreeConfigService().getBackofficeSearchLimit();
            Map<String, Object> variablesMap = createVariablesMap(request);
            BraintreeTransactionData transactionDataResult = new BraintreeTransactionData();
            transactionDataResult.setTransactionEntries(new ArrayList<>());

            fillInTransactionEntries(variablesMap, transactionDataResult, searchLimit);

            BrainTreeFindTransactionResult transactionResult = new BrainTreeFindTransactionResult();

            if (transactionDataResult.getTransactionEntries().size() > searchLimit) {
                LOG.warn(String.format(REFUND_SEARCH_TOO_MANY_RESULTS_MESSAGE, searchLimit));
                transactionResult.setExceedLimit(true);
                return transactionResult;
            }

            transactionResult.setTransactionData(transactionDataResult);
            return transactionResult;
        } catch (final Exception exception) {
            throw new AdapterException(exception.getMessage(), exception);
        }
    }

    private void fillInTransactionEntries(Map<String, Object> variablesMap, BraintreeTransactionData transactionDataResult, int searchLimit) throws IOException {
        String endCursor = null;
        boolean hasNextPage = false;
        do {
            Optional.ofNullable(endCursor).ifPresent(s -> variablesMap.put(AFTER_PARAMETER, s));
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME, variablesMap);
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            if (mapErrors == null) {
                BrainTreeGraphQLTransactionData braintreeTransactionData = translateResponse(
                        (Map<String, Object>) result.get(RESULT_DATA));
                if (braintreeTransactionData != null) {
                    transactionDataResult.getTransactionEntries()
                            .addAll(braintreeTransactionData.getTransactionData().getTransactionEntries());
                    hasNextPage = braintreeTransactionData.getHasNextPage();
                    endCursor = braintreeTransactionData.getEndCursor();
                }
            } else {
                final String errorMessage = ERROR_DUE_SEARCHING_REFUNDS_MESSAGE + getFistErrorMessage(mapErrors);
                LOG.error(errorMessage);
                throw new IllegalArgumentException();
            }

        } while (hasNextPage && transactionDataResult.getTransactionEntries().size() <= searchLimit);
    }

    private BrainTreeGraphQLTransactionData translateResponse(Map<String, Object> data) {
        BrainTreeSearchRefund payload = objectMapper
                .convertValue(data.get(MUTATION_NAME), BrainTreeSearchRefund.class);
        return refundGraphQLSearchResultConverter.convert(payload.getRefunds());
    }

    private Map<String, Object> createVariablesMap(BrainTreeFindTransactionRequest brainTreeFindTransactionRequest) {

        Map<String, Object> map = new HashMap<>();

        final BrainTreeTransactionSearchInput input = new BrainTreeTransactionSearchInput();
        final BrainTreeSearchValueInput transactionId = new BrainTreeSearchValueInput();
        final BrainTreeSearchPaymentCustomerInput customer = new BrainTreeSearchPaymentCustomerInput();
        final BrainTreeSearchValueInput customerId = new BrainTreeSearchValueInput();

        if (brainTreeFindTransactionRequest != null) {

            if (brainTreeFindTransactionRequest.getTransactionIds() != null) {
                transactionId.setIn(brainTreeFindTransactionRequest.getTransactionIds());
            }

            if (StringUtils.isNotBlank(brainTreeFindTransactionRequest.getCustomerId())) {
                customerId.setIs(brainTreeCustomerAccountService
                        .getGraphQLIdForCustomer(brainTreeFindTransactionRequest.getCustomerId()));
            }

            customer.setId(customerId);
            input.setId(transactionId);
            input.setCustomer(customer);

            map.put(INPUT_PARAMETER, input);
            LOG.info(BRAIN_TREE_FIND_REFUNDS_REQUEST + map);
        }
        return map;
    }

    public BrainTreeCustomerAccountService getBrainTreeCustomerAccountService() {
        return brainTreeCustomerAccountService;
    }

    public void setBrainTreeCustomerAccountService(BrainTreeCustomerAccountService brainTreeCustomerAccountService) {
        this.brainTreeCustomerAccountService = brainTreeCustomerAccountService;
    }

    public BrainTreeTransactionService getBrainTreeTransactionService() {
        return brainTreeTransactionService;
    }

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

    public Converter<BrainTreeRefundConnection, BrainTreeGraphQLTransactionData> getRefundGraphQLSearchResultConverter() {
        return refundGraphQLSearchResultConverter;
    }

    public void setRefundGraphQLSearchResultConverter(
            Converter<BrainTreeRefundConnection, BrainTreeGraphQLTransactionData> refundGraphQLSearchResultConverter) {
        this.refundGraphQLSearchResultConverter = refundGraphQLSearchResultConverter;
    }

}
