package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeSaleTransactionRequest;
import com.braintree.command.request.BrainTreeTokenizeCreditCardRequest;
import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.command.result.BrainTreeTokenizeCardResult;
import com.braintree.commands.BrainTreeSaleCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.helpers.SubmitForSettlementHelper;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeAddressInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeChargePaymentMethodInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionCustomerDetailsInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionShippingInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionTaxInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeVaultPaymentMethodAfterTransactingInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeVaultPaymentMethodCriteria;
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.method.BrainTreePaymentService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.service.BrainTreeDescriptorService;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.dto.CardInfo;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

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 BrainTreeSaleCommand and is used in GraphQL API.
 */
public class DefaultBrainTreeGraphQLSaleTransactionCommand extends AbstractCommand implements
        BrainTreeSaleCommand {

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

    private static final String DEFINITION_FILE_NAME_CHARGE_PAYPAL = "chargePayPalAccount";
    private static final String DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL = "authorizePayPalAccount";
    private static final String SALE_TRANSACTION_REQUEST_PARAMETER = "Sale transaction request";
    private static final String ERROR_DURING_USING_EXISTING_PAYMENT_MESSAGE = "Error during using existing payment.";
    private static final String PAYMENT_METHOD_TOKEN_IS_NULL_ERROR_MESSAGE = "Error: PaymentMethodToken is null!";
    private static final String BT_SALE_TRANSACTION_WITH_ERROR_LOG_MESSAGE = "BT sale transaction(new transaction) with error: %s %s";
    private static final String ERROR_DURING_SALE_TRANSACTION_REQUEST_MESSAGE = "Error during sale transaction. Request: ";

    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BraintreePaymentInfoService paymentInfoService;
    private BrainTreePaymentService brainTreePaymentService;
    private SubmitForSettlementHelper submitForSettlementHelper;
    private BrainTreeDescriptorService brainTreeDescriptorService;

    @Override
    public BrainTreeSaleTransactionResult perform(final BrainTreeSaleTransactionRequest saleRequest) {
        LOG.error("---> " + this);
        validateParameterNotNullStandardMessage(SALE_TRANSACTION_REQUEST_PARAMETER, saleRequest);
        try {
            String fileDefinition = getMutationFileDefinition(saleRequest);
            Map<String, Object> result = makeGraphQlCall(fileDefinition,
                    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), fileDefinition);
            } else {
                List<BrainTreeErrorDefinition> errors = mapErrors.stream()
                        .map(stringObjectMap -> objectMapper.convertValue(stringObjectMap, BrainTreeErrorDefinition.class))
                        .collect(Collectors.toList());
                braintreeResult = translateErrorResponse(errors);
            }

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

    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_SALE_TRANSACTION_WITH_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 BrainTreeSaleTransactionResult translateResponse(Map<String, Object> data, String mutationName) {
        final BrainTreeSaleTransactionResult result = new BrainTreeSaleTransactionResult(false);
        BrainTreeTransactionPayload payload = objectMapper
                .convertValue(data.get(mutationName), BrainTreeTransactionPayload.class);

        if (payload != null && payload.getTransaction() != null && payload.getTransaction().getStatus() != null) {
            if (payload.getTransaction().getStatus().isSuccess()) {
                result.setSuccess(true);
                BrainTreeTransaction transaction = payload.getTransaction();
                result.setMerchantTransactionCode(transaction.getMerchantAccountId());
                result.setRequestId(transaction.getLegacyId());
                result.setTransactionGraphQLId(transaction.getId());
                result.setTransactionId(transaction.getLegacyId());
                result.setRequestToken(transaction.getLegacyId());
                result.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL);
                result.setTransactionStatus(TransactionStatus.ACCEPTED);
                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));
            } else {
                result.setErrorMessage(
                        getMessageByStatus(payload.getTransaction().getStatusHistory(), payload.getTransaction().getStatus()));
                result.setErrorCode(getErrorCodeFromStatusHistory(payload.getTransaction().getStatusHistory()));
                result.setTransactionStatusDetails(TransactionStatusDetails.BANK_DECLINE);
            }

        }
        return result;
    }

    private Map<String, Object> createVariablesMap(BrainTreeSaleTransactionRequest saleRequest) {
        Map<String, Object> map = new HashMap<>();
        BrainTreeChargePaymentMethodInput input = new BrainTreeChargePaymentMethodInput();
        BrainTreeTransactionInput transaction = new BrainTreeTransactionInput();
        input.setTransaction(transaction);

        transaction.setAmount(saleRequest.getTotalAmount());

        BrainTreeTransactionTaxInput tax = new BrainTreeTransactionTaxInput();
        tax.setTaxAmount(saleRequest.getTaxAmount());
        transaction.setTax(tax);

        if (saleRequest.getCustomerId() != null) {
            transaction
                    .setCustomerId(brainTreeCustomerAccountService.getGraphQLIdForCustomer(saleRequest.getCustomerId()));
        }
        transaction.setChannel(getBrainTreeConfigService().getBrainTreeChannel());
        transaction.setMerchantAccountId(saleRequest.getMerchantAccountIdForCurrentSite());

        if (!saleRequest.getUsePaymentMethodToken() && getBrainTreeConfigService().isStoreInVault()) {
            BrainTreeVaultPaymentMethodAfterTransactingInput vault = new BrainTreeVaultPaymentMethodAfterTransactingInput();
            vault.setWhen(BrainTreeVaultPaymentMethodCriteria.ON_SUCCESSFUL_TRANSACTION);
            input.getTransaction().setVaultPaymentMethodAfterTransacting(vault);
        }

        setCustomFields(transaction, saleRequest.getCustomFields());

        setAdditionalParameters(saleRequest, input);

        addShippingInfo(saleRequest, input);
        if (saleRequest.getCustomerId() == null && saleRequest.getCustomerEmail() != null) {
            addCustomerInfo(saleRequest, input);
        }

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

    private void addCustomerInfo(BrainTreeSaleTransactionRequest saleRequest, BrainTreeChargePaymentMethodInput input) {
        BrainTreeTransactionCustomerDetailsInput customerDetailsInput = new BrainTreeTransactionCustomerDetailsInput();
        customerDetailsInput.setEmail(saleRequest.getCustomerEmail());
        input.getTransaction().setCustomerDetails(customerDetailsInput);
    }

    private void addShippingInfo(BrainTreeSaleTransactionRequest saleRequest, BrainTreeChargePaymentMethodInput input) {
        BrainTreeTransactionShippingInput shippingInput = new BrainTreeTransactionShippingInput();
        BrainTreeAddressInput addressInput = new BrainTreeAddressInput();
        BillingInfo shippingInfo = saleRequest.getShippingInfo();
        if (shippingInfo.getState() != null) {
            addressInput.setRegion(shippingInfo.getState());
        } else {
            addressInput.setRegion(shippingInfo.getRegion());
        }
        addressInput.setCountryCode(shippingInfo.getCountry());
        addressInput.setFirstName(shippingInfo.getFirstName());
        addressInput.setStreetAddress(shippingInfo.getStreet1());
        addressInput.setLastName(shippingInfo.getLastName());
        addressInput.setPostalCode(shippingInfo.getPostalCode());
        addressInput.setLocality(shippingInfo.getCity());
        shippingInput.setShippingAddress(addressInput);
        input.getTransaction().setShipping(shippingInput);
    }

    private void setAdditionalParameters(BrainTreeSaleTransactionRequest saleRequest,
                                         BrainTreeChargePaymentMethodInput input) {
        if (BooleanUtils.isTrue(saleRequest.getUsePaymentMethodToken())) {
            if (saleRequest.getPaymentMethodToken() == null) {
                getLoggingHandler().getLogger().error(PAYMENT_METHOD_TOKEN_IS_NULL_ERROR_MESSAGE);
                throw new IllegalArgumentException(ERROR_DURING_USING_EXISTING_PAYMENT_MESSAGE);
            }
            input.setPaymentMethodId(
                    paymentInfoService.getGraphQLTokenForPaymentMethod(saleRequest.getPaymentMethodToken()));
        } else {
            if (StringUtils.isNotBlank(saleRequest.getMethodNonce())) {
                input.setPaymentMethodId(saleRequest.getMethodNonce());
            } else if (StringUtils.isNotBlank(saleRequest.getPaymentMethodToken())) {
                input.setPaymentMethodId(
                        paymentInfoService.getGraphQLTokenForPaymentMethod(saleRequest.getPaymentMethodToken()));
            } else if (saleRequest.getCard() != null) {
                addCardInfo(saleRequest, input);
            }
        }

        String paymentType = saleRequest.getPaymentType();
        if (brainTreeDescriptorService.isDescriptorAvailable(paymentType)) {
            input.getTransaction().setDescriptor(brainTreeDescriptorService.getTransactionDescriptor(paymentType));
        }
    }

    private void addCardInfo(BrainTreeSaleTransactionRequest saleRequest, BrainTreeChargePaymentMethodInput input) {
        BrainTreeTokenizeCreditCardRequest request = new BrainTreeTokenizeCreditCardRequest();
        CardInfo card = saleRequest.getCard();
        request.setCardHolderName(card.getCardHolderFullName());
        request.setCardNumber(card.getCardNumber());
        request.setCvv(card.getCv2Number());
        request.setExpirationMonth(card.getExpirationMonth());
        request.setExpirationYear(card.getExpirationYear());
        BrainTreeTokenizeCardResult tokenizeCardResult = brainTreePaymentService.tokenizeCreditCard(request);
        input.setPaymentMethodId(tokenizeCardResult.getMethodGraphQLId());
    }

    private String getMutationFileDefinition(BrainTreeSaleTransactionRequest saleRequest) {
        if (getBrainTreeConfigService().isMultiCaptureEnabled() ||
                submitForSettlementHelper.isAvailableSubmitForSettlement(saleRequest.getPaymentType())) {
            return DEFINITION_FILE_NAME_CHARGE_PAYPAL;
        }
        return DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL;
    }

    public BrainTreeCustomerAccountService getBrainTreeCustomerAccountService() {
        return brainTreeCustomerAccountService;
    }

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

    public BraintreePaymentInfoService getPaymentInfoService() {
        return paymentInfoService;
    }

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

    public BrainTreePaymentService getBrainTreePaymentService() {
        return brainTreePaymentService;
    }

    public void setBrainTreePaymentService(BrainTreePaymentService brainTreePaymentService) {
        this.brainTreePaymentService = brainTreePaymentService;
    }

    public void setSubmitForSettlementHelper(SubmitForSettlementHelper submitForSettlementHelper) {
        this.submitForSettlementHelper = submitForSettlementHelper;
    }

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