package io.delphiplatform.api.v3.rdb.service.ads;

import com.healthmarketscience.sqlbuilder.CommonTableExpression;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;

import java.time.LocalDate;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;

import io.delphiplatform.api.exception.InvalidParameterException;
import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.OffsetBasedPageRequest;
import io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance;
import io.delphiplatform.api.v3.rdb.service.ads.query.sqlbuilder.AdsReportRequestSortingService;
import io.delphiplatform.api.v3.rdb.service.dto.AdsReportQueryFilterParams;
import io.delphiplatform.api.v3.view.util.Params;
import io.delphiplatform.api.v3.view.util.Params.ParamsBuilder;

import static io.delphiplatform.api.v3.constant.ExpressionConstants.CAMPAIGN_EXPRESSION_NAME;
import static io.delphiplatform.api.v3.constant.ExpressionConstants.LINK_EXPRESSION_NAME;
import static io.delphiplatform.api.v3.constant.ExpressionConstants.PROJECT_EXPRESSION_NAME;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

class AdsPerformanceSummaryItemsServiceTest {

    public static final LocalDate START_DATE = LocalDate.of(2020, 1, 1);
    public static final LocalDate END_DATE = LocalDate.of(2020, 1, 10);
    public static final int USER_ID = 10;
    public static final String CAMP_1 = "CAMP1";
    public static final String PROJ_1 = "PROJ_1";
    public static final UUID LINK_1 = UUID.randomUUID();
    private AdsPerformanceSummaryItemsService service;
    private CommonTableExpression linksCte;
    private CommonTableExpression campaignsCte;
    private CommonTableExpression decibelProjectsCte;

    @Mock
    private AdsCampaignService campaignService;
    @Mock
    private AdsMetricService metricService;
    @Mock
    private DecibelProjectService decibelProjectService;
    @Mock
    private LinkfireDimLinkService linkService;
    @Captor
    private ArgumentCaptor<AdsReportQueryFilterParams> filterParamsArgumentCaptor;

    @BeforeEach
    public void setUp() {
        initMocks(this);
        AdsMetricsQueryMappingInfoProvider adsMetricsQueryMappingInfoProvider = new AdsMetricsQueryMappingInfoProvider();
        AdsReportRequestSortingService sortingService = new AdsReportRequestSortingService(
            adsMetricsQueryMappingInfoProvider);
        service = new AdsPerformanceSummaryItemsService(campaignService, metricService, decibelProjectService,
            linkService, sortingService);

        linksCte = new CommonTableExpression(LINK_EXPRESSION_NAME).setQuery("select 1");
        campaignsCte = new CommonTableExpression(CAMPAIGN_EXPRESSION_NAME).setQuery("select 1");
        decibelProjectsCte = new CommonTableExpression(PROJECT_EXPRESSION_NAME).setQuery("select 1");
    }

