package com.braintree.facade;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.google.common.collect.Lists;
import de.hybris.platform.b2bacceleratorfacades.checkout.data.PlaceOrderData;
import de.hybris.platform.b2bacceleratorfacades.order.impl.DefaultB2BAcceleratorCheckoutFacade;
import de.hybris.platform.b2b.enums.CheckoutPaymentType;
import de.hybris.platform.commercefacades.order.data.AbstractOrderData;
import de.hybris.platform.commercefacades.order.data.CCPaymentInfoData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.core.model.order.AbstractOrderModel;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.order.InvalidCartException;
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 javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import static com.braintree.constants.BraintreeConstants.BRAINTREE_PROVIDER_NAME;
import static com.braintree.constants.BraintreeConstants.FAKE_REQUEST_ID;


public class BrainTreeB2BCheckoutFacade extends DefaultB2BAcceleratorCheckoutFacade {

    @Resource(name = "brainTreeCheckoutFacade")
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Resource(name = "brainTreeTransactionService")
    private BrainTreeTransactionService brainTreeTransactionService;
    @Resource(name = "brainTreeConfigService")
    private BrainTreeConfigService brainTreeConfigService;
    @Resource(name = "brainTreePaymentFacadeImpl")
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;

    public void storeIntentToCart() {
        CartModel cart = getCartService().getSessionCart();
        if (cart.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
            paymentInfo.setPayPalIntent(brainTreeConfigService.getIntent());
            getModelService().save(paymentInfo);
        }
    }

    public void storeShipsFromPostalCodeToCart(final String shipsFromPostalCode) {
        CartModel cart = getCartService().getSessionCart();
        if (cart.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
            paymentInfo.setShipsFromPostalCode(shipsFromPostalCode);
            getModelService().save(paymentInfo);
        }
    }

    public void storeCustomFieldsToCart(final Map<String, String> customFields) {
        CartModel cart = getCartService().getSessionCart();
        if (cart.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
            paymentInfo.setCustomFields(customFields);
            getModelService().save(paymentInfo);
            getModelService().save(cart);
        }
    }

    @Override
    public boolean authorizePayment(final String securityCode) {
        final CartModel cart = getCartService().getSessionCart();
        if (CheckoutPaymentType.ACCOUNT.equals(cart.getPaymentType())) {
            return super.authorizePayment(securityCode);
        }
        return brainTreeCheckoutFacade.authorizePayment(securityCode);
    }

    @Override
    protected CCPaymentInfoData getPaymentDetails() {
        return brainTreeCheckoutFacade.getPaymentDetails();
    }

    @Override
    public boolean setPaymentDetails(final String paymentInfoId) {
        return brainTreeCheckoutFacade.setPaymentDetails(paymentInfoId);
    }

    public void createPaymentMethodForReplenishment() {
        brainTreeTransactionService.createPaymentMethodTokenForOrderReplenishment();
    }

    @Override
    public <T extends AbstractOrderData> T placeOrder(PlaceOrderData placeOrderData) throws InvalidCartException {
        CartModel cart = getCartService().getSessionCart();

        if (placeOrderData.getReplenishmentOrder() != null && placeOrderData.getReplenishmentOrder()) {
            brainTreePaymentFacade.forceUnduplicateCartForReplenishment(cart.getCode());
        }

//		Due to B2B specific: save successful authorization even without actual request
        if (isNoTransactionAuthorized(cart)) {
            createFakeAuthorizationTransaction(cart);
        }

        return super.placeOrder(placeOrderData);
    }

    public OrderData placeOrderByCart(CartModel cartModel) throws InvalidCartException {
        if (cartModel != null) {
            beforePlaceOrder(cartModel);
            final OrderModel orderModel = placeOrder(cartModel);
            afterPlaceOrder(cartModel, orderModel);
            if (orderModel != null) {
                return getOrderConverter().convert(orderModel);
            }
        }
        return null;
    }

    public OrderModel placeOrderForLocalPaymentReversed(CartModel cartModel) throws InvalidCartException {
        if (cartModel != null) {
            beforePlaceOrder(cartModel);
            final OrderModel orderModel = placeOrder(cartModel);
            afterPlaceOrder(cartModel, orderModel);
            return orderModel;
        }
        return null;
    }

