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 de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.b2b.company.B2BCommerceUnitService;
import de.hybris.platform.b2b.model.B2BCommentModel;
import de.hybris.platform.b2b.model.B2BUnitModel;
import de.hybris.platform.b2bacceleratorfacades.checkout.data.PlaceOrderData;
import de.hybris.platform.b2bacceleratorfacades.order.data.B2BPaymentTypeData;
import de.hybris.platform.b2b.enums.CheckoutPaymentType;
import de.hybris.platform.commercefacades.order.CartFacade;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.order.data.DeliveryModeData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.commercefacades.order.data.ZoneDeliveryModeData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commerceservices.delivery.DeliveryService;
import de.hybris.platform.commerceservices.order.CommerceCheckoutService;
import de.hybris.platform.commerceservices.service.data.CommerceCheckoutParameter;
import de.hybris.platform.commerceservices.service.data.CommerceOrderResult;
import de.hybris.platform.commerceservices.strategies.CheckoutCustomerStrategy;
import de.hybris.platform.core.HybrisEnumValue;
import de.hybris.platform.core.PK;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.delivery.DeliveryModeModel;
import de.hybris.platform.core.model.order.payment.CreditCardPaymentInfoModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.deliveryzone.model.ZoneDeliveryModeModel;
import de.hybris.platform.enumeration.EnumerationService;
import de.hybris.platform.order.CartService;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.payment.dto.TransactionStatus;
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.dto.converter.Converter;
import de.hybris.platform.servicelayer.i18n.I18NService;
import de.hybris.platform.servicelayer.model.ModelService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import static de.hybris.platform.b2b.enums.CheckoutPaymentType.ACCOUNT;
import static de.hybris.platform.b2b.enums.CheckoutPaymentType.CARD;
import static de.hybris.platform.b2bacceleratorfacades.order.data.B2BReplenishmentRecurrenceEnum.DAILY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class BrainTreeB2BCheckoutFacadeTest {

    private static final String INTENT = "intent";
    private static final String SHIPS_FROM_POSTAL_CODE = "shipsFromPostalCode";
    private static final String SECURITY_CODE = "securityCode";
    private static final String PAYMENT_INFO_ID = "paymentInfoId";
    private static final PK ADDRESS_PK = PK.BIG_PK;
    private static final String CART_CODE = "cartCode";
    private static final String REQUEST_ID = "requestId";
    private static final String KEY = "key";
    private static final String VALUE = "value";

    @Mock
    private CartService cartService;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;
    @Mock
    private ModelService modelService;
    @Mock
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Mock
    private CartFacade cartFacade;
    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Mock
    private DeliveryService deliveryService;
    @Mock
    private Converter<AddressModel, AddressData> addressConverter;
    @Mock
    private Converter<DeliveryModeModel, DeliveryModeData> deliveryModeConverter;
    @Mock
    private Converter<ZoneDeliveryModeModel, ZoneDeliveryModeData> zoneDeliveryModeConverter;
    @Mock
    private CheckoutCustomerStrategy checkoutCustomerStrategy;
    @Mock
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Mock
    private CommerceCheckoutService commerceCheckoutService;
    @Mock
    private Converter<OrderModel, OrderData> orderConverter;
    @Mock
    private EnumerationService enumerationService;
    @Mock
    private B2BCommerceUnitService b2bCommerceUnitService;
    @Mock
    private Converter<CheckoutPaymentType, B2BPaymentTypeData> b2bPaymentTypeDataConverter;

    @Spy
    @InjectMocks
    private BrainTreeB2BCheckoutFacade unit;

    @Mock
    private CartModel cart;
    @Mock
    private BrainTreePaymentInfoModel brainTreePaymentInfoModel;
    @Mock
    private CreditCardPaymentInfoModel creditCardPaymentInfoModel;
    @Mock
    private PaymentTransactionEntryModel paymentTransactionEntryModel;
    @Mock
    private PaymentTransactionModel paymentTransactionModel;
    @Mock
    private AddressModel address;
    @Mock
    private DeliveryModeModel deliveryMode;
    @Mock
    private CustomerModel customer;
    @Mock
    private I18NService i18NService;
    @Mock
    private OrderModel order;
    @Mock
    private B2BCommentModel b2bComment;
    @Mock
    private BrainTreePaymentInfoModel brainTreePaymentInfo;
    @Mock
    private B2BUnitModel b2bUnit;

    @Test
    public void shouldStoreIntentIfBraintreePaymentInfoModel() {
        when(cart.getPaymentInfo()).thenReturn(brainTreePaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);
        when(brainTreeConfigService.getIntent()).thenReturn(INTENT);

        unit.storeIntentToCart();

        verify(brainTreePaymentInfoModel).setPayPalIntent(INTENT);
        verify(modelService).save(brainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotStoreIntentIfCreditCardPaymentInfoModel() {
        when(cart.getPaymentInfo()).thenReturn(creditCardPaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.storeIntentToCart();

        verify(modelService, never()).save(creditCardPaymentInfoModel);
    }

    @Test
    public void shouldSetShipsFromPostalCodeIfBraintreePaymentInfoModel() {
        when(cart.getPaymentInfo()).thenReturn(brainTreePaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.storeShipsFromPostalCodeToCart(SHIPS_FROM_POSTAL_CODE);

        verify(brainTreePaymentInfoModel).setShipsFromPostalCode(SHIPS_FROM_POSTAL_CODE);
        verify(modelService).save(brainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotSetShipsFromPostalCodeIfCreditCardPaymentInfoModel() {
        when(cart.getPaymentInfo()).thenReturn(creditCardPaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.storeShipsFromPostalCodeToCart(SHIPS_FROM_POSTAL_CODE);

        verify(modelService, never()).save(creditCardPaymentInfoModel);
    }

    @Test
    public void shouldStoreCustomFieldsIfBraintreePaymentInfoModel() {
        Map<String, String> customFields = Map.of(KEY, VALUE);
        when(cart.getPaymentInfo()).thenReturn(brainTreePaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.storeCustomFieldsToCart(customFields);

        verify(brainTreePaymentInfoModel).setCustomFields(customFields);
        verify(modelService).save(brainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotStoreCustomFieldsIfCreditCardPaymentInfoModel() {
        Map<String, String> customFields = Map.of(KEY, VALUE);
        when(cart.getPaymentInfo()).thenReturn(creditCardPaymentInfoModel);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.storeCustomFieldsToCart(customFields);

        verify(modelService, never()).save(brainTreePaymentInfoModel);
    }

    @Test
    public void shouldAuthorize() {
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.authorizePayment(SECURITY_CODE);

        verify(brainTreeCheckoutFacade).authorizePayment(SECURITY_CODE);
    }

    @Test
    public void shouldUseSuperInAuthorize() {
        when(cartService.getSessionCart()).thenReturn(cart);
        when(cart.getPaymentType()).thenReturn(CheckoutPaymentType.ACCOUNT);

        unit.authorizePayment(SECURITY_CODE);

        verify(brainTreeCheckoutFacade, never()).authorizePayment(SECURITY_CODE);
    }

    @Test
    public void shouldReturnPaymentDetails() {
        unit.getPaymentDetails();

        verify(brainTreeCheckoutFacade).getPaymentDetails();
    }

    @Test
    public void shouldSetPaymentDetails() {
        unit.setPaymentDetails(PAYMENT_INFO_ID);

        verify(brainTreeCheckoutFacade).setPaymentDetails(PAYMENT_INFO_ID);
    }

    @Test
    public void shouldCreatePaymentMethodForReplenishment() {
        unit.createPaymentMethodForReplenishment();

        verify(brainTreeTransactionService).createPaymentMethodTokenForOrderReplenishment();
    }

    @Test
    public void shouldForceUnduplicateCartForReplenishmentDuringOrderPlace() throws InvalidCartException {
        CartData cartData = new CartData();
        AddressData addressData = new AddressData();
        DeliveryModeData deliveryModeData = new DeliveryModeData();
        B2BPaymentTypeData paymentTypeData = new B2BPaymentTypeData();
        paymentTypeData.setCode(CheckoutPaymentType.ACCOUNT.getCode());
        cartData.setCalculated(true);
        cartData.setDeliveryAddress(addressData);
        cartData.setDeliveryMode(deliveryModeData);
        cartData.setPaymentType(paymentTypeData);

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cartFacade.hasSessionCart()).thenReturn(true);
        when(cartFacade.getSessionCart()).thenReturn(cartData);
        when(modelService.create(PaymentTransactionEntryModel.class)).thenReturn(paymentTransactionEntryModel);
        when(modelService.create(PaymentTransactionModel.class)).thenReturn(paymentTransactionModel);
        when(deliveryService.getSupportedDeliveryAddressesForOrder(cart, false)).thenReturn(List.of(address));
        when(addressConverter.convert(address)).thenReturn(addressData);
        when(deliveryModeConverter.convert(deliveryMode)).thenReturn(deliveryModeData);
        when(i18NService.getCurrentLocale()).thenReturn(Locale.US);
        when(i18NService.getCurrentTimeZone()).thenReturn(TimeZone.getDefault());
        when(b2bCommerceUnitService.getRootUnit()).thenReturn(b2bUnit);
        List<HybrisEnumValue> paymentTypes = new ArrayList<>();
        paymentTypes.add(ACCOUNT);
        when(enumerationService.getEnumerationValues(CheckoutPaymentType._TYPECODE)).thenReturn(paymentTypes);
        when(b2bPaymentTypeDataConverter.convert(any())).thenReturn(paymentTypeData);

        when(cart.getCode()).thenReturn(CART_CODE);
        when(cart.getPaymentType()).thenReturn(CheckoutPaymentType.ACCOUNT);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(cart.getDeliveryMode()).thenReturn(deliveryMode);
        when(address.getPk()).thenReturn(ADDRESS_PK);

        doReturn(null).when(unit).scheduleOrder(any());

        PlaceOrderData placeOrderData = new PlaceOrderData();
        placeOrderData.setReplenishmentOrder(true);
        placeOrderData.setReplenishmentStartDate(new Date());
        placeOrderData.setReplenishmentRecurrence(DAILY);
        placeOrderData.setNDays("1");

        unit.placeOrder(placeOrderData);

        verify(brainTreePaymentFacade).forceUnduplicateCartForReplenishment(CART_CODE);
    }

    @Test
    public void shouldNotCreateFakeAuthorizationTransactionDuringPlaceOrder() throws InvalidCartException {
        CartData cartData = new CartData();
        AddressData addressData = new AddressData();
        DeliveryModeData deliveryModeData = new DeliveryModeData();
        B2BPaymentTypeData paymentTypeData = new B2BPaymentTypeData();
        paymentTypeData.setCode(CheckoutPaymentType.ACCOUNT.getCode());
        cartData.setCalculated(true);
        cartData.setDeliveryAddress(addressData);
        cartData.setDeliveryMode(deliveryModeData);
        cartData.setPaymentType(paymentTypeData);

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cartFacade.hasSessionCart()).thenReturn(true);
        when(cartFacade.getSessionCart()).thenReturn(cartData);
        when(deliveryService.getSupportedDeliveryAddressesForOrder(cart, false)).thenReturn(List.of(address));
        when(addressConverter.convert(address)).thenReturn(addressData);
        when(deliveryModeConverter.convert(deliveryMode)).thenReturn(deliveryModeData);
        when(b2bCommerceUnitService.getRootUnit()).thenReturn(b2bUnit);
        List<HybrisEnumValue> paymentTypes = new ArrayList<>();
        paymentTypes.add(ACCOUNT);
        when(enumerationService.getEnumerationValues(CheckoutPaymentType._TYPECODE)).thenReturn(paymentTypes);
        when(b2bPaymentTypeDataConverter.convert(any())).thenReturn(paymentTypeData);

        when(cart.getPaymentType()).thenReturn(CheckoutPaymentType.ACCOUNT);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(cart.getDeliveryMode()).thenReturn(deliveryMode);
        when(cart.getUser()).thenReturn(customer);
        when(cart.getPaymentTransactions()).thenReturn(List.of(paymentTransactionModel));
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionEntryModel.getRequestId()).thenReturn(REQUEST_ID);
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(address.getPk()).thenReturn(ADDRESS_PK);

        PlaceOrderData placeOrderData = new PlaceOrderData();

        unit.placeOrder(placeOrderData);

        verify(modelService, never()).create(PaymentTransactionEntryModel.class);
    }

    @Test
    public void shouldCreateFakeAuthorizationTransactionDuringPlaceOrder() throws InvalidCartException {
        CartData cartData = new CartData();
        AddressData addressData = new AddressData();
        DeliveryModeData deliveryModeData = new DeliveryModeData();
        B2BPaymentTypeData paymentTypeData = new B2BPaymentTypeData();
        paymentTypeData.setCode(CheckoutPaymentType.ACCOUNT.getCode());
        cartData.setCalculated(true);
        cartData.setDeliveryAddress(addressData);
        cartData.setDeliveryMode(deliveryModeData);
        cartData.setPaymentType(paymentTypeData);

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cartFacade.hasSessionCart()).thenReturn(true);
        when(cartFacade.getSessionCart()).thenReturn(cartData);
        when(deliveryService.getSupportedDeliveryAddressesForOrder(cart, false)).thenReturn(List.of(address));
        when(addressConverter.convert(address)).thenReturn(addressData);
        when(deliveryModeConverter.convert(deliveryMode)).thenReturn(deliveryModeData);
        when(modelService.create(PaymentTransactionEntryModel.class)).thenReturn(paymentTransactionEntryModel);
        when(modelService.create(PaymentTransactionModel.class)).thenReturn(paymentTransactionModel);
        when(b2bCommerceUnitService.getRootUnit()).thenReturn(b2bUnit);
        List<HybrisEnumValue> paymentTypes = new ArrayList<>();
        paymentTypes.add(ACCOUNT);
        when(enumerationService.getEnumerationValues(CheckoutPaymentType._TYPECODE)).thenReturn(paymentTypes);
        when(b2bPaymentTypeDataConverter.convert(any())).thenReturn(paymentTypeData);

        when(cart.getPaymentType()).thenReturn(CheckoutPaymentType.ACCOUNT);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(cart.getDeliveryMode()).thenReturn(deliveryMode);
        when(cart.getUser()).thenReturn(customer);
        when(cart.getPaymentTransactions()).thenReturn(List.of(paymentTransactionModel));
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionEntryModel.getRequestId()).thenReturn(REQUEST_ID);
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ERROR.name());
        when(address.getPk()).thenReturn(ADDRESS_PK);

        PlaceOrderData placeOrderData = new PlaceOrderData();

        unit.placeOrder(placeOrderData);

        verify(modelService).create(PaymentTransactionEntryModel.class);
        verify(modelService).create(PaymentTransactionModel.class);
    }

    @Test
    public void shouldCreateFakeAuthorizationTransactionDuringPlaceOrderAndSetPaymentInfo() throws InvalidCartException {
        CartData cartData = new CartData();
        AddressData addressData = new AddressData();
        DeliveryModeData deliveryModeData = new DeliveryModeData();
        B2BPaymentTypeData paymentTypeData = new B2BPaymentTypeData();
        paymentTypeData.setCode(CheckoutPaymentType.ACCOUNT.getCode());
        cartData.setCalculated(true);
        cartData.setDeliveryAddress(addressData);
        cartData.setDeliveryMode(deliveryModeData);
        cartData.setPaymentType(paymentTypeData);

        when(cartService.getSessionCart()).thenReturn(cart);
        when(cartFacade.hasSessionCart()).thenReturn(true);
        when(cartFacade.getSessionCart()).thenReturn(cartData);
        when(deliveryService.getSupportedDeliveryAddressesForOrder(cart, false)).thenReturn(List.of(address));
        when(addressConverter.convert(address)).thenReturn(addressData);
        when(deliveryModeConverter.convert(deliveryMode)).thenReturn(deliveryModeData);
        when(modelService.create(PaymentTransactionEntryModel.class)).thenReturn(paymentTransactionEntryModel);
        when(modelService.create(PaymentTransactionModel.class)).thenReturn(paymentTransactionModel);
        when(b2bCommerceUnitService.getRootUnit()).thenReturn(b2bUnit);
        List<HybrisEnumValue> paymentTypes = new ArrayList<>();
        paymentTypes.add(ACCOUNT);
        when(enumerationService.getEnumerationValues(CheckoutPaymentType._TYPECODE)).thenReturn(paymentTypes);
        when(b2bPaymentTypeDataConverter.convert(any())).thenReturn(paymentTypeData);

        when(cart.getPaymentType()).thenReturn(CheckoutPaymentType.ACCOUNT);
        when(cart.getDeliveryAddress()).thenReturn(address);
        when(cart.getDeliveryMode()).thenReturn(deliveryMode);
        when(cart.getUser()).thenReturn(customer);
        when(cart.getPaymentTransactions()).thenReturn(List.of(paymentTransactionModel));
        when(cart.getPaymentInfo()).thenReturn(brainTreePaymentInfo);
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionEntryModel.getRequestId()).thenReturn(REQUEST_ID);
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ERROR.name());
        when(address.getPk()).thenReturn(ADDRESS_PK);

        PlaceOrderData placeOrderData = new PlaceOrderData();

        unit.placeOrder(placeOrderData);

        verify(modelService).create(PaymentTransactionEntryModel.class);
        verify(modelService).create(PaymentTransactionModel.class);
        verify(paymentTransactionModel).setInfo(brainTreePaymentInfo);
    }

    @Test
    public void shouldPlaceOrderByCart() throws InvalidCartException {
        CommerceOrderResult commerceOrderResult = new CommerceOrderResult();
        commerceOrderResult.setOrder(order);
        when(commerceCheckoutService.placeOrder(any(CommerceCheckoutParameter.class))).thenReturn(commerceOrderResult);

        unit.placeOrderByCart(cart);

        verify(orderConverter).convert(order);
        verify(brainTreeCheckoutFacade).beforePlaceOrder(cart);
    }

    @Test
    public void shouldPlaceOrderByCartAndSetPendingQuoteOrderStatus() throws InvalidCartException {
        CommerceOrderResult commerceOrderResult = new CommerceOrderResult();
        commerceOrderResult.setOrder(order);

        when(cart.getB2bcomments()).thenReturn(List.of(b2bComment));
        when(commerceCheckoutService.placeOrder(any(CommerceCheckoutParameter.class))).thenReturn(commerceOrderResult);

        unit.placeOrderByCart(cart);

        verify(orderConverter).convert(order);
        verify(brainTreeCheckoutFacade).beforePlaceOrder(cart);
    }

    @Test
    public void shouldReturnNullIfCartNull() throws InvalidCartException {
        OrderData result = unit.placeOrderByCart(null);

        assertNull(result);
        verify(orderConverter, never()).convert(any());
        verify(brainTreeCheckoutFacade, never()).beforePlaceOrder(any());
    }

    @Test
    public void shouldPlaceOrderForLocalPayment() throws InvalidCartException {
        CommerceOrderResult commerceOrderResult = new CommerceOrderResult();
        commerceOrderResult.setOrder(order);
        when(commerceCheckoutService.placeOrder(any(CommerceCheckoutParameter.class))).thenReturn(commerceOrderResult);

        OrderModel result = unit.placeOrderForLocalPaymentReversed(cart);

        assertEquals(order, result);
    }

    @Test
    public void shouldReturnNullIfCartNullForLocalPayment() throws InvalidCartException {
        OrderModel result = unit.placeOrderForLocalPaymentReversed(null);

        assertNull(result);
    }

    @Test
    public void shouldSetCardPaymentType() {
        when(cartFacade.hasSessionCart()).thenReturn(true);
        when(cartService.getSessionCart()).thenReturn(cart);

        unit.setCardPaymentType();

        verify(cart).setPaymentType(CARD);
        verify(modelService).save(cart);

    }

}