package com.braintree.customer.service;

import com.braintree.command.request.BrainTreeAddressRequest;
import com.braintree.command.result.BrainTreeAddressResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customer.dao.BrainTreeCustomerAccountDao;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeExchangeLegacyIdRequest;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.commerceservices.customer.impl.DefaultCustomerAccountService;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.PaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.orderscheduling.model.CartToOrderCronJobModel;
import de.hybris.platform.servicelayer.exceptions.ModelSavingException;
import de.hybris.platform.servicelayer.util.ServicesUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import static com.braintree.constants.BraintreeConstants.TYPE_CUSTOMER_PARAMETER;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNull;


public class BrainTreeCustomerAccountService extends DefaultCustomerAccountService {
    private static final Logger LOG = Logger.getLogger(BrainTreeCustomerAccountService.class);

    private BrainTreeCustomerAccountDao brainTreeCustomerAccountDao;
    private BrainTreeConfigService brainTreeConfigService;
    private BrainTreePaymentService brainTreePaymentService;

    public BrainTreePaymentInfoModel getBrainTreePaymentInfoForCode(final CustomerModel customer, final String code) {
        return brainTreeCustomerAccountDao.findBrainTreePaymentInfoByCustomer(customer, code);
    }

    public PaymentInfoModel getPaymentInfoForCode(final CustomerModel customer, final String code) {
        return brainTreeCustomerAccountDao.findPaymentInfoByCustomer(customer, code);
    }

    public List<BrainTreePaymentInfoModel> getBrainTreePaymentInfos(final CustomerModel customerModel,
        final boolean saved) {
        return brainTreeCustomerAccountDao
            .findBrainTreePaymentInfosByCustomer(customerModel, saved);
    }

    public void unlinkCCPaymentInfo(final CustomerModel customerModel,
        final BrainTreePaymentInfoModel brainTreePaymentInfo) {
        ServicesUtil.validateParameterNotNull(customerModel, "Customer model cannot be null");
        ServicesUtil.validateParameterNotNull(brainTreePaymentInfo, "BrainTreePaymentInfo model cannot be null");

        if (customerModel.getPaymentInfos().contains(brainTreePaymentInfo)) {
            final Collection paymentInfoList = new ArrayList(customerModel.getPaymentInfos());
            paymentInfoList.remove(brainTreePaymentInfo);
            customerModel.setPaymentInfos(paymentInfoList);
            getModelService().save(customerModel);
            getModelService().refresh(customerModel);
        } else {
            throw new IllegalArgumentException("Credit Card Payment Info " + brainTreePaymentInfo
                + " does not belong to the customer " + customerModel + " and will not be removed.");
        }
    }


    @Override
    public void deleteAddressEntry(CustomerModel customerModel, AddressModel addressModel) {
        validateParameterNotNull(addressModel, "Address model cannot be null");

        final String brainTreeAddressId = addressModel.getBrainTreeAddressId();
        if (StringUtils.isNotEmpty(brainTreeAddressId)) {
            final BrainTreeAddressRequest addressRequest = convertBrainTreeAddress(customerModel, addressModel);
            addressRequest.setAddressId(brainTreeAddressId);
            brainTreePaymentService.removeAddress(addressRequest);
        }
        super.deleteAddressEntry(customerModel, addressModel);
    }


    @Override
    public void saveAddressEntry(CustomerModel customerModel, AddressModel addressModel) {
        super.saveAddressEntry(customerModel, addressModel);

        if (addressModel.getShippingAddress() && brainTreeConfigService.isStoreInVaultForCurrentUser()) {
            saveAddressOnBraintreeSide(customerModel, addressModel);
        }
    }

    @Override
    public void convertGuestToCustomer(String pwd, String orderGUID) throws DuplicateUidException {
        try {
            super.convertGuestToCustomer(pwd, orderGUID);
        } catch (ModelSavingException e) {
            LOG.error(String.format("Model saving exception : %s", e.getMessage()), e);
            throw new DuplicateUidException(e.getMessage());
        }
        final OrderModel orderModel = getOrderDetailsForGUID(orderGUID, getBaseStoreService().getCurrentBaseStore());

        final AddressModel deliveryAddress = orderModel.getDeliveryAddress();
        if (getUserService().getCurrentUser() instanceof CustomerModel && deliveryAddress != null) {
            final CustomerModel customerModel = (CustomerModel) getUserService().getCurrentUser();
            if (brainTreeConfigService.isStoreInVault() && StringUtils
                .isNotEmpty(customerModel.getBraintreeCustomerId())) {

                saveAddressOnBraintreeSide(customerModel, deliveryAddress);

            }
        }
    }

    private void saveAddressOnBraintreeSide(CustomerModel customerModel, AddressModel addressModel) {
        final BrainTreeAddressRequest brainTreeAddressRequest = convertBrainTreeAddress(customerModel, addressModel);
        final BrainTreeAddressResult brainTreeAddress = brainTreePaymentService.createAddress(brainTreeAddressRequest,
                customerModel);
        if (brainTreeAddress != null && brainTreeAddress.getAddress() != null) {
            addressModel.setBrainTreeAddressId(brainTreeAddress.getAddress().getId());
            getModelService().save(customerModel);
            getModelService().refresh(customerModel);
        }
    }

