package io.delphiplatform.api.v3.bigtable.processing;

import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.bigtable.entity.TikTokTrackCountryMetrics;
import io.delphiplatform.api.v3.model.tiktok.IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns;
import io.delphiplatform.api.v3.model.tiktok.TikTokMetric;
import io.delphiplatform.api.v3.model.tiktok.TikTokTrackAnalyticsMetrics;
import io.delphiplatform.api.v3.model.tiktok.TikTokTrackAnalyticsRangeMetrics;
import io.delphiplatform.api.v3.rdb.service.BreakdownHelper;

@Service
public class TikTokTrackMetricsAggregator {

    private final BreakdownHelper breakdownHelper;

    public TikTokTrackMetricsAggregator(BreakdownHelper breakdownHelper) {
        this.breakdownHelper = breakdownHelper;
    }

    public TikTokTrackAnalyticsRangeMetrics dailyBreakdown(
        IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns contentTypeCountryBreakdown,
        Set<TikTokMetric> metrics
    ) {

        TikTokTrackAnalyticsRangeMetrics daily = new TikTokTrackAnalyticsRangeMetrics();
        daily.setMinDate(contentTypeCountryBreakdown.getMinDate());
        daily.setMaxDate(contentTypeCountryBreakdown.getMaxDate());

        Stream.concat(contentTypeCountryBreakdown.getPgc().entrySet().stream(),
                contentTypeCountryBreakdown.getUgc().entrySet().stream())
            .map(Entry::getValue)
            .forEach(rangeMetric -> mergeRangeMetrics(daily, rangeMetric));

        Set<TikTokMetric> fieldsToHide = CollectionUtils.isEmpty(metrics) ? null :
            Arrays.stream(TikTokMetric.values()).filter(Predicate.not(metrics::contains)).collect(Collectors.toSet());

        daily.clearFields(fieldsToHide);

        return daily;
    }

    public Map<String, TikTokTrackAnalyticsRangeMetrics> countryDailyBreakdown(
        IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns contentTypeCountryBreakdown) {
        Stream<Entry<String, TikTokTrackAnalyticsRangeMetrics>> countryToRangeMetrics =
            Stream.concat(
                contentTypeCountryBreakdown.getPgc().entrySet().stream(),
                contentTypeCountryBreakdown.getUgc().entrySet().stream()
            );

        return breakdownHelper.breakdown(countryToRangeMetrics,
            Entry::getKey,
            (currentItem, targetResult) -> mergeRangeMetrics(targetResult, currentItem.getValue()),
            () -> {
                TikTokTrackAnalyticsRangeMetrics range = new TikTokTrackAnalyticsRangeMetrics();
                range.setMinDate(contentTypeCountryBreakdown.getMinDate());
                range.setMaxDate(contentTypeCountryBreakdown.getMaxDate());
                return range;
            }
        );
    }

    public Map<String, TikTokTrackAnalyticsMetrics> countryTotalsBreakdown(
        IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns contentTypeCountryBreakdown) {

        return
            Stream.concat(contentTypeCountryBreakdown.getPgc().entrySet().stream(),
                    contentTypeCountryBreakdown.getUgc().entrySet().stream())
                .collect(Collectors.groupingBy(Map.Entry::getKey, CountryTotalsCollector.get()));
    }

    public IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns contentTypeCountryBreakdown(
        Stream<TikTokTrackCountryMetrics> countryMetrics, Set<TikTokMetric> metrics,
        Map<String, Set<String>> isrcToRelated) {
        Collection<IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> breakdowns =
            getListContentTypeCountryBreakdown(countryMetrics, isrcToRelated, metrics);
        return CollectionUtils.isEmpty(breakdowns) ? new IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns()
            : breakdowns.stream().findFirst().get();
    }

    public Collection<IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> getListContentTypeCountryBreakdown(
        Stream<TikTokTrackCountryMetrics> countryMetrics,
        Map<String, Set<String>> isrcToRelated,
        Set<TikTokMetric> metrics) {
        Set<TikTokMetric> fieldsToHide = CollectionUtils.isEmpty(metrics) ? null :
            Arrays.stream(TikTokMetric.values()).filter(Predicate.not(metrics::contains)).collect(Collectors.toSet());

        return getAllBreakdowns(countryMetrics, isrcToRelated)
            .stream()
            .peek(breakdowns ->
                Stream.concat(
                    breakdowns.getUgc().values().stream(),
                    breakdowns.getPgc().values().stream()
                ).forEach(rangeMetrics -> {
                    rangeMetrics.setMinDate(breakdowns.getMinDate());
                    rangeMetrics.setMaxDate(breakdowns.getMaxDate());
                    rangeMetrics.clearFields(fieldsToHide);
                })
            ).collect(Collectors.toSet());
    }

