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.MockitoAnnotations;
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.AdsDataHealthRangeReport;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdown;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdownGroup;
import io.delphiplatform.api.v3.model.datahealth.DataHealthStatus;
import io.delphiplatform.api.v3.rdb.entity.AdsStatusDailyEntity;
import io.delphiplatform.api.v3.rdb.repository.AdsDataStatusDailyRepository;
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 io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.ACCOUNT_ID;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CAMPAIGN_ID;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.DATE_PROPERTY;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.DSP_PROPERTY;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.PARENT_REP_OWNER;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class AdsDataHealthStatusAnalyticsServiceTest {

    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 ACCOUNT_1 = "account1";
    public static final String LABEL_1 = "label_1";
    public static final String LABEL_2 = "label_2";
    public static final String FACEBOOK = "facebook";

    private AdsDataHealthStatusAnalyticsService service;

    @Mock
    private AdsDataStatusDailyRepository repository;

    @Mock
    private SpecificationProvider<AdsStatusDailyEntity> specificationProvider;

    @Mock
    private Specification<AdsStatusDailyEntity> specification;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        service = new AdsDataHealthStatusAnalyticsService(repository, specificationProvider);
    }

    @Test
    void getDataHealthRangeReport_emptyResponse() {
        when(specificationProvider.greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_2)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(DSP_PROPERTY, null)).thenReturn(specification);
        when(specificationProvider.nullValueSpec(CAMPAIGN_ID, true)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);

        when(specification.and(specification)).thenReturn(specification);
        when(specification.or(specification)).thenReturn(specification);

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

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

        Assertions.assertNull(report.getMinDate());
        Assertions.assertNull(report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProAdsMap().isEmpty());

        verify(specificationProvider).greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_2);
        verify(specificationProvider).multiValueSpec(DSP_PROPERTY, null);
        verify(specificationProvider).nullValueSpec(CAMPAIGN_ID, true);
        verify(specificationProvider).multiValueSpec(PARENT_REP_OWNER, null);
        verify(specificationProvider).multiValueSpec(ACCOUNT_ID, null);
    }

    @Test
    void getDataHealthRangeReport_minMaxDateConsiderStatus() {
        when(specificationProvider.greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_3)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(DSP_PROPERTY, null)).thenReturn(specification);
        when(specificationProvider.nullValueSpec(CAMPAIGN_ID, true)).thenReturn(specification);
        when(
            specificationProvider.multiValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.multiValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(ACCOUNT_ID, null)).thenReturn(specification);

        when(specification.and(specification)).thenReturn(specification);
        when(specification.or(specification)).thenReturn(specification);

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

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

        Assertions.assertEquals(DATE_2, report.getMinDate());
        Assertions.assertEquals(DATE_2, report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProMap().isEmpty());
        Assertions.assertEquals(1, report.getBreakdowns().getDspProAdsMap().size());
        Assertions.assertEquals(List.of(COMPLETE),
            report.getBreakdowns().getDspProAdsMap()
                .get(FACEBOOK)
                .get(LABEL_1)
                .get(ACCOUNT_1)
                .getDays()
        );

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_3);
        verify(specificationProvider).multiValueSpec("dsp", null);
        verify(specificationProvider).nullValueSpec(CAMPAIGN_ID, true);
        verify(specificationProvider).multiValueSpec(PARENT_REP_OWNER, null);
        verify(specificationProvider).multiValueSpec(ACCOUNT_ID, null);
    }


    @Test
    void getDataHealthRangeReport_onlyMissingStatuses() {
        when(specificationProvider.greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_3)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(DSP_PROPERTY, null)).thenReturn(specification);
        when(specificationProvider.nullValueSpec(CAMPAIGN_ID, true)).thenReturn(specification);
        when(
            specificationProvider.multiValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.multiValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(ACCOUNT_ID, null)).thenReturn(specification);

        when(specification.and(specification)).thenReturn(specification);
        when(specification.or(specification)).thenReturn(specification);

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

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

        Assertions.assertNull(report.getMinDate());
        Assertions.assertNull(report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProAdsMap().isEmpty());

        verify(specificationProvider).greaterThanOrEqualToValueSpec("date", DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec("date", DATE_3);
        verify(specificationProvider).multiValueSpec("dsp", null);
        verify(specificationProvider).nullValueSpec(CAMPAIGN_ID, true);
        verify(specificationProvider).multiValueSpec(PARENT_REP_OWNER, null);
        verify(specificationProvider).multiValueSpec(ACCOUNT_ID, null);
    }

    @Test
    void getDataHealthRangeReport_differentMinMaxDate() {
        when(specificationProvider.greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1)).thenReturn(specification);
        when(specificationProvider.lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_3)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(DSP_PROPERTY, null)).thenReturn(specification);
        when(specificationProvider.nullValueSpec(CAMPAIGN_ID, true)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(ACCOUNT_ID, null)).thenReturn(specification);
        when(specificationProvider.multiValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);
        when(
            specificationProvider.nullValueSpec(PARENT_REP_OWNER, null)).thenReturn(specification);

        when(specification.and(specification)).thenReturn(specification);
        when(specification.or(specification)).thenReturn(specification);

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

                createEntity(DATE_1, FACEBOOK, COMPLETE, ACCOUNT_1, LABEL_2),
                createEntity(DATE_2, FACEBOOK, COMPLETE, ACCOUNT_1, LABEL_2),
                createEntity(DATE_3, FACEBOOK, COMPLETE, ACCOUNT_1, LABEL_2)
            )));

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

        Assertions.assertEquals(DATE_1, report.getMinDate());
        Assertions.assertEquals(DATE_3, report.getMaxDate());
        Assertions.assertTrue(report.getBreakdowns().getDspMap().isEmpty());
        Assertions.assertTrue(report.getBreakdowns().getDspProMap().isEmpty());
        Assertions.assertEquals(1, report.getBreakdowns().getDspProAdsMap().size());
        Assertions.assertEquals(2, report.getBreakdowns().getDspProAdsMap().get(FACEBOOK).size());
        Assertions.assertEquals(List.of(MISSING, COMPLETE, PENDING),
            report.getBreakdowns().getDspProAdsMap()
                .get(FACEBOOK)
                .get(LABEL_1)
                .get(ACCOUNT_1)
                .getDays()
        );

        Assertions.assertEquals(List.of(COMPLETE, COMPLETE, COMPLETE),
            report.getBreakdowns().getDspProAdsMap()
                .get(FACEBOOK)
                .get(LABEL_2)
                .get(ACCOUNT_1)
                .getDays()
        );

        verify(specificationProvider).greaterThanOrEqualToValueSpec(DATE_PROPERTY, DATE_1);
        verify(specificationProvider).lessThanOrEqualToValueSpec(DATE_PROPERTY, DATE_3);
        verify(specificationProvider).multiValueSpec(DSP_PROPERTY, null);
        verify(specificationProvider).nullValueSpec(CAMPAIGN_ID, true);
        verify(specificationProvider).multiValueSpec(PARENT_REP_OWNER, null);
        verify(specificationProvider).multiValueSpec(ACCOUNT_ID, null);
    }

    private AdsStatusDailyEntity createEntity(LocalDate date, String dsp, DataHealthStatus status, String accountId,
        String parRepOwner) {
        AdsStatusDailyEntity entity = new AdsStatusDailyEntity();
        entity.setDsp(dsp);
        entity.setDate(date);
        entity.setAccountId(accountId);
        entity.setParentRepOwner(parRepOwner);
        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();
    }
}