    private BrainTreeAddressRequest convertBrainTreeAddress(CustomerModel customerModel, AddressModel address) {
        final BrainTreeAddressRequest addressRequest = new BrainTreeAddressRequest(
            customerModel.getBraintreeCustomerId());
        addressRequest.setStreetAddress(address.getLine1());
        addressRequest.setExtendedAddress(address.getLine2());
        addressRequest.setFirstName(address.getFirstname());
        addressRequest.setLastName(address.getLastname());
        addressRequest.setLocality(address.getTown());
        addressRequest.setPostalCode(address.getPostalcode());

        if (address.getCountry() != null) {
            addressRequest.setCountryCodeAlpha2(address.getCountry().getIsocode());
        }
        if (address.getRegion() != null) {
            //The state or province. For PayPal addresses, the region must be a 2-letter abbreviation; for all other payment methods, it must be less than or equal to 255 characters.
            //because of hybris specific use the isocodeShort - 2 character isocode - and its right for braintree
            //the isocode return  2 character isocode US-CA - wrong for braintree
            addressRequest.setRegion(address.getRegion().getIsocodeShort());
        }

        return addressRequest;
    }

    public void deleteBrainTreePaymentInfo(final CustomerModel customerModel,
        final BrainTreePaymentInfoModel paymentInfoModel) {
        validateParameterNotNull(customerModel, "Customer model cannot be null");
        validateParameterNotNull(paymentInfoModel, "BrainTreePaymentInfo model cannot be null");
        if (customerModel.getPaymentInfos().contains(paymentInfoModel)) {
            paymentInfoModel.setSaved(false);
            cancelAssociatedReplenishments(paymentInfoModel);
            getModelService().save(paymentInfoModel);
            getModelService().refresh(customerModel);
        } else {
            throw new IllegalArgumentException("Payment Info " + paymentInfoModel
                + " does not belong to the customer " + customerModel + " and will not be removed.");
        }
    }

    private void cancelAssociatedReplenishments(final BrainTreePaymentInfoModel paymentInfoModel) {
        paymentInfoModel.getCartToOrderCronJob().forEach(this::cancelReplenishment);
    }

    private void cancelReplenishment(CartToOrderCronJobModel cronJobModel) {
        cronJobModel.setActive(false);
        getModelService().save(cronJobModel);
    }

    public String getGraphQLIdForCustomer(String legacyId) {
        validateParameterNotNull(legacyId, "Legacy id cannot be null");
        final CustomerModel customerModel = brainTreeCustomerAccountDao.findCustomerByBrainTreeCustomerId(legacyId);
        if (customerModel != null) {
            String braintreeGraphQLId = customerModel.getBraintreeCustomerGraphQLId();
            if (braintreeGraphQLId == null) {
                final BrainTreeExchangeLegacyIdRequest request = new BrainTreeExchangeLegacyIdRequest();
                request.setLegacyId(legacyId);
                request.setType(TYPE_CUSTOMER_PARAMETER);
                braintreeGraphQLId = brainTreePaymentService.exchangeLegacyIdToGraphQLId(request);
                customerModel.setBraintreeCustomerGraphQLId(braintreeGraphQLId);
                getModelService().save(customerModel);
            }
            return braintreeGraphQLId;
        } else {
            final BrainTreeExchangeLegacyIdRequest request = new BrainTreeExchangeLegacyIdRequest();
            request.setLegacyId(legacyId);
            request.setType(TYPE_CUSTOMER_PARAMETER);
            return brainTreePaymentService.exchangeLegacyIdToGraphQLId(request);
        }
    }

    public Optional<CustomerModel> getCustomerByPayerId(String payerId) {
        return brainTreeCustomerAccountDao.findCustomerByPayPalPayerId(payerId);
    }

    public void setAccessTokenForCustomer(String accessTokenGuid, String payerId) {
        Optional<CustomerModel> optionalCustomerModel = brainTreeCustomerAccountDao.findCustomerByPayPalPayerId(payerId);
        if (optionalCustomerModel.isPresent()) {
            final CustomerModel customerModel = optionalCustomerModel.get();
            customerModel.setPayPalAccessToken(accessTokenGuid);
            getModelService().save(customerModel);
        }
    }

    public void setDefaultAccessToken(String payerId) {
        Optional<CustomerModel> optionalCustomerModel = brainTreeCustomerAccountDao.findCustomerByPayPalPayerId(payerId);
        if (optionalCustomerModel.isPresent()) {
            final CustomerModel customerModel = optionalCustomerModel.get();
            customerModel.setPayPalAccessToken(StringUtils.EMPTY);
            getModelService().save(customerModel);
        }
    }

    public void setBrainTreeCustomerAccountDao(final BrainTreeCustomerAccountDao brainTreeCustomerAccountDao) {
        this.brainTreeCustomerAccountDao = brainTreeCustomerAccountDao;
    }

    public void setBrainTreeConfigService(final BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }

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