package com.braintree.payment.info.dao;

import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.payment.info.dao.impl.DefaultBrainTreePaymentInfoDao;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.servicelayer.data.SearchPageData;
import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import de.hybris.platform.servicelayer.search.SearchResult;
import de.hybris.platform.servicelayer.search.paginated.PaginatedFlexibleSearchParameter;
import de.hybris.platform.servicelayer.search.paginated.PaginatedFlexibleSearchService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

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

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

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

    private static final String PAYMENT_METHOD_TOKEN = "paymentMethodToken";
    private static final String CODE = "code";
    private static final String DUPLICATE = "duplicate";
    private static final String CUSTOMER_ID = "customerId";
    private static final int COUNT_ZERO = 0;
    private static final String IS_DELETED_ON_BRAINTREE = "isDeletedOnBraintree";
    private static final String SAVED = "saved";
    private static final String TYPE = "type";
    private static final int COUNT = 1;

    private static final String SEARCH_QUERY = "SELECT {pk} FROM {BrainTreePaymentInfo} WHERE {customerId}=?customerId "
            + "AND {paymentMethodToken}=?paymentMethodToken AND {duplicate}=?duplicate";

    private static final String GET_PAYMENT_INFO_MODEL_BY_CUSTOMER_ID_CODE_QUERY = "SELECT {"
            + BrainTreePaymentInfoModel.PK + "} FROM {" + BrainTreePaymentInfoModel._TYPECODE + "} WHERE {"
            + BrainTreePaymentInfoModel.CUSTOMERID + "}=?" + CUSTOMER_ID + " AND {"
            + BrainTreePaymentInfoModel.PK + "}=?" + CODE + " AND {"
            + BrainTreePaymentInfoModel.DUPLICATE + "}=?" + DUPLICATE;

    private static final String GET_CREDIT_CARD_PAYMENT_INFO_MODELS = "SELECT {"
            + BrainTreePaymentInfoModel.PK + "} FROM {" + BrainTreePaymentInfoModel._TYPECODE + "}" + " WHERE {"
            + BrainTreePaymentInfoModel.PAYMENTPROVIDER + "}=?" + TYPE + " AND {"
            + BrainTreePaymentInfoModel.SAVED + "}=?" + SAVED + " AND {"
            + BrainTreePaymentInfoModel.DUPLICATE + "}=?" + DUPLICATE + " AND {"
            + BrainTreePaymentInfoModel.ISDELETEDONBRAINTREE + "}=?" + IS_DELETED_ON_BRAINTREE;

    private static final String GET_CREDIT_CARD_PAYMENT_INFO_MODELS_FOR_CUSTOMER = "SELECT {"
            + BrainTreePaymentInfoModel.PK + "} FROM {" + BrainTreePaymentInfoModel._TYPECODE + "}" + " WHERE {"
            + BrainTreePaymentInfoModel.PAYMENTPROVIDER + "}=?" + TYPE + " AND {"
            + BrainTreePaymentInfoModel.SAVED + "}=?" + SAVED + " AND {"
            + BrainTreePaymentInfoModel.DUPLICATE + "}=?" + DUPLICATE + " AND {"
            + BrainTreePaymentInfoModel.CUSTOMERID + "}=?" + CUSTOMER_ID + " AND {"
            + BrainTreePaymentInfoModel.ISDELETEDONBRAINTREE + "}=?" + IS_DELETED_ON_BRAINTREE;

    private static final String GET_PAYMENT_INFO_COUNT_BY_CUSTOMER_AND_PAYMENT_PROVIDER = "SELECT COUNT({"
            + BrainTreePaymentInfoModel.PK + "}) FROM {" + BrainTreePaymentInfoModel._TYPECODE + "}" + " WHERE {"
            + BrainTreePaymentInfoModel.PAYMENTPROVIDER + "}=?" + TYPE + " AND {"
            + BrainTreePaymentInfoModel.SAVED + "}=?" + SAVED + " AND {"
            + BrainTreePaymentInfoModel.DUPLICATE + "}=?" + DUPLICATE + " AND {"
            + BrainTreePaymentInfoModel.CUSTOMERID + "}=?" + CUSTOMER_ID;

    @Mock
    private FlexibleSearchService flexibleSearchService;

    @Mock
    private PaginatedFlexibleSearchService paginatedFlexibleSearchService;

    @Mock
    private BrainTreePaymentInfoModel expectedBrainTreePaymentInfoModel;

    @Mock
    private SearchResult searchResult;

    @InjectMocks
    private final BrainTreePaymentInfoDao unit = new DefaultBrainTreePaymentInfoDao();

    @Test
    public void shouldFindBrainTreePaymentInfo() {
        // given
        final Map queryParams = new HashMap();
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);
        queryParams.put(PAYMENT_METHOD_TOKEN, PAYMENT_METHOD_TOKEN);
        queryParams.put(DUPLICATE, Boolean.FALSE);
        when(flexibleSearchService.search(SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(Integer.valueOf(searchResult.getCount())).thenReturn(Integer.valueOf(COUNT));
        when(searchResult.getResult()).thenReturn(List.of(expectedBrainTreePaymentInfoModel));

        // when
        final BrainTreePaymentInfoModel actualBrainTreePaymentInfoModel = unit
                .find(CUSTOMER_ID,
                        PAYMENT_METHOD_TOKEN);

        // then
        assertEquals(expectedBrainTreePaymentInfoModel, actualBrainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotFindBrainTreePaymentInfo() {
        // given
        final Map queryParams = new HashMap();
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);
        queryParams.put(PAYMENT_METHOD_TOKEN, PAYMENT_METHOD_TOKEN);
        queryParams.put(DUPLICATE, Boolean.FALSE);
        when(flexibleSearchService.search(SEARCH_QUERY, queryParams)).thenReturn(searchResult);
        when(Integer.valueOf(searchResult.getCount())).thenReturn(Integer.valueOf(COUNT_ZERO));

        // when
        final BrainTreePaymentInfoModel actualBrainTreePaymentInfoModel = unit
                .find(CUSTOMER_ID,
                        PAYMENT_METHOD_TOKEN);

        // then
        assertNull(actualBrainTreePaymentInfoModel);
    }

    @Test
    public void shouldNotFindBrainfTreePaymentInfoByToken() {
        final List<BrainTreePaymentInfoModel> paymentInfoModels = new ArrayList<>();
        paymentInfoModels.add(expectedBrainTreePaymentInfoModel);
        final Map queryParams = new HashMap();
        queryParams.put(PAYMENT_METHOD_TOKEN, PAYMENT_METHOD_TOKEN);
        queryParams.put(DUPLICATE, Boolean.FALSE);

        when(flexibleSearchService.search("SELECT {pk} FROM {BrainTreePaymentInfo} WHERE "
                + "{paymentMethodToken}=?paymentMethodToken AND {duplicate}=?duplicate", queryParams))
                .thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT);
        when(searchResult.getResult()).thenReturn(paymentInfoModels);

        assertEquals(expectedBrainTreePaymentInfoModel, unit.find(PAYMENT_METHOD_TOKEN));
    }

    @Test
    public void shouldNotFindBrainTreePaymentInfoByToken() {
        final Map queryParams = new HashMap();
        queryParams.put(PAYMENT_METHOD_TOKEN, PAYMENT_METHOD_TOKEN);
        queryParams.put(DUPLICATE, Boolean.FALSE);

        when(flexibleSearchService.search("SELECT {pk} FROM {BrainTreePaymentInfo} WHERE "
                + "{paymentMethodToken}=?paymentMethodToken AND {duplicate}=?duplicate", queryParams))
                .thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(COUNT_ZERO);

        assertNull(unit.find(PAYMENT_METHOD_TOKEN));
    }

    @Test
    public void shouldFindBrainTreePaymentInfoByCustomerIdAndCode() {
        // given
        final Map queryParams = new HashMap();
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);
        queryParams.put(CODE, CODE);
        queryParams.put(DUPLICATE, Boolean.FALSE);

        when(flexibleSearchService.search(GET_PAYMENT_INFO_MODEL_BY_CUSTOMER_ID_CODE_QUERY, queryParams))
                .thenReturn(searchResult);
        when(Integer.valueOf(searchResult.getCount())).thenReturn(Integer.valueOf(COUNT));
        when(searchResult.getResult()).thenReturn(List.of(expectedBrainTreePaymentInfoModel));

        // when
        final Optional<BrainTreePaymentInfoModel> actualBrainTreePaymentInfoModel = unit
                .findByCustomerIdAndCode(CUSTOMER_ID,
                        CODE);

        // then
        assertTrue(actualBrainTreePaymentInfoModel.isPresent());
        assertEquals(expectedBrainTreePaymentInfoModel, actualBrainTreePaymentInfoModel.get());
    }

    @Test
    public void shouldNotFindBrainTreePaymentInfoByCustomerIdAndCode() {
        // given
        final Map queryParams = new HashMap();
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);
        queryParams.put(CODE, CODE);
        queryParams.put(DUPLICATE, Boolean.FALSE);

        when(flexibleSearchService.search(GET_PAYMENT_INFO_MODEL_BY_CUSTOMER_ID_CODE_QUERY, queryParams)).thenReturn(searchResult);
        when(Integer.valueOf(searchResult.getCount())).thenReturn(Integer.valueOf(COUNT_ZERO));

        // when
        final Optional<BrainTreePaymentInfoModel> actualBrainTreePaymentInfoModel =
                unit.findByCustomerIdAndCode(CUSTOMER_ID, CODE);

        // then
        assertTrue(actualBrainTreePaymentInfoModel.isEmpty());
    }

    @Test
    public void shouldFindAllSavedCreditCards() {
        // given
        final SearchPageData searchResult = new SearchPageData();
        final SearchPageData<BrainTreePaymentInfoModel> searchPageData = new SearchPageData<>();
        final Map queryParams = new HashMap();
        queryParams.put(TYPE, BrainTreePaymentMethod.CREDITCARD.getCode());
        queryParams.put(SAVED, Boolean.TRUE);
        queryParams.put(DUPLICATE, Boolean.FALSE);
        queryParams.put(IS_DELETED_ON_BRAINTREE, Boolean.FALSE);

        ArgumentCaptor<PaginatedFlexibleSearchParameter> paginatedFlexibleSearchParameter =
                ArgumentCaptor.forClass(PaginatedFlexibleSearchParameter.class);

        when(paginatedFlexibleSearchService.search(any())).thenReturn(searchResult);

        // when
        final SearchPageData<BrainTreePaymentInfoModel> result = unit.findAllSavedCreditCard(searchPageData);

        // then
        assertNotNull(result);
        verify(paginatedFlexibleSearchService).search(paginatedFlexibleSearchParameter.capture());
        assertEquals(GET_CREDIT_CARD_PAYMENT_INFO_MODELS,
                paginatedFlexibleSearchParameter.getValue().getFlexibleSearchQuery().getQuery());
        assertEquals(queryParams,
                paginatedFlexibleSearchParameter.getValue().getFlexibleSearchQuery().getQueryParameters());
    }

    @Test
    public void shouldFindAllSavedCreditCardsByCustomerId() {
        // given
        final Map queryParams = new HashMap();
        final List list = List.of(expectedBrainTreePaymentInfoModel);
        queryParams.put(TYPE, BrainTreePaymentMethod.CREDITCARD.getCode());
        queryParams.put(SAVED, Boolean.TRUE);
        queryParams.put(DUPLICATE, Boolean.FALSE);
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);
        queryParams.put(IS_DELETED_ON_BRAINTREE, Boolean.FALSE);

        ArgumentCaptor<FlexibleSearchQuery> flexibleSearchQueryArgumentCaptor =
                ArgumentCaptor.forClass(FlexibleSearchQuery.class);

        when(searchResult.getResult()).thenReturn(list);
        when(flexibleSearchService.search(any(FlexibleSearchQuery.class))).thenReturn(searchResult);

        // when
        List<BrainTreePaymentInfoModel> result = unit.findAllSavedCreditCardByCustomer(CUSTOMER_ID);

        // then
        verify(flexibleSearchService).search(flexibleSearchQueryArgumentCaptor.capture());
        assertEquals(queryParams, flexibleSearchQueryArgumentCaptor.getValue().getQueryParameters());
        assertEquals(GET_CREDIT_CARD_PAYMENT_INFO_MODELS_FOR_CUSTOMER,
                flexibleSearchQueryArgumentCaptor.getValue().getQuery());
        assertEquals(list, result);
    }

    @Test
    public void shouldCountPaymentInfoByCustomerIdAndPaymentProvider() {
        // given
        final Map queryParams = new HashMap();
        queryParams.put(TYPE, BrainTreePaymentMethod.PAYPAL.getCode());
        queryParams.put(SAVED, Boolean.TRUE);
        queryParams.put(DUPLICATE, Boolean.FALSE);
        queryParams.put(CUSTOMER_ID, CUSTOMER_ID);

        ArgumentCaptor<FlexibleSearchQuery> flexibleSearchQueryArgumentCaptor =
                ArgumentCaptor.forClass(FlexibleSearchQuery.class);

        when(flexibleSearchService.searchUnique(any(FlexibleSearchQuery.class))).thenReturn(COUNT);

        // when
        int result = unit.countPaymentInfoByCustomerIdAndPaymentProvider(CUSTOMER_ID, BrainTreePaymentMethod.PAYPAL);

        // then
        verify(flexibleSearchService).searchUnique(flexibleSearchQueryArgumentCaptor.capture());
        assertEquals(queryParams, flexibleSearchQueryArgumentCaptor.getValue().getQueryParameters());
        assertEquals(GET_PAYMENT_INFO_COUNT_BY_CUSTOMER_AND_PAYMENT_PROVIDER,
                flexibleSearchQueryArgumentCaptor.getValue().getQuery());
        assertEquals(COUNT, result);
    }

}
