package com.braintree.customer.dao;

import com.braintree.model.BrainTreePaymentInfoModel;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.PK;
import de.hybris.platform.core.model.order.payment.PaymentInfoModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.servicelayer.exceptions.ModelNotFoundException;
import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import de.hybris.platform.servicelayer.search.SearchResult;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


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

    private static final String CUSTOMER_ID = "CUSTOMER_ID";
    private static final String PAYER_ID = "PAYER_ID";
    private static final String PAYER_ID_KEY = "payPalPayerId";
    private static final String CODE = "1";
    private static final String BT_PAYMENT_INFO_SEARCH_QUERY = "SELECT {pk} FROM {BrainTreePaymentInfo} WHERE {user} = ?customer AND {pk} = ?pk";
    private static final String PAYMENT_INFO_SEARCH_QUERY = "SELECT {pk} FROM {PaymentInfo} WHERE {user} = ?customer AND {pk} = ?pk AND {duplicate} = ?duplicate";
    private static final String CUSTOMER_SEARCH_QUERY = "SELECT {pk} FROM {Customer} WHERE {braintreeCustomerId} = ?customerId";
    private static final String BT_PAYMENT_INFO_LIST_SEARCH_QUERY_NOT_SAVED = "SELECT {pk} FROM {BrainTreePaymentInfo} WHERE {user} = ?customer AND {duplicate} = ?duplicate "
            + "AND {isDeletedOnBraintree} = ?isDeletedOnBraintree";
    private static final String SAVED_BT_PAYMENT_INFO_LIST_SEARCH_QUERY = "SELECT {pk} FROM {BrainTreePaymentInfo} WHERE {user} = ?customer AND {saved} = ?saved AND {duplicate} = ?duplicate AND {isDeletedOnBraintree} = ?isDeletedOnBraintree";
    private static final String CUSTOMER_BY_PAYPALPAYERID_QUERY = "SELECT {pk} FROM {Customer} WHERE {payPalPayerId} = ?payPalPayerId";


    private static final Boolean DUPLICATE = Boolean.FALSE;
    private static final int COUNT = 1;
    private static final int COUNT_ZERO = 0;
    private static final boolean SAVED = true;
    private static final boolean NOT_SAVED = false;

    @Mock
    private FlexibleSearchService flexibleSearchService;

    @InjectMocks
    private BrainTreeCustomerAccountDao unit;

    @Mock
    private BrainTreePaymentInfoModel paymentInfoModel;
    @Mock
    private CustomerModel customerModel;
    @Mock
    private SearchResult searchResult;

    @Captor
    private ArgumentCaptor<FlexibleSearchQuery> flexibleSearchQueryCaptor;

    @Test
    public void shouldFindBrainTreePaymentInfoByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "pk", PK.parse(CODE)
        );

        when(flexibleSearchService.search(BT_PAYMENT_INFO_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT);
        when(searchResult.getResult()).thenReturn(List.of(paymentInfoModel));

        // when
        final BrainTreePaymentInfoModel actualBrainTreePaymentInfoModel = unit
                .findBrainTreePaymentInfoByCustomer(customerModel, CODE);

        // then
        assertEquals(paymentInfoModel, actualBrainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotFindBrainTreePaymentInfoByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "pk", PK.parse(CODE)
        );
        when(flexibleSearchService.search(BT_PAYMENT_INFO_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT_ZERO);

        // when
        final BrainTreePaymentInfoModel actualBrainTreePaymentInfoModel = unit
                .findBrainTreePaymentInfoByCustomer(customerModel, CODE);

        // then
        assertNull(actualBrainTreePaymentInfoModel);
    }

    @Test
    public void shouldFindPaymentInfoByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "pk", PK.parse(CODE),
                "duplicate", DUPLICATE
        );

        when(flexibleSearchService.search(PAYMENT_INFO_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT);
        when(searchResult.getResult()).thenReturn(List.of(paymentInfoModel));

        // when
        final PaymentInfoModel actualPaymentInfoModel = unit
                .findPaymentInfoByCustomer(customerModel, CODE);

        // then
        assertEquals(paymentInfoModel, actualPaymentInfoModel);
    }

    @Test
    public void shouldNotFindPaymentInfoByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "pk", PK.parse(CODE),
                "duplicate", DUPLICATE
        );
        when(flexibleSearchService.search(PAYMENT_INFO_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT_ZERO);

        // when
        final PaymentInfoModel actualPaymentInfoModel = unit
                .findPaymentInfoByCustomer(customerModel, CODE);

        // then
        assertNull(actualPaymentInfoModel);
    }

    @Test
    public void shouldFindSavedBrainTreePaymentInfosByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "saved", SAVED,
                "duplicate", DUPLICATE,
                "isDeletedOnBraintree", Boolean.FALSE
        );

        when(flexibleSearchService
                .search(SAVED_BT_PAYMENT_INFO_LIST_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getResult()).thenReturn(List.of(paymentInfoModel));

        // when
        final List<BrainTreePaymentInfoModel> actualBrainTreePaymentInfoModels = unit
                .findBrainTreePaymentInfosByCustomer(customerModel, SAVED);

        // then
        assertTrue(actualBrainTreePaymentInfoModels.contains(paymentInfoModel));
    }

    @Test
    public void shouldFindNotSavedBrainTreePaymentInfosByCustomer() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customer", customerModel,
                "duplicate", DUPLICATE,
                "isDeletedOnBraintree", Boolean.FALSE
        );

        when(flexibleSearchService
                .search(BT_PAYMENT_INFO_LIST_SEARCH_QUERY_NOT_SAVED, queryParams)).thenReturn(searchResult);
        when(searchResult.getResult()).thenReturn(List.of(paymentInfoModel));

        // when
        final List<BrainTreePaymentInfoModel> actualBrainTreePaymentInfoModels = unit
                .findBrainTreePaymentInfosByCustomer(customerModel, NOT_SAVED);

        // then
        assertTrue(actualBrainTreePaymentInfoModels.contains(paymentInfoModel));
    }

    @Test
    public void shouldFindCustomerByBrainTreeCustomerId() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customerId", CUSTOMER_ID
        );

        when(flexibleSearchService.search(CUSTOMER_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT);
        when(searchResult.getResult()).thenReturn(List.of(customerModel));

        // when
        final CustomerModel actualCustomerModel = unit
                .findCustomerByBrainTreeCustomerId(CUSTOMER_ID);

        // then
        assertEquals(customerModel, actualCustomerModel);
    }

    @Test
    public void shouldNotFindCustomerByBrainTreeCustomerId() {
        // given
        final Map<String, Object> queryParams = Map.of(
                "customerId", CUSTOMER_ID
        );

        when(flexibleSearchService.search(CUSTOMER_SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT_ZERO);

        // when
        final CustomerModel actualCustomerModel = unit
                .findCustomerByBrainTreeCustomerId(CUSTOMER_ID);

        // then
        assertNull(actualCustomerModel);
    }

    @Test
    public void shouldFindCustomerByPayPalPayerId() {
        when(flexibleSearchService.searchUnique(any(FlexibleSearchQuery.class))).thenReturn(customerModel);

        Optional<CustomerModel> result = unit.findCustomerByPayPalPayerId(PAYER_ID);

        assertTrue(result.isPresent());
        verify(flexibleSearchService).searchUnique(flexibleSearchQueryCaptor.capture());
        FlexibleSearchQuery value = flexibleSearchQueryCaptor.getValue();
        assertEquals(CUSTOMER_BY_PAYPALPAYERID_QUERY, value.getQuery());
        Map<String, Object> queryParameters = value.getQueryParameters();
        assertTrue(queryParameters.containsKey(PAYER_ID_KEY));
        assertEquals(PAYER_ID, queryParameters.get(PAYER_ID_KEY));
    }

    @Test
    public void shouldNotFindCustomerByPayPalPayerId() {
        when(flexibleSearchService.searchUnique(any(FlexibleSearchQuery.class))).thenThrow(new ModelNotFoundException("ModelNotFoundException"));

        Optional<CustomerModel> result = unit.findCustomerByPayPalPayerId(PAYER_ID);

        assertFalse(result.isPresent());
        verify(flexibleSearchService).searchUnique(flexibleSearchQueryCaptor.capture());
        FlexibleSearchQuery value = flexibleSearchQueryCaptor.getValue();
        assertEquals(CUSTOMER_BY_PAYPALPAYERID_QUERY, value.getQuery());
        Map<String, Object> queryParameters = value.getQueryParameters();
        assertTrue(queryParameters.containsKey(PAYER_ID_KEY));
        assertEquals(PAYER_ID, queryParameters.get(PAYER_ID_KEY));
    }

}
