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

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import io.delphiplatform.api.v3.model.tiktok.TikTokStateMetric;
import io.delphiplatform.api.v3.model.tiktok.TikTokTrackAnalyticsMetrics;
import io.delphiplatform.api.v3.model.tiktok.TikTokTrackAnalyticsRangeMetrics;
import io.delphiplatform.api.v3.model.tiktok.top.TikTokTopChartTrackState;
import io.delphiplatform.api.v3.model.tiktok.top.TikTokTopChartTrackStates;
import io.delphiplatform.api.v3.model.tiktok.top.TikTokTopTracksAnalytics;
import io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames;
import io.delphiplatform.api.v3.rdb.entity.tiktok.FactTikTokTopTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.tiktok.FactTikTokTopTrackEntityId;
import io.delphiplatform.api.v3.rdb.repository.FactTikTokTopChartTrackLifetimeRepository;
import io.delphiplatform.api.v3.rdb.repository.FactTikTokTopTrackRepository;
import io.delphiplatform.api.v3.rdb.repository.TikTokTopTrackChartRepository;
import io.delphiplatform.api.v3.rdb.service.specification.SpecificationProvider;
import io.delphiplatform.api.v3.rdb.service.tiktok.TikTokTopTracksService;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CHART_DATE;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.ID_PROPERTY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

class TikTokTopTracksServiceTest {

    public static final LocalDate DATE_1 = LocalDate.of(2020, 1, 1);
    public static final LocalDate DATE_2 = LocalDate.of(2020, 1, 2);
    public static final String ISRC = "isrc1";
    public static final String ISRC_2 = "isrc2";
    public static final String COUNTRY_AD = "ad";
    public static final String CHART_ID = "top50_1day_isrc_period_creations_" + COUNTRY_AD;

    private TikTokTopTracksService service;

    @Mock
    FactTikTokTopChartTrackLifetimeRepository lifetimeRepository;

    @Mock
    FactTikTokTopTrackRepository repository;

    @Mock
    SpecificationProvider<FactTikTokTopTrackEntity> specificationProvider;

    @Mock
    private Specification<FactTikTokTopTrackEntity> specification;

    @Mock
    TikTokTopTrackChartRepository chartRepository;

    @BeforeEach
    public void setUp() {
        initMocks(this);
        service = new TikTokTopTracksService(repository,
            lifetimeRepository,
            specificationProvider,
            null,
            chartRepository
        );
    }

    @Test
    void getTikTokTopTracks() throws Exception {
        when(repository.findMaxDateByChartId(CHART_ID)).thenReturn(DATE_2);
        when(chartRepository.existsById(CHART_ID)).thenReturn(true);

        when(specificationProvider
            .multiValueSpec(List.of(CommonJpaPropertyNames.ID_PROPERTY, CommonJpaPropertyNames.ISRC), List.of(ISRC)))
            .thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        FactTikTokTopTrackEntity factTikTokTopTrackEntity = createFactTikTokTopTrackEntity(DATE_1, ISRC, CHART_ID, 10,
            201L, List.of("ad", "kz", "fr", "us", "uk", "ca"));
        when(repository.findAll(eq(specification), any(Pageable.class)))
            .thenReturn(new PageImpl<>(List.of(factTikTokTopTrackEntity)));

        Params params = getRequestParams(DATE_2, CHART_ID,
            Set.of(TikTokStateMetric.AGGREGATED_METRICS, TikTokStateMetric.METRICS), ISRC
        );

        TikTokTopChartTrackStates report = service.getTikTokTopTracks(params);
        Assertions.assertEquals(1, report.getCount());
        TikTokTopChartTrackState state1 = report.getItems().get(0);
        Assertions.assertNotNull(state1.getMetrics());
        Assertions.assertNotNull(state1.getAggregatedMetrics());
        Assertions.assertNull(state1.getLifetimeMetrics());
        Assertions.assertEquals(List.of("ad", "kz", "fr", "us", "uk", "ca"),
            state1.getAggregatedMetrics().getLast7DayTopMarkets());
        Assertions.assertEquals(201L, state1.getMetrics().getCreations());

        verify(specificationProvider).singleValueSpec(eq(List.of(ID_PROPERTY,
            CommonJpaPropertyNames.CHART_ID)), eq(CHART_ID));
        verify(specificationProvider).singleValueSpec(eq(List.of(ID_PROPERTY, CHART_DATE)), eq(DATE_2));
        verify(specificationProvider)
            .lessThanOrEqualToValueSpec(eq(CommonJpaPropertyNames.CURRENT_POSITION), eq(params.getMaxPosition()));
        verify(specificationProvider)
            .greaterThanOrEqualToValueSpec(eq(CommonJpaPropertyNames.CURRENT_POSITION), eq(params.getMinPosition()));
        verify(specificationProvider)
            .multiValueSpec(eq(List.of(ID_PROPERTY, CommonJpaPropertyNames.ISRC)), eq(params.getIsrc()));
    }


