package com.braintree.delivery.dao.impl;

import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.model.c2l.CountryModel;
import de.hybris.platform.core.model.c2l.CurrencyModel;
import de.hybris.platform.core.model.order.AbstractOrderModel;
import de.hybris.platform.core.model.order.delivery.DeliveryModeModel;
import de.hybris.platform.deliveryzone.model.ZoneDeliveryModeValueModel;
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.store.BaseStoreModel;
import de.hybris.platform.util.PriceValue;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

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

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final Double SUBTOTAL_AMOUNT = 15.0;
    private static final Double DELIVERY_AMOUNT = 10.0;
    private static final String CURRENCY_ISO_CODE = "USD";
    private static final String EUR = "EUR";
    private static final String USD = "USD";

    @Mock
    protected FlexibleSearchService flexibleSearchService;
    @Mock
    private SearchResult searchResult;
    @Mock
    private AbstractOrderModel abstractOrder;
    @Mock
    private BaseStoreModel storeModel;
    @Mock
    private CurrencyModel currencyModel;
    @Mock
    private CountryModel countryModel;
    @Mock
    private DeliveryModeModel deliveryModeModel;
    @Mock
    private CurrencyModel anotherCurrency;

    @InjectMocks
    private DefaultBraintreeCountyZoneDeliveryModeDao unit;

    @Test
    public void findDeliveryModesShouldGetArraysListOfModes() {
        when(abstractOrder.getStore()).thenReturn(storeModel);
        when(abstractOrder.getCurrency()).thenReturn(currencyModel);

        final Map<String, Object> params = new HashMap<>();
        params.put("deliveryCountry", countryModel);
        params.put("currency", abstractOrder.getCurrency());
        params.put("net", abstractOrder.getNet());
        params.put("active", Boolean.TRUE);
        params.put("store", abstractOrder.getStore());

        final FlexibleSearchQuery fQuery = new FlexibleSearchQuery(
                "SELECT DISTINCT {zdm:pk} FROM { ZoneDeliveryModeValue AS val JOIN ZoneDeliveryMode AS zdm "
                        + "ON {val:deliveryMode}={zdm:pk} JOIN ZoneCountryRelation AS z2c ON {val:zone}={z2c:source} "
                        + "JOIN BaseStore2DeliveryModeRel AS s2d ON {val:deliveryMode}={s2d:target} } WHERE {val:currency}=?currency "
                        + "AND {z2c:target}=?deliveryCountry AND {s2d:source}=?store AND {zdm:net}=?net AND {zdm:active}=?active");
        fQuery.addQueryParameters(params);
        fQuery.setResultClassList(Collections.singletonList(DeliveryModeModel.class));

        List<DeliveryModeModel> deliveryModeModels = new ArrayList<>();
        when(searchResult.getResult()).thenReturn(deliveryModeModels);

        when(flexibleSearchService.search(fQuery)).thenReturn(searchResult);

        Assert.assertEquals(deliveryModeModels, unit.findDeliveryModes(abstractOrder, countryModel));
        verify(flexibleSearchService).search(fQuery);
    }

    @Test
    public void countPriceForDeliveryModeShouldReturnCorrectPrice() {
        when(abstractOrder.getSubtotal()).thenReturn(SUBTOTAL_AMOUNT);
        when(abstractOrder.getCurrency()).thenReturn(currencyModel);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("deliveryMode", deliveryModeModel);
        parameters.put("curr", currencyModel);
        parameters.put("country", countryModel);
        parameters.put("amount", SUBTOTAL_AMOUNT);

        String query = "SELECT {v.pk} FROM {ZoneDeliveryModeValue AS v JOIN ZoneCountryRelation AS z2cRel "
                + "ON {v.zone}={z2cRel.source} } WHERE {v.deliveryMode} = ?deliveryMode AND {v.currency} = ?curr "
                + "AND {v.minimum} <= ?amount AND {z2cRel.target} = ?country ORDER BY {v.minimum} DESC ";

        ZoneDeliveryModeValueModel bestMatch = mock(ZoneDeliveryModeValueModel.class);
        when(bestMatch.getCurrency()).thenReturn(currencyModel);
        when(bestMatch.getValue()).thenReturn(DELIVERY_AMOUNT);
        when(currencyModel.getIsocode()).thenReturn(CURRENCY_ISO_CODE);

        List<ZoneDeliveryModeValueModel> resultList = new ArrayList<>();
        resultList.add(bestMatch);
        when(searchResult.getCount()).thenReturn(1);
        when(searchResult.getResult()).thenReturn(resultList);
        when(flexibleSearchService.search(query, parameters)).thenReturn(searchResult);

        PriceValue excepted = new PriceValue(CURRENCY_ISO_CODE, DELIVERY_AMOUNT, Boolean.FALSE);

        Assert.assertEquals(excepted, unit.countPriceForDeliveryMode(deliveryModeModel, abstractOrder, countryModel));
    }

    @Test
    public void countPriceForDeliveryModeShouldReturnNullWhenSearchResultIsEmpty() {
        when(abstractOrder.getSubtotal()).thenReturn(SUBTOTAL_AMOUNT);
        when(abstractOrder.getCurrency()).thenReturn(currencyModel);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("deliveryMode", deliveryModeModel);
        parameters.put("curr", currencyModel);
        parameters.put("country", countryModel);
        parameters.put("amount", SUBTOTAL_AMOUNT);

        String query = "SELECT {v.pk} FROM {ZoneDeliveryModeValue AS v JOIN ZoneCountryRelation AS z2cRel "
                + "ON {v.zone}={z2cRel.source} } WHERE {v.deliveryMode} = ?deliveryMode AND {v.currency} = ?curr "
                + "AND {v.minimum} <= ?amount AND {z2cRel.target} = ?country ORDER BY {v.minimum} DESC ";

        when(flexibleSearchService.search(query, parameters)).thenReturn(searchResult);
        when(searchResult.getCount()).thenReturn(0);

        Assert.assertNull(unit.countPriceForDeliveryMode(deliveryModeModel, abstractOrder, countryModel));
    }

    @Test
    public void countPriceForDeliveryModeShouldReturnNullWhenCurrencyIsNull() {
        when(abstractOrder.getSubtotal()).thenReturn(SUBTOTAL_AMOUNT);
        when(abstractOrder.getCurrency()).thenReturn(null);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("deliveryMode", deliveryModeModel);
        parameters.put("curr", null);
        parameters.put("country", countryModel);
        parameters.put("amount", SUBTOTAL_AMOUNT);

        String query = "SELECT {v.pk} FROM {ZoneDeliveryModeValue AS v JOIN ZoneCountryRelation AS z2cRel "
                + "ON {v.zone}={z2cRel.source} } WHERE {v.deliveryMode} = ?deliveryMode AND {v.currency} = ?curr "
                + "AND {v.minimum} <= ?amount AND {z2cRel.target} = ?country ORDER BY {v.minimum} DESC ";

        ZoneDeliveryModeValueModel bestMatch = mock(ZoneDeliveryModeValueModel.class);

        List<ZoneDeliveryModeValueModel> resultList = new ArrayList<>();
        resultList.add(bestMatch);
        when(searchResult.getCount()).thenReturn(1);
        when(searchResult.getResult()).thenReturn(resultList);
        when(flexibleSearchService.search(query, parameters)).thenReturn(searchResult);

        Assert.assertNull(unit.countPriceForDeliveryMode(deliveryModeModel, abstractOrder, countryModel));
    }

    @Test
    public void countPriceForDeliveryModeShouldReturnNullWhenSearchCurrencyResultIsNull() {
        when(abstractOrder.getSubtotal()).thenReturn(SUBTOTAL_AMOUNT);
        when(abstractOrder.getCurrency()).thenReturn(currencyModel);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("deliveryMode", deliveryModeModel);
        parameters.put("curr", currencyModel);
        parameters.put("country", countryModel);
        parameters.put("amount", SUBTOTAL_AMOUNT);

        String query = "SELECT {v.pk} FROM {ZoneDeliveryModeValue AS v JOIN ZoneCountryRelation AS z2cRel "
                + "ON {v.zone}={z2cRel.source} } WHERE {v.deliveryMode} = ?deliveryMode AND {v.currency} = ?curr "
                + "AND {v.minimum} <= ?amount AND {z2cRel.target} = ?country ORDER BY {v.minimum} DESC ";

        ZoneDeliveryModeValueModel bestMatch = mock(ZoneDeliveryModeValueModel.class);

        List<ZoneDeliveryModeValueModel> resultList = new ArrayList<>();
        resultList.add(bestMatch);
        when(searchResult.getCount()).thenReturn(1);
        when(searchResult.getResult()).thenReturn(resultList);
        when(flexibleSearchService.search(query, parameters)).thenReturn(searchResult);
        when(bestMatch.getCurrency()).thenReturn(null);

        Assert.assertNull(unit.countPriceForDeliveryMode(deliveryModeModel, abstractOrder, countryModel));
    }

    @Test
    public void countPriceForDeliveryModeShouldReturnNullWhenSearchCurrencyIsNotEquals() {
        when(abstractOrder.getSubtotal()).thenReturn(SUBTOTAL_AMOUNT);
        when(abstractOrder.getCurrency()).thenReturn(currencyModel);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("deliveryMode", deliveryModeModel);
        parameters.put("curr", currencyModel);
        parameters.put("country", countryModel);
        parameters.put("amount", SUBTOTAL_AMOUNT);

        String query = "SELECT {v.pk} FROM {ZoneDeliveryModeValue AS v JOIN ZoneCountryRelation AS z2cRel "
                + "ON {v.zone}={z2cRel.source} } WHERE {v.deliveryMode} = ?deliveryMode AND {v.currency} = ?curr "
                + "AND {v.minimum} <= ?amount AND {z2cRel.target} = ?country ORDER BY {v.minimum} DESC ";

        ZoneDeliveryModeValueModel bestMatch = mock(ZoneDeliveryModeValueModel.class);

        List<ZoneDeliveryModeValueModel> resultList = new ArrayList<>();
        resultList.add(bestMatch);
        when(searchResult.getCount()).thenReturn(1);
        when(searchResult.getResult()).thenReturn(resultList);
        when(flexibleSearchService.search(query, parameters)).thenReturn(searchResult);
        when(bestMatch.getCurrency()).thenReturn(anotherCurrency);
        when(anotherCurrency.getIsocode()).thenReturn(USD);
        when(currencyModel.getIsocode()).thenReturn(EUR);

        Assert.assertNull(unit.countPriceForDeliveryMode(deliveryModeModel, abstractOrder, countryModel));
    }

}
