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

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.model.datahealth.AdsDataHealthRangeReport;
import io.delphiplatform.api.v3.model.datahealth.AdsDataHealthStatusReport;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdownGroup;
import io.delphiplatform.api.v3.model.datahealth.DataHealthRangeBreakdown;
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.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;

@Service
@Transactional(readOnly = true)
public class AdsDataHealthStatusAnalyticsService extends EntityService<AdsStatusDailyEntity> {

    private final SpecificationProvider<AdsStatusDailyEntity> specificationProvider;

    protected AdsDataHealthStatusAnalyticsService(
        AdsDataStatusDailyRepository repository,
        SpecificationProvider<AdsStatusDailyEntity> specificationProvider) {
        super(repository);
        this.specificationProvider = specificationProvider;
    }

    public AdsDataHealthRangeReport getDataHealthRangeReport(Params params) {
        List<AdsStatusDailyEntity> dailyStatuses = getDailyStatuses(params);

        AdsDataHealthRangeReport report = new AdsDataHealthRangeReport();
        LocalDate minDate = dailyStatuses.stream()
            .filter(ds -> CollectionUtils.notContains(DataHealthStatus.NO_DATA_STATUSES, ds.getStatus()))
            .map(AdsStatusDailyEntity::getDate)
            .min(LocalDate::compareTo)
            .orElse(null);
        report.setMinDate(minDate);

        LocalDate maxDate = dailyStatuses.stream()
            .filter(ds -> CollectionUtils.notContains(DataHealthStatus.NO_DATA_STATUSES, ds.getStatus()))
            .map(AdsStatusDailyEntity::getDate)
            .max(LocalDate::compareTo)
            .orElse(null);
        report.setMaxDate(maxDate);

        if (report.getMinDate() == null && report.getMaxDate() == null) {
            return report;
        }

        if (CollectionUtils.contains(params.getDataHealthBreakdownGroups(), DataHealthBreakdownGroup.DSP_SEGMENT)) {

            Map<String, DataHealthRangeBreakdown> dspMap =
                dailyStatuses.stream()
                    .filter(elem -> elem.getAccountId() == null && elem.getParentRepOwner() == null)
                    .collect(Collectors
                        .groupingBy(AdsStatusDailyEntity::getDsp, AdsDspAnalyticCollector.get(minDate, maxDate)));

            AdsDataHealthStatusReport<DataHealthRangeBreakdown> adsDataHealthStatusReport = new AdsDataHealthStatusReport<>();

            adsDataHealthStatusReport.setDspMap(dspMap);

            Map<String, Map<String, DataHealthRangeBreakdown>> dspProMap =
                dailyStatuses.stream().filter(elem -> elem.getAccountId() == null && elem.getParentRepOwner() != null).
                    collect(Collectors.groupingBy(AdsStatusDailyEntity::getDsp,
                        Collectors.groupingBy(AdsStatusDailyEntity::getParentRepOwner,
                            AdsDspAnalyticCollector.get(minDate, maxDate))));
            adsDataHealthStatusReport.setDspProMap(dspProMap);

            Map<String, Map<String, Map<String, DataHealthRangeBreakdown>>> dspProAdsMap =
                dailyStatuses.stream().filter(elem -> elem.getAccountId() != null && elem.getParentRepOwner() != null).
                    collect(Collectors.groupingBy(AdsStatusDailyEntity::getDsp,
                        Collectors.groupingBy(AdsStatusDailyEntity::getParentRepOwner,
                            Collectors
                                .groupingBy(AdsStatusDailyEntity::getAccountId,
                                    AdsDspAnalyticCollector.get(minDate, maxDate)))));

            adsDataHealthStatusReport.setDspProAdsMap(dspProAdsMap);

            report.setBreakdowns(adsDataHealthStatusReport);

        }

        return report;
    }

    private List<AdsStatusDailyEntity> getDailyStatuses(Params params) {
        Specification<AdsStatusDailyEntity> spec = and(List.of(
            specificationProvider.greaterThanOrEqualToValueSpec(DATE_PROPERTY, params.getStartDate()),
            specificationProvider.lessThanOrEqualToValueSpec(DATE_PROPERTY, params.getEndDate()),
            specificationProvider.multiValueSpec(DSP_PROPERTY, params.getDsp()),
            specificationProvider.nullValueSpec(CAMPAIGN_ID, true),
            or(List.of(specificationProvider
                    .multiValueSpec(PARENT_REP_OWNER, params.getParentRepOwnerKeys()),
                specificationProvider
                    .nullValueSpec(PARENT_REP_OWNER,
                        CollectionUtils.isEmpty(params.getParentRepOwnerKeys()) ? null : true))),
            or(List.of(specificationProvider.multiValueSpec(ACCOUNT_ID, params.getAdsAccounts()),
                specificationProvider.nullValueSpec(ACCOUNT_ID,
                    CollectionUtils.isEmpty(params.getAdsAccounts()) ? null : true)))
        ));
        return getResult(spec, Pageable.unpaged());
    }

}