    @Test
    void getTikTokTopTracksTopMarketsCorrectDates() throws Exception {
        LocalDate chartDate = dateForDay(27);
        when(repository.findMaxDateByChartId(CHART_ID)).thenReturn(chartDate);
        when(chartRepository.existsById(CHART_ID)).thenReturn(true);

        when(specificationProvider
            .multiValueSpec(List.of(CommonJpaPropertyNames.ID_PROPERTY, CommonJpaPropertyNames.ISRC), List.of(ISRC)))
            .thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        FactTikTokTopTrackEntity factTikTokTopTrackEntity = createFactTikTokTopTrackEntity(chartDate, ISRC, CHART_ID,
            10, null, List.of("uk", "fr", "us"));
        when(repository.findAll(eq(specification), any(Pageable.class)))
            .thenReturn(new PageImpl<>(List.of(factTikTokTopTrackEntity)));

        Params params = getRequestParams(chartDate, CHART_ID,
            Set.of(TikTokStateMetric.AGGREGATED_METRICS, TikTokStateMetric.METRICS), ISRC
        );

        TikTokTopChartTrackStates report = service.getTikTokTopTracks(params);
        Assertions.assertEquals(1, report.getCount());
        TikTokTopChartTrackState state1 = report.getItems().get(0);
        Assertions.assertEquals(List.of("uk", "fr", "us"),
            state1.getAggregatedMetrics().getLast7DayTopMarkets());
    }

    @Test
    void getTikTokTopTracksTwoIsrcs() throws Exception {
        when(repository.findMaxDateByChartId(CHART_ID)).thenReturn(DATE_2);
        when(chartRepository.existsById(CHART_ID)).thenReturn(true);

        when(specificationProvider
            .multiValueSpec(List.of(CommonJpaPropertyNames.ID_PROPERTY, CommonJpaPropertyNames.ISRC),
                List.of(ISRC, ISRC_2)))
            .thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        FactTikTokTopTrackEntity factIsrc1 = createFactTikTokTopTrackEntity(DATE_1, ISRC, CHART_ID, 1,
            1L, List.of("it"));
        FactTikTokTopTrackEntity factIsrc2 = createFactTikTokTopTrackEntity(DATE_1, ISRC_2, CHART_ID, 2,
            2L, List.of("uk"));
        when(repository.findAll(eq(specification), any(Pageable.class)))
            .thenReturn(new PageImpl<>(List.of(factIsrc1, factIsrc2)));

        Params params = getRequestParams(DATE_2, CHART_ID,
            Set.of(TikTokStateMetric.AGGREGATED_METRICS, TikTokStateMetric.METRICS), ISRC, ISRC_2
        );

        TikTokTopChartTrackStates report = service.getTikTokTopTracks(params);
        Assertions.assertEquals(2, report.getCount());
        TikTokTopChartTrackState state1 = report.getItems().get(0);
        Assertions.assertNotNull(state1.getMetrics());
        Assertions.assertNotNull(state1.getAggregatedMetrics());
        Assertions.assertNull(state1.getLifetimeMetrics());
        Assertions.assertEquals(1, state1.getMetrics().getPosition());
        Assertions.assertEquals(List.of("it"), state1.getAggregatedMetrics().getLast7DayTopMarkets());

        TikTokTopChartTrackState state2 = report.getItems().get(1);
        Assertions.assertNotNull(state2.getMetrics());
        Assertions.assertNotNull(state2.getAggregatedMetrics());
        Assertions.assertNull(state2.getLifetimeMetrics());
        Assertions.assertEquals(2, state2.getMetrics().getPosition());
        Assertions.assertEquals(List.of("uk"), state2.getAggregatedMetrics().getLast7DayTopMarkets());

    }