    @Test
    void getModels_groupByProjectNoFilterUsingCte() {
        Params params = getParams(GroupByAdsPerformance.PROJECT, null);
        Params paramsForCampaigns = params.toBuilder()
            .considerConfidential(true)
            .projectIds(Collections.emptySet())
            .build();

        when(campaignService.findCampaignIdsExpression(paramsForCampaigns))
            .thenReturn(campaignsCte);
        when(linkService.findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), decibelProjectsCte.getTable()))
            .thenReturn(linksCte);
        when(decibelProjectService.findProjectsIdsExpression(USER_ID, null))
            .thenReturn(decibelProjectsCte);

        service.getModels(params);

        verify(campaignService).findCampaignIdsExpression(paramsForCampaigns);
        verify(decibelProjectService).findProjectsIdsExpression(USER_ID, null);
        verify(linkService).findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), decibelProjectsCte.getTable());

        verify(metricService).getMetrics(
            filterParamsArgumentCaptor.capture(),
            eq(Set.of(GroupByAdsPerformance.PROJECT)),
            eq(OffsetBasedPageRequest.ofParamsKeepCase(0, 5000))
        );

        AdsReportQueryFilterParams filterParams = filterParamsArgumentCaptor.getValue();
        Assertions.assertEquals(START_DATE, filterParams.getStartDate());
        Assertions.assertEquals(END_DATE, filterParams.getEndDate());
        Assertions.assertEquals(campaignsCte, filterParams.getCampaignIdsCte());
        Assertions.assertEquals(linksCte, filterParams.getLinkIdsCte());
        Assertions.assertEquals(decibelProjectsCte, filterParams.getProjectIdsCte());
    }

    @Test
    void getModels_groupByCampaignNoFilterUsingCte() {
        Params params = getParams(GroupByAdsPerformance.CAMPAIGN, null);
        Params paramsForCampaigns = params.toBuilder()
            .considerConfidential(true)
            .projectIds(Collections.emptySet())
            .build();

        when(campaignService.findCampaignIdsExpression(paramsForCampaigns))
            .thenReturn(campaignsCte);
        when(linkService.findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), null))
            .thenReturn(linksCte);

        service.getModels(params);

        verify(campaignService).findCampaignIdsExpression(paramsForCampaigns);
        verify(linkService).findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), null);

        verify(metricService).getMetrics(
            filterParamsArgumentCaptor.capture(),
            eq(Set.of(GroupByAdsPerformance.CAMPAIGN)),
            eq(OffsetBasedPageRequest.ofParamsKeepCase(0, 5000))
        );

        AdsReportQueryFilterParams filterParams = filterParamsArgumentCaptor.getValue();
        Assertions.assertEquals(START_DATE, filterParams.getStartDate());
        Assertions.assertEquals(END_DATE, filterParams.getEndDate());
        Assertions.assertEquals(campaignsCte, filterParams.getCampaignIdsCte());
        Assertions.assertEquals(linksCte, filterParams.getLinkIdsCte());
        Assertions.assertEquals(Collections.emptySet(), filterParams.getProjectIds());
        Assertions.assertNull(filterParams.getProjectIdsCte());

        verifyNoMoreInteractions(linkService, metricService, campaignService);
    }

    @Test
    void getModels_groupByLinkUrlUsingCte() {
        Params params = getParams(GroupByAdsPerformance.LINK_URL, CAMP_1);

        Params paramsForCampaigns = params.toBuilder()
            .considerConfidential(true)
            .projectIds(Collections.emptySet())
            .build();

        when(campaignService.findCampaignIdsExpression(paramsForCampaigns))
            .thenReturn(campaignsCte);
        when(linkService.findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), null))
            .thenReturn(linksCte);

        service.getModels(params);

        verify(campaignService).findCampaignIdsExpression(paramsForCampaigns);
        verify(linkService).findByProjectsAndCampaignsExpression(campaignsCte.getTable(),
            Collections.emptySet(), null);

        verify(metricService).getMetrics(
            filterParamsArgumentCaptor.capture(),
            eq(Set.of(GroupByAdsPerformance.LINK_URL)),
            eq(OffsetBasedPageRequest.ofParamsKeepCase(0, 5000))
        );

        AdsReportQueryFilterParams filterParams = filterParamsArgumentCaptor.getValue();

        Assertions.assertEquals(START_DATE, filterParams.getStartDate());
        Assertions.assertEquals(END_DATE, filterParams.getEndDate());
        Assertions.assertEquals(campaignsCte, filterParams.getCampaignIdsCte());
        Assertions.assertEquals(linksCte, filterParams.getLinkIdsCte());
        Assertions.assertEquals(Collections.emptySet(), filterParams.getProjectIds());
        Assertions.assertNull(filterParams.getProjectIdsCte());
    }

    @Test
    void getModels_sortByNoGroupBy() {
        Params params = Params.builder()
            .sortBy("dimensions.project.project_name")
            .startDate(START_DATE)
            .endDate(END_DATE)
            .userId(USER_ID)
            .build();

        Assertions.assertThrows(InvalidParameterException.class, () -> service.getModels(params));
    }

    private Params getParams(GroupByAdsPerformance groupBy, String campaignId) {
        ParamsBuilder paramsBuilder = Params.builder()
            .adsPerformancesGroupBy(Set.of(groupBy))
            .startDate(START_DATE)
            .endDate(END_DATE)
            .userId(USER_ID);
        if (CollectionUtils.isNotEmpty(campaignId)) {
            paramsBuilder.campaignIds(Set.of(campaignId));
        }
        return paramsBuilder
            .build();
    }

}
