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.mockito.Spy;
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.List;
import java.util.Set;

import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdown;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdownGroup;
import io.delphiplatform.api.v3.model.datahealth.DataHealthRangeReport;
import io.delphiplatform.api.v3.model.datahealth.DataHealthStatus;
import io.delphiplatform.api.v3.rdb.entity.DspDataStatusDailyEntity;
import io.delphiplatform.api.v3.rdb.repository.DspDataStatusDailyRepository;
import io.delphiplatform.api.v3.rdb.service.specification.SpecificationProvider;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.v3.model.datahealth.DataHealthStatus.COMPLETE;
import static io.delphiplatform.api.v3.model.datahealth.DataHealthStatus.MISSING;
import static io.delphiplatform.api.v3.model.datahealth.DataHealthStatus.PENDING;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

class DataHealthStatusAnalyticsServiceTest {

    public static final LocalDate DATE_4 = LocalDate.of(2020, 1, 4);
    public static final LocalDate DATE_3 = LocalDate.of(2020, 1, 3);
    public static final LocalDate DATE_2 = LocalDate.of(2020, 1, 2);
    public static final LocalDate DATE_1 = LocalDate.of(2020, 1, 1);
    public static final String YOUTUBE = "youtube";
    public static final String METRICS = "metrics";
    public static final String CHARTS = "charts";
    private DataHealthStatusAnalyticsService service;

    @Mock
    private DspDataStatusDailyRepository repository;
    @Mock
    private SpecificationProvider<DspDataStatusDailyEntity> specificationProvider;
    @Spy
    private BreakdownHelper breakdownHelper;
    @Mock
    private Specification<DspDataStatusDailyEntity> specification;


    @BeforeEach
    public void setUp() {
        initMocks(this);
        service = new DataHealthStatusAnalyticsService(repository, specificationProvider, breakdownHelper);
    }

    @Test
    void getDataHealthRangeReport_emptyResponse() {
        when(specificationProvider.greaterThanOrEqualToValueSpec("date", DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec("date", DATE_2)).thenReturn(specification);
        when(specificationProvider.multiValueSpec("dsp", null)).thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        when(repository.findAll(eq(specification), eq(Pageable.unpaged())))
            .thenReturn(new PageImpl<>(List.of()));

        DataHealthRangeReport report = service.getDataHealthRangeReport(getRequestParams(DATE_1, DATE_2));

        Assertions.assertNull(report.getMinDate());
        Assertions.assertNull(report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspSegment().isEmpty());

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_2);
        verify(specificationProvider).multiValueSpec("dsp", null);
    }

    @Test
    void getDataHealthRangeReport_minMaxDateConsiderStatus() {
        when(specificationProvider.greaterThanOrEqualToValueSpec("date", DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec("date", DATE_4)).thenReturn(specification);
        when(specificationProvider.multiValueSpec("dsp", null)).thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        when(repository.findAll(eq(specification), eq(Pageable.unpaged())))
            .thenReturn(new PageImpl<>(List.of(
                createEntity(DATE_1, COMPLETE, METRICS),
                createEntity(DATE_2, COMPLETE, METRICS),
                createEntity(DATE_3, COMPLETE, METRICS),
                createEntity(DATE_4, COMPLETE, METRICS),

                createEntity(DATE_1, MISSING, CHARTS),
                createEntity(DATE_2, COMPLETE, CHARTS),
                createEntity(DATE_3, COMPLETE, CHARTS),
                createEntity(DATE_4, PENDING, CHARTS)
            )));

        DataHealthRangeReport report = service.getDataHealthRangeReport(getRequestParams(DATE_1, DATE_4));

        Assertions.assertEquals(DATE_1, report.getMinDate());
        Assertions.assertEquals(DATE_4, report.getMaxDate());
        Assertions.assertEquals(1, report.getBreakdowns().getDspSegment().size());
        Assertions.assertEquals(2, report.getBreakdowns().getDspSegment().get(YOUTUBE).size());

        Assertions.assertEquals(4, report.getBreakdowns().getDspSegment().get(YOUTUBE).get(METRICS).getDays().size());
        Assertions.assertEquals(
            List.of(COMPLETE, COMPLETE, COMPLETE, COMPLETE),
            report.getBreakdowns().getDspSegment().get(YOUTUBE).get(METRICS).getDays());

        Assertions.assertEquals(4, report.getBreakdowns().getDspSegment().get(YOUTUBE).get(CHARTS).getDays().size());
        Assertions.assertEquals(
            List.of(MISSING, COMPLETE, COMPLETE, PENDING),
            report.getBreakdowns().getDspSegment().get(YOUTUBE).get(CHARTS).getDays());

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_4);
        verify(specificationProvider).multiValueSpec("dsp", null);
    }


    @Test
    void getDataHealthRangeReport_multipleSegmentsDifferentMinMaxDate() {
        when(specificationProvider.greaterThanOrEqualToValueSpec("date", DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec("date", DATE_3)).thenReturn(specification);
        when(specificationProvider.multiValueSpec("dsp", null)).thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        when(repository.findAll(eq(specification), eq(Pageable.unpaged())))
            .thenReturn(new PageImpl<>(List.of(
                createEntity(DATE_1, MISSING, METRICS),
                createEntity(DATE_2, COMPLETE, METRICS),
                createEntity(DATE_3, PENDING, METRICS)
            )));

        DataHealthRangeReport report = service.getDataHealthRangeReport(getRequestParams(DATE_1, DATE_3));

        Assertions.assertEquals(DATE_2, report.getMinDate());
        Assertions.assertEquals(DATE_2, report.getMaxDate());
        Assertions.assertEquals(1, report.getBreakdowns().getDspSegment().size());
        Assertions.assertEquals(1, report.getBreakdowns().getDspSegment().get(YOUTUBE).size());
        Assertions.assertEquals(1, report.getBreakdowns().getDspSegment().get(YOUTUBE).get(METRICS).getDays().size());
        Assertions.assertEquals(COMPLETE,
            report.getBreakdowns().getDspSegment().get(YOUTUBE).get(METRICS).getDays().get(0));

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_3);
        verify(specificationProvider).multiValueSpec("dsp", null);
    }

    @Test
    void getDataHealthRangeReport_oneSegmentAllMissing() {
        when(specificationProvider.greaterThanOrEqualToValueSpec("date", DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec("date", DATE_3)).thenReturn(specification);
        when(specificationProvider.multiValueSpec("dsp", null)).thenReturn(specification);
        when(specification.and(specification)).thenReturn(specification);

        when(repository.findAll(eq(specification), eq(Pageable.unpaged())))
            .thenReturn(new PageImpl<>(List.of(
                createEntity(DATE_1, MISSING, METRICS),
                createEntity(DATE_2, MISSING, METRICS),
                createEntity(DATE_3, MISSING, METRICS)
            )));

        DataHealthRangeReport report = service.getDataHealthRangeReport(getRequestParams(DATE_1, DATE_3));

        Assertions.assertNull(report.getMinDate());
        Assertions.assertNull(report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspSegment().isEmpty());

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_3);
        verify(specificationProvider).multiValueSpec("dsp", null);
    }

    private DspDataStatusDailyEntity createEntity(LocalDate date, DataHealthStatus status, String segment) {
        DspDataStatusDailyEntity entity = new DspDataStatusDailyEntity();
        entity.setId(null);
        entity.setDsp(YOUTUBE);
        entity.setSegment(segment);
        entity.setDate(date);
        entity.setStatus(status);
        return entity;
    }

    private Params getRequestParams(LocalDate startDate, LocalDate endDate) {
        return Params.builder()
            .startDate(startDate)
            .endDate(endDate)
            .dataHealthBreakdowns(Set.of(DataHealthBreakdown.DAYS))
            .dataHealthBreakdownGroups(Set.of(DataHealthBreakdownGroup.DSP_SEGMENT))
            .build();
    }
}