    private TikTokTrackAnalyticsMetrics getTikTokTrackAnalyticsMetrics(Long val) {
        TikTokTrackAnalyticsMetrics tikTokTrackAnalyticsMetrics = new TikTokTrackAnalyticsMetrics();
        tikTokTrackAnalyticsMetrics.setCreations(val);
        return tikTokTrackAnalyticsMetrics;
    }

    private TikTokTrackAnalyticsRangeMetrics getTikTokTrackAnalyticsRangeMetrics(LocalDate date, Long val) {
        TikTokTrackAnalyticsRangeMetrics tikTokTrackAnalyticsRangeMetrics = new TikTokTrackAnalyticsRangeMetrics();
        tikTokTrackAnalyticsRangeMetrics.setCreationsByDate(Map.of(date, val));
        return tikTokTrackAnalyticsRangeMetrics;
    }

    private TikTokTrackAnalyticsRangeMetrics getTikTokTrackAnalyticsRangeMetrics(
        LocalDate date1, Long val1,
        LocalDate date2, Long val2
    ) {
        TikTokTrackAnalyticsRangeMetrics tikTokTrackAnalyticsRangeMetrics = new TikTokTrackAnalyticsRangeMetrics();
        tikTokTrackAnalyticsRangeMetrics.setCreationsByDate(Map.of(date1, val1, date2, val2));
        return tikTokTrackAnalyticsRangeMetrics;
    }