    @Override
    protected void beforePlaceOrder(CartModel cartModel) {
        super.beforePlaceOrder(cartModel);
        this.brainTreeCheckoutFacade.beforePlaceOrder(cartModel);
    }

    @Override
    protected void afterPlaceOrder(CartModel cartModel, OrderModel orderModel) {
        if (orderModel != null) {
            getModelService().remove(cartModel);
            getModelService().refresh(orderModel);
        }
    }

    private boolean isNoTransactionAuthorized(final CartModel cart) {
        return cart.getPaymentTransactions().stream()
                .map(PaymentTransactionModel::getEntries)
                .flatMap(Collection::stream)
                .filter(isAuthorizationTypePredicate())
                .filter(isFakeTransactionPredicate().negate())
                .map(PaymentTransactionEntryModel::getTransactionStatus)
                .noneMatch(Predicate.isEqual(TransactionStatus.ACCEPTED.name()));
    }

    private Predicate<PaymentTransactionEntryModel> isAuthorizationTypePredicate() {
        return entry -> PaymentTransactionType.AUTHORIZATION.equals(entry.getType());
    }

    private Predicate<PaymentTransactionEntryModel> isFakeTransactionPredicate() {
        return entry -> FAKE_REQUEST_ID.equals(entry.getRequestId());
    }

    public void setCardPaymentType() {
        final CartModel cart = getCart();
        cart.setPaymentType(CheckoutPaymentType.CARD);
        getModelService().save(cart);
    }

    private void createFakeAuthorizationTransaction(CartModel cart) {

        final PaymentTransactionEntryModel paymentTransactionEntryModel = getModelService()
                .create(PaymentTransactionEntryModel.class);
        paymentTransactionEntryModel.setType(PaymentTransactionType.AUTHORIZATION);
        paymentTransactionEntryModel.setTransactionStatus(TransactionStatus.ACCEPTED.name());
        paymentTransactionEntryModel.setTransactionStatusDetails(TransactionStatusDetails.SUCCESFULL.name());
        paymentTransactionEntryModel.setRequestId(FAKE_REQUEST_ID);
        final String code =
                BRAINTREE_PROVIDER_NAME + "_cart_" + cart.getCode() + "_stamp_" + System.currentTimeMillis();
        paymentTransactionEntryModel.setCode(code);

        savePaymentTransaction(paymentTransactionEntryModel, cart);
    }

    private void savePaymentTransaction(final PaymentTransactionEntryModel paymentTransactionEntry,
                                        final AbstractOrderModel cart) {
        final List<PaymentTransactionModel> paymentTransactions;
        List<PaymentTransactionEntryModel> paymentTransactionEntries;
        paymentTransactions = Lists.newArrayList();

        PaymentTransactionModel braintreePaymentTransaction = getModelService().create(PaymentTransactionModel.class);
        braintreePaymentTransaction.setRequestId(paymentTransactionEntry.getRequestId());
        braintreePaymentTransaction.setPaymentProvider(BRAINTREE_PROVIDER_NAME);
        if (cart.getPaymentInfo() != null && cart.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            BrainTreePaymentInfoModel paymentInfo = (BrainTreePaymentInfoModel) cart.getPaymentInfo();
            braintreePaymentTransaction.setInfo(paymentInfo);
        }
        paymentTransactions.add(braintreePaymentTransaction);

        paymentTransactionEntries = Lists.newArrayList();

        paymentTransactionEntries.add(paymentTransactionEntry);
        braintreePaymentTransaction.setEntries(paymentTransactionEntries);
        cart.setPaymentTransactions(paymentTransactions);

        getModelService().saveAll(paymentTransactionEntry, braintreePaymentTransaction, cart);
    }

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

    public void setBrainTreeCheckoutFacade(DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade) {
        this.brainTreeCheckoutFacade = brainTreeCheckoutFacade;
    }

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

    public void setBrainTreePaymentFacade(DefaultBrainTreePaymentFacade brainTreePaymentFacade) {
        this.brainTreePaymentFacade = brainTreePaymentFacade;
    }

}
