package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeCreatePaymentMethodRequest;
import com.braintree.command.result.BrainTreeCreatePaymentMethodResult;
import com.braintree.commands.BrainTreeCreatePaymentMethodCommand;
import com.braintree.commands.impl.AbstractCommand;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.constants.BraintreeConstants;
import com.braintree.constants.BraintreeConstants.PropertyConfiguration;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.graphql.commands.request.BrainTreePaymentMethodVerificationOptionsInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeRiskDataInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeVaultPaymentMethodInput;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodDetails;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodOrigin;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeVaultPaymentMethodPayload;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeVerification;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import de.hybris.platform.payment.AdapterException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;

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

import static com.braintree.constants.BraintreeConstants.INPUT_PARAMETER;
import static com.braintree.constants.BraintreeConstants.RESULT_DATA;
import static com.braintree.constants.BraintreeConstants.RESULT_ERRORS;

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

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

    private static final String DEFINITION_FILE_NAME = "vaultPaymentMethod";
    private static final String MUTATION_NAME = "vaultPaymentMethod";
    private static final String ERROR_DUE_TO_CREATION_OF_PAYMENT_METHOD_MESSAGE =
            "[Create payment method] Error due to creation of payment method. ";

    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    private BrainTreePaymentService brainTreePaymentService;
    private BraintreePaymentInfoService braintreePaymentInfoService;

    @Override
    public BrainTreeCreatePaymentMethodResult perform(
            BrainTreeCreatePaymentMethodRequest brainTreeCreatePaymentMethodRequest) {

        try {
            Map<String, Object> result = makeGraphQlCall(DEFINITION_FILE_NAME,
                    createVariablesMap(brainTreeCreatePaymentMethodRequest));
            ArrayList<Map<String, Object>> mapErrors = (ArrayList<Map<String, Object>>) result.get(RESULT_ERRORS);

            if (mapErrors == null) {
                return translateResult((Map<String, Object>) result.get(RESULT_DATA));
            } else {
                final String errorMessage =
                        ERROR_DUE_TO_CREATION_OF_PAYMENT_METHOD_MESSAGE + getFistErrorMessage(
                                mapErrors);
                LOG.error(errorMessage);
                throw new IllegalArgumentException();
            }

        } catch (final Exception exception) {
            throw new AdapterException(exception.getMessage(), exception);
        }

    }

    private BrainTreeCreatePaymentMethodResult translateResult(Map<String, Object> data) {

        final BrainTreeCreatePaymentMethodResult result = new BrainTreeCreatePaymentMethodResult();

        BrainTreeVaultPaymentMethodPayload payload = objectMapper
                .convertValue(data.get(MUTATION_NAME), BrainTreeVaultPaymentMethodPayload.class);

        BrainTreeVerification verification = payload.getVerification();
        BraintreePaymentMethodData paymentMethod = payload.getPaymentMethod();

        result.setSuccess(true);

        if (verification != null) {
            result.setErrorCode(verification.getProcessorResponse().getLegacyCode());
            result.setErrorMessage(getLocalizedErrorMessage(PropertyConfiguration.GENERAL_VALIDATION_ERROR_MESSAGE));
            getLoggingHandler().handleVerificationError(verification);
        }

        if (paymentMethod != null) {
            result.setPaymentMethodToken(paymentMethod.getLegacyId());
            result.setPaymentMethodGraphQLToken(paymentMethod.getId());
            BrainTreePaymentMethodDetails details = paymentMethod.getDetails();
            if (details != null) {
                result.setEmail(details.getEmail());
                result.setExpirationMonth(details.getExpirationMonth());
                result.setExpirationYear(details.getExpirationYear());
                result.setCardType(details.getBrandCode());
                result.setLast4(details.getLast4());
                result.setCardholderName(details.getCardholderName());
                if (details.getLast4() != null) {
                    result.setCardNumber(String.format(BraintreeConstants.CARD_NUMBER_MASK, details.getLast4()));
                }
                String originType = Optional.ofNullable(details.getOrigin()).map(BrainTreePaymentMethodOrigin::getType)
                        .orElse(null);
                String paymentProvider = braintreePaymentInfoService
                        .getPaymentProviderByDetailsAndOrigin(details.get__typename(), originType);
                result.setPaymentProvider(paymentProvider);
                result.setImageSource(braintreePaymentInfoService
                        .getImageSourceByPaymentProviderAndCardType(paymentProvider, details.getBrandCode()));
            }
        }
        getLoggingHandler().handleResult(payload);

        return result;
    }


    private Map<String, Object> createVariablesMap(final BrainTreeCreatePaymentMethodRequest request) {

        getLoggingHandler().handleCreatePaymentMethodRequest(request);

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

        BrainTreeVaultPaymentMethodInput input = new BrainTreeVaultPaymentMethodInput();
        BrainTreePaymentMethodVerificationOptionsInput verification = new BrainTreePaymentMethodVerificationOptionsInput();

        verification.setSkip(!(getBrainTreeConfigService().isVerifyCard()));
        verification.setMerchantAccountId(getBrainTreeConfigService().getMerchantAccountIdForCurrentSiteAndCurrency());

        if (StringUtils.isNotEmpty(request.getCustomerId())) {
            input.setCustomerId(brainTreeCustomerAccountService.getGraphQLIdForCustomer(request.getCustomerId()));
        }
        input.setPaymentMethodId(request.getMethodNonce());
        input.setVerification(verification);

        if (BooleanUtils.isTrue(request.getAdvancedFraudTools())) {
            BrainTreeRiskDataInput riskData = new BrainTreeRiskDataInput();
            riskData.setDeviceData(request.getDeviceData());
            input.setRiskData(riskData);
        }

        map.put(INPUT_PARAMETER, input);

        return map;
    }

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

    public BrainTreePaymentService getBrainTreePaymentService() {
        return brainTreePaymentService;
    }

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


    public BraintreePaymentInfoService getBraintreePaymentInfoService() {
        return braintreePaymentInfoService;
    }

    public void setBraintreePaymentInfoService(
            BraintreePaymentInfoService braintreePaymentInfoService) {
        this.braintreePaymentInfoService = braintreePaymentInfoService;
    }

}