    @Test
    void getTikTokTopTracksAnalytics() {
        when(specificationProvider.greaterThanOrEqualToValueSpec(CommonJpaPropertyNames.
            CHART_DATE, DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec(CommonJpaPropertyNames.
            CHART_DATE, DATE_2)).thenReturn(specification);
        when(specificationProvider
            .multiValueSpec(List.of(CommonJpaPropertyNames.ID_PROPERTY, CommonJpaPropertyNames.ISRC), List.of(ISRC)))
            .thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);
        when(chartRepository.existsById(CHART_ID)).thenReturn(true);

        FactTikTokTopTrackEntity factTikTokTopTrackEntity = createFactTikTokTopTrackEntity(DATE_1, ISRC, CHART_ID, 12,
            null, null);
        FactTikTokTopTrackEntity factTikTokTopTrackEntity2 = createFactTikTokTopTrackEntity(DATE_2, ISRC, CHART_ID, 15,
            null, null);
        when(repository.findAll(eq(specification), any(Pageable.class)))
            .thenReturn(new PageImpl<>(List.of(factTikTokTopTrackEntity, factTikTokTopTrackEntity2)));
        TikTokTopTracksAnalytics report = service
            .getTikTokTopTracksAnalytics(getRequestParams(DATE_1, DATE_2, CHART_ID, null, ISRC));

        Assertions.assertEquals(DATE_1, report.getMinDate());
        Assertions.assertEquals(DATE_2, report.getMaxDate());
        Assertions.assertEquals(ISRC, report.getIsrc());
        Assertions.assertEquals(12, report.getMinPosition());
        Assertions.assertEquals(15, report.getMaxPosition());

        verify(specificationProvider).greaterThanOrEqualToValueSpec(CommonJpaPropertyNames.CHART_DATE, DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec(CommonJpaPropertyNames.CHART_DATE, DATE_2);
        verify(specificationProvider).multiValueSpec(List.of(CommonJpaPropertyNames.ID_PROPERTY,
            CommonJpaPropertyNames.ISRC), List.of(ISRC));
    }

    @Test
    void testIsLifetimeMetricsRequested() {
        testIsSomeMetricsRequested(TikTokTopTracksService::isLifetimeMetricsRequested,
            TikTokStateMetric.LIFETIME_METRICS);
    }

    @Test
    void testIsMetricsRequested() {
        testIsSomeMetricsRequested(TikTokTopTracksService::isMetricsRequested,
            TikTokStateMetric.METRICS);
    }

    @Test
    void testIsAggMetricsRequested() {
        testIsSomeMetricsRequested(TikTokTopTracksService::isAggMetricsRequested,
            TikTokStateMetric.AGGREGATED_METRICS);
    }

    @Test
    void testIsSourceMetricsRequested() {
        testIsSomeMetricsRequested(TikTokTopTracksService::isSourceMetricsRequested,
            TikTokStateMetric.SOURCE_META);
    }

    private void testIsSomeMetricsRequested(Function<Collection<TikTokStateMetric>, Boolean> func,
        TikTokStateMetric metric) {
        Assertions.assertFalse(TikTokTopTracksService.isLifetimeMetricsRequested(null));
        Assertions.assertTrue(func.apply(Set.of(metric)));
        Assertions
            .assertFalse(func.apply(Arrays.stream(TikTokStateMetric.values()).filter(e -> !e.equals(metric)).collect(
                Collectors.toSet())));
        Assertions
            .assertTrue(TikTokTopTracksService.isLifetimeMetricsRequested(
                Set.of(TikTokStateMetric.values())));
    }


    private Params getRequestParams(LocalDate date, String chartId, Set<TikTokStateMetric> metrics, String... isrc) {
        return Params.builder()
            .date(date)
            .singleChartId(chartId)
            .isrc(List.of(isrc))
            .minPosition(10)
            .maxPosition(20)
            .tikTokTrackStateIncludes(metrics)
            .build();
    }

    private Params getRequestParams(LocalDate startDate, LocalDate endDate, String chartId,
        Set<TikTokStateMetric> metrics, String... isrc) {
        return Params.builder()
            .startDate(startDate)
            .endDate(endDate)
            .singleChartId(chartId)
            .isrc(List.of(isrc))
            .minPosition(10)
            .maxPosition(20)
            .tikTokTrackStateIncludes(metrics)
            .build();
    }

    private FactTikTokTopTrackEntity createFactTikTokTopTrackEntity(LocalDate date, String isrc, String chartId,
        Integer position, Long creations, List<String> last7DayTopMarkets) {
        FactTikTokTopTrackEntity factTikTokTopTrackEntity = new FactTikTokTopTrackEntity();
        factTikTokTopTrackEntity.setCurrentPosition(position);

        FactTikTokTopTrackEntityId id = new FactTikTokTopTrackEntityId();
        id.setIsrc(isrc);
        id.setChartDate(date);
        id.setChartId(chartId);

        factTikTokTopTrackEntity.setId(id);
        factTikTokTopTrackEntity.setIsrc(isrc);

        factTikTokTopTrackEntity.setCreations(creations);
        factTikTokTopTrackEntity.setLast7DayTopMarkets(last7DayTopMarkets == null
            ? null : last7DayTopMarkets.toArray(new String[0]));

        return factTikTokTopTrackEntity;
    }

    private LocalDate dateForDay(int dayOfMonth) {
        return LocalDate.of(2022, 1, dayOfMonth);
    }

}