    private Collection<IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> getAllBreakdowns(
        Stream<TikTokTrackCountryMetrics> countryMetrics,
        Map<String, Set<String>> isrcToRelated) {
        Map<String, IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> isrcToBreakdowns = new HashMap<>();

        countryMetrics.forEach(countryMetric -> {
            String isrc = countryMetric.getIsrc();
            List<IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> relatedBreakdowns =
                getRelatedBreakdowns(isrcToBreakdowns, isrcToRelated, isrc);

            relatedBreakdowns.forEach(breakdowns -> {
                switch (countryMetric.getContentType()) {
                    case UGC:
                        addToBreakdown(countryMetric, breakdowns.getUgc());
                        break;
                    case PGC:
                        addToBreakdown(countryMetric, breakdowns.getPgc());
                }
                breakdowns.updateDates(countryMetric.getDate());
            });
        });

        return isrcToBreakdowns.values();
    }

    private List<IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> getRelatedBreakdowns(
        Map<String, IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns> isrcToBreakdowns,
        Map<String, Set<String>> isrcToRelated,
        String isrc) {

        Set<String> affectedIsrcs = isrcToRelated.entrySet().stream()
            .filter(entry -> entry.getValue().contains(isrc))
            .map(Entry::getKey)
            .collect(Collectors.toSet());

        return affectedIsrcs.stream().map(affectedIsrc -> isrcToBreakdowns
            .computeIfAbsent(affectedIsrc,
                newIsrc -> {
                    IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns newBreakdown = new IsrcContentTypeCountryTikTokTrackAnalyticsBreakdowns();
                    newBreakdown.setIscr(newIsrc);
                    return newBreakdown;
                })).collect(Collectors.toList());
    }

    public void addToBreakdown(TikTokTrackCountryMetrics countryMetric,
        Map<String, TikTokTrackAnalyticsRangeMetrics> ugc) {
        ugc
            .compute(countryMetric.getCountryCode(), (k, existing) -> {
                TikTokTrackAnalyticsRangeMetrics result =
                    existing == null ? new TikTokTrackAnalyticsRangeMetrics() : existing;
                mergeMetrics(countryMetric, result);
                return result;
            });
    }

    protected static void mergeRangeMetrics(TikTokTrackAnalyticsRangeMetrics target, TikTokTrackAnalyticsRangeMetrics source) {
        mergeMetric(source.getCommentsByDate(), target.getCommentsByDate());
        mergeMetric(source.getCreationsByDate(), target.getCreationsByDate());
        mergeMetric(source.getFavoritesByDate(), target.getFavoritesByDate());
        mergeMetric(source.getSharesByDate(), target.getSharesByDate());
        mergeMetric(source.getLikesByDate(), target.getLikesByDate());
        mergeMetric(source.getVideoViewsByDate(), target.getVideoViewsByDate());
    }

    private static void mergeMetric(Map<LocalDate, Long> sourceMap, Map<LocalDate, Long> targetMap) {
        if (sourceMap != null) {
            sourceMap.forEach((k, v) -> targetMap.merge(k, v, Long::sum));
        }
    }

    private void mergeMetrics(TikTokTrackCountryMetrics countryMetrics, TikTokTrackAnalyticsRangeMetrics rangeMetrics) {
        // @formatter:off each property on a single line
        mergeMetric(rangeMetrics.getCommentsByDate(), countryMetrics.getDate(), countryMetrics.getStats().getComments());
        mergeMetric(rangeMetrics.getCreationsByDate(), countryMetrics.getDate(), countryMetrics.getStats().getCreations());
        mergeMetric(rangeMetrics.getFavoritesByDate(), countryMetrics.getDate(), countryMetrics.getStats().getFavorites());
        mergeMetric(rangeMetrics.getLikesByDate(), countryMetrics.getDate(), countryMetrics.getStats().getLikes());
        mergeMetric(rangeMetrics.getSharesByDate(), countryMetrics.getDate(), countryMetrics.getStats().getShares());
        mergeMetric(rangeMetrics.getVideoViewsByDate(), countryMetrics.getDate(), countryMetrics.getStats().getVideoViews());
        // @formatter:on
    }

    private void mergeMetric(Map<LocalDate, Long> targetMetricsByDate, LocalDate date, Long value) {
        targetMetricsByDate.compute(date, (k, existing) -> existing == null ? value : existing + value);
    }

}
