package com.braintree.order.refund.partial.services.impl;

import com.braintree.command.request.BrainTreeRefundTransactionRequest;
import com.braintree.command.result.BrainTreeRefundTransactionResult;
import com.braintree.constants.BraintreeConstants.PropertyConfiguration;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.order.refund.partial.services.BraintreePartialRefundService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import de.hybris.platform.payment.enums.PaymentTransactionType;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.servicelayer.user.UserService;

import java.math.BigDecimal;

import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;
import static de.hybris.platform.util.localization.Localization.getLocalizedString;

public class DefaultBraintreePartialRefundServiceImpl implements BraintreePartialRefundService {

    private static final String ERRORS = "errors";

    private BrainTreePaymentService brainTreePaymentService;
    private BrainTreeTransactionService brainTreeTransactionService;
    private UserService userService;

    @Override
    public PaymentTransactionEntryModel partialRefundTransaction(OrderModel order,
            PaymentTransactionEntryModel paymentTransactionEntry, BigDecimal amount) throws BraintreeErrorException {

        validateParameterNotNullStandardMessage("order", order);
        validateParameterNotNullStandardMessage("braintreeTransactionID", paymentTransactionEntry);
        validateParameterNotNullStandardMessage("amountString", amount);

        if (eligibleForPartialRefund(paymentTransactionEntry)) {
            if (!isValidTransactionId(order, paymentTransactionEntry.getRequestId())) {
                throw new BraintreeErrorException(getLocalizedString(PropertyConfiguration.TRANSACTION_ID_INVALID),
                        paymentTransactionEntry.getRequestId());
            }
            return createPartialRefundTransaction(paymentTransactionEntry, amount, order.getBrainTreeOrderId());
        } else {
            throw new BraintreeErrorException(getLocalizedString(PropertyConfiguration.TRANSACTION_NOT_REFUNDABLE),
                    paymentTransactionEntry.getRequestId());
        }
    }

    public boolean eligibleForPartialRefund(PaymentTransactionEntryModel paymentEntry) {
        return (PaymentTransactionType.CAPTURE.equals(paymentEntry.getType()) || PaymentTransactionType.PARTIAL_CAPTURE
                .equals(paymentEntry.getType()))
                && TransactionStatus.ACCEPTED.name().equals(paymentEntry.getTransactionStatus())
                && TransactionStatusDetails.SUCCESFULL.name().equals(paymentEntry.getTransactionStatusDetails());
    }

    protected PaymentTransactionEntryModel createPartialRefundTransaction(
            final PaymentTransactionEntryModel paymentEntry,
            final BigDecimal amount, final String braintreeOrderId) throws BraintreeErrorException {
        final String merchantTransactionCode = getMerchantCode();
        if (merchantTransactionCode != null) {
            final BrainTreeRefundTransactionRequest request = new BrainTreeRefundTransactionRequest(
                    merchantTransactionCode);
            request.setTransactionId(paymentEntry.getRequestId());

            request.setAmount(amount);
            request.setOrderId(braintreeOrderId);
            final BrainTreeRefundTransactionResult result = brainTreePaymentService.refundTransaction(request);
            validateTransactionResponse(result);

            return brainTreeTransactionService
                    .createPartialRefundTransaction(paymentEntry, result,
                            amount);

        }

        throw new BraintreeErrorException(getLocalizedString(PropertyConfiguration.MERCHANT_CODE_IS_MISSING),
                paymentEntry.getRequestId());
    }

    @Override
    public boolean isValidTransactionId(final OrderModel orderModel, final String transactionId) {
        boolean result = false;

        for (final PaymentTransactionModel paymentTransactionModel : orderModel.getPaymentTransactions()) {
            for (final PaymentTransactionEntryModel paymentTransactionEntryModel : paymentTransactionModel
                    .getEntries()) {
                if ((paymentTransactionEntryModel.getType() == PaymentTransactionType.CAPTURE ||
                        paymentTransactionEntryModel.getType() == PaymentTransactionType.PARTIAL_CAPTURE)
                        && TransactionStatus.ACCEPTED.name().equals(paymentTransactionEntryModel.getTransactionStatus())
                        && paymentTransactionEntryModel.getRequestId().equals(transactionId)) {
                    result = true;
                    break;
                }
            }
        }

        return result;
    }

    private static void validateTransactionResponse(BrainTreeRefundTransactionResult result) throws BraintreeErrorException {
        if (!result.isSuccess()) {
            JsonParser jsonParser = new JsonParser();
            JsonElement jsonElement = jsonParser.parse(result.getResponseBody());
            JsonArray errors = jsonElement.getAsJsonObject().getAsJsonArray(ERRORS);

            if (errors != null && !errors.isEmpty()) {
                throw new BraintreeErrorException(result.getErrorMessage());
            }
        }
    }

    protected String getMerchantCode() {
        return userService.getCurrentUser().getUid();
    }

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

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

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

}
