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

import org.openapitools.jackson.nullable.JsonNullable;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import fr.xebia.extras.selma.Selma;
import io.delphiplatform.api.v3.model.mapper.YouTubeDimensionsMapper;
import io.delphiplatform.api.v3.model.video.YouTubeAgeBands;
import io.delphiplatform.api.v3.model.video.YouTubeDeviceType;
import io.delphiplatform.api.v3.model.video.YouTubeDimensions;
import io.delphiplatform.api.v3.model.video.YouTubeGenders;
import io.delphiplatform.api.v3.model.video.YouTubeMetrics;
import io.delphiplatform.api.v3.model.video.YouTubeOperatingSystem;
import io.delphiplatform.api.v3.model.video.YouTubeSummaryItem;
import io.delphiplatform.api.v3.model.video.YouTubeTrafficSourceTypes;

import static io.delphiplatform.api.util.model.ModelUtils.addAndReplaceZeroWithNull;
import static io.delphiplatform.api.util.model.ModelUtils.addAndReplaceZeroWithNullFloat;
import static io.delphiplatform.api.util.model.ModelUtils.getUniqueOrNull;
import static io.delphiplatform.api.util.model.ModelUtils.merge;
import static io.delphiplatform.api.util.model.ModelUtils.mergeObjects;
import static io.delphiplatform.api.util.model.ModelUtils.weightedAverage;

@Service
public class YouTubeSummariesMergingService {

    private final YouTubeDimensionsMapper youTubeDimensionsMapper;
    private final Function<YouTubeSummaryItem, Long> viewsExtractor = i -> i.getMetrics().getViews().orElse(0L);
    private final Function<YouTubeSummaryItem, Long> populatedAgeBandViewsExtractor =
        i -> i.getMetrics().getAgeBandViewsPercentages().isPresent() ?
            i.getMetrics().getViews().orElse(0L) : 0;

    public YouTubeSummariesMergingService() {
        this.youTubeDimensionsMapper = Selma.builder(YouTubeDimensionsMapper.class).build();
    }

    public YouTubeDimensions mergeDimensions(List<YouTubeSummaryItem> summaries) {
        YouTubeDimensions result = new YouTubeDimensions();
        List<YouTubeDimensions> collect = summaries.stream()
            .map(YouTubeSummaryItem::getDimensions)
            .collect(Collectors.toList());

        result.contentType(getUniqueOrNull(collect, d -> d.getContentType().orElse(null)));
        result.channelId(getUniqueOrNull(collect, d -> d.getChannelId().orElse(null)));
        result.dspVideoId(getUniqueOrNull(collect, d -> d.getDspVideoId().orElse(null)));
        result.isrc(getUniqueOrNull(collect, d -> d.getIsrc().orElse(null)));

        return result;
    }

    public YouTubeMetrics mergeMetrics(List<YouTubeSummaryItem> summaries) {
        YouTubeMetrics summed = summaries.parallelStream()
            .map(YouTubeSummaryItem::getMetrics)
            .reduce(new YouTubeMetrics(), (a, b) -> mergeObjects(a, b, this::add));

        summed.averageViewDurationSeconds(weightedAverageViewsDurationSeconds(summaries));
        summed.averageViewDurationPercentage(weightedAverageViewsDurationPercent(summaries));
        summed.setGenderViewsPercentages(toAverageGenders(summaries));
        summed.setAgeBandViewsPercentages(toAverageAgeBands(summaries));
        return summed;
    }

    public float weightedAverageViewsDurationSeconds(List<YouTubeSummaryItem> items) {
        return weightedAverage(items, viewsExtractor,
            item -> item.getMetrics().getAverageViewDurationSeconds().orElse(0F));
    }

    public float weightedAverageViewsDurationPercent(List<YouTubeSummaryItem> items) {
        return weightedAverage(items, viewsExtractor,
            item -> item.getMetrics().getAverageViewDurationPercentage().orElse(0F));
    }

    private JsonNullable<YouTubeAgeBands> toAverageAgeBands(List<YouTubeSummaryItem> summaries) {
        // @formatter:off each property on a single line
        YouTubeAgeBands result = new YouTubeAgeBands();

        result.all1317(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll1317)));
        result.all1824(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll1824)));
        result.all2534(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll2534)));
        result.all3544(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll3544)));
        result.all4554(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll4554)));
        result.all5564(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll5564)));
        result.all65Plus(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getAll65Plus)));
        result.female1317(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale1317)));
        result.female1824(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale1824)));
        result.female2534(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale2534)));
        result.female3544(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale3544)));
        result.female4554(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale4554)));
        result.female5564(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale5564)));
        result.female65Plus(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getFemale65Plus)));
        result.male1317(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale1317)));
        result.male1824(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale1824)));
        result.male2534(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale2534)));
        result.male3544(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale3544)));
        result.male4554(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale4554)));
        result.male5564(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale5564)));
        result.male65Plus(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getMale65Plus)));
        result.unknown1317(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown1317)));
        result.unknown1824(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown1824)));
        result.unknown2534(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown2534)));
        result.unknown3544(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown3544)));
        result.unknown4554(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown4554)));
        result.unknown5564(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown5564)));
        result.unknown65Plus(weightedAverage(summaries, populatedAgeBandViewsExtractor, ageBandsExtractor(YouTubeAgeBands::getUnknown65Plus)));
        // @formatter:on
        return JsonNullable.of(result);
    }

    private JsonNullable<YouTubeGenders> toAverageGenders(List<YouTubeSummaryItem> summaries) {
        YouTubeGenders result = new YouTubeGenders();
        result.female(weightedAverage(summaries, populatedAgeBandViewsExtractor, gendersExtractor(YouTubeGenders::getFemale)));
        result.male(weightedAverage(summaries, populatedAgeBandViewsExtractor, gendersExtractor(YouTubeGenders::getMale)));
        result.unknown(weightedAverage(summaries, populatedAgeBandViewsExtractor, gendersExtractor(YouTubeGenders::getUnknown)));
        return JsonNullable.of(result);
    }

    private Function<YouTubeSummaryItem, Float> ageBandsExtractor(
        Function<YouTubeAgeBands, JsonNullable<Float>> valueExtractor) {
        return summary -> {
            if (summary.getMetrics().getAgeBandViewsPercentages() != null
                && summary.getMetrics().getAgeBandViewsPercentages().isPresent()) {
                return valueExtractor.apply(summary.getMetrics().getAgeBandViewsPercentages().get()).orElse(0F);
            }
            return 0F;
        };
    }

    private Function<YouTubeSummaryItem, Float> gendersExtractor(
        Function<YouTubeGenders, JsonNullable<Float>> valueExtractor) {
        return summary -> {
            if (summary.getMetrics().getGenderViewsPercentages() != null
                && summary.getMetrics().getGenderViewsPercentages().isPresent()) {
                return valueExtractor.apply(summary.getMetrics().getGenderViewsPercentages().get()).orElse(0F);
            }
            return 0F;
        };
    }

    private YouTubeMetrics add(YouTubeMetrics info1, YouTubeMetrics info2) {
        YouTubeMetrics sum = new YouTubeMetrics();
        // @formatter:off each property on a single line
        sum.setAverageViewDurationPercentage(addAndReplaceZeroWithNullFloat(info1.getAverageViewDurationPercentage(), info2.getAverageViewDurationPercentage()));
        sum.setAverageViewDurationSeconds(addAndReplaceZeroWithNullFloat(info1.getAverageViewDurationSeconds(), info2.getAverageViewDurationSeconds()));
        sum.setComments(addAndReplaceZeroWithNull(info1.getComments(), info2.getComments()));
        sum.setDislikes(addAndReplaceZeroWithNull(info1.getDislikes(), info2.getDislikes()));
        sum.setLikes(addAndReplaceZeroWithNull(info1.getLikes(), info2.getLikes()));
        sum.setShares(addAndReplaceZeroWithNull(info1.getShares(), info2.getShares()));
        sum.setViews(addAndReplaceZeroWithNull(info1.getViews(), info2.getViews()));

        sum.setAgeBandViewsPercentages(merge(info1.getAgeBandViewsPercentages(), info2.getAgeBandViewsPercentages(), this::add));
        sum.setGenderViewsPercentages(merge(info1.getGenderViewsPercentages(), info2.getGenderViewsPercentages(), this::add));
        sum.setTrafficSourceTypes(merge(info1.getTrafficSourceTypes(), info2.getTrafficSourceTypes(), this::add));
        sum.setDeviceType(merge(info1.getDeviceType(), info2.getDeviceType(), this::add));
        sum.setOperatingSystem(merge(info1.getOperatingSystem(), info2.getOperatingSystem(), this::add));
        // @formatter:on
        return sum;
    }

    private YouTubeAgeBands add(YouTubeAgeBands info1, YouTubeAgeBands info2) {
        YouTubeAgeBands sum = new YouTubeAgeBands();
        sum.setAll1317(addAndReplaceZeroWithNullFloat(info1.getAll1317(), info2.getAll1317()));
        sum.setAll1824(addAndReplaceZeroWithNullFloat(info1.getAll1824(), info2.getAll1824()));
        sum.setAll2534(addAndReplaceZeroWithNullFloat(info1.getAll2534(), info2.getAll2534()));
        sum.setAll3544(addAndReplaceZeroWithNullFloat(info1.getAll3544(), info2.getAll3544()));
        sum.setAll4554(addAndReplaceZeroWithNullFloat(info1.getAll4554(), info2.getAll4554()));
        sum.setAll5564(addAndReplaceZeroWithNullFloat(info1.getAll5564(), info2.getAll5564()));
        sum.setAll65Plus(addAndReplaceZeroWithNullFloat(info1.getAll65Plus(), info2.getAll65Plus()));
        sum.setFemale1317(addAndReplaceZeroWithNullFloat(info1.getFemale1317(), info2.getFemale1317()));
        sum.setFemale1824(addAndReplaceZeroWithNullFloat(info1.getFemale1824(), info2.getFemale1824()));
        sum.setFemale2534(addAndReplaceZeroWithNullFloat(info1.getFemale2534(), info2.getFemale2534()));
        sum.setFemale3544(addAndReplaceZeroWithNullFloat(info1.getFemale3544(), info2.getFemale3544()));
        sum.setFemale4554(addAndReplaceZeroWithNullFloat(info1.getFemale4554(), info2.getFemale4554()));
        sum.setFemale5564(addAndReplaceZeroWithNullFloat(info1.getFemale5564(), info2.getFemale5564()));
        sum.setFemale65Plus(addAndReplaceZeroWithNullFloat(info1.getFemale65Plus(), info2.getFemale65Plus()));
        sum.setMale1317(addAndReplaceZeroWithNullFloat(info1.getMale1317(), info2.getMale1317()));
        sum.setMale1824(addAndReplaceZeroWithNullFloat(info1.getMale1824(), info2.getMale1824()));
        sum.setMale2534(addAndReplaceZeroWithNullFloat(info1.getMale2534(), info2.getMale2534()));
        sum.setMale3544(addAndReplaceZeroWithNullFloat(info1.getMale3544(), info2.getMale3544()));
        sum.setMale4554(addAndReplaceZeroWithNullFloat(info1.getMale4554(), info2.getMale4554()));
        sum.setMale5564(addAndReplaceZeroWithNullFloat(info1.getMale5564(), info2.getMale5564()));
        sum.setMale65Plus(addAndReplaceZeroWithNullFloat(info1.getMale65Plus(), info2.getMale65Plus()));
        sum.setUnknown1317(addAndReplaceZeroWithNullFloat(info1.getUnknown1317(), info2.getUnknown1317()));
        sum.setUnknown1824(addAndReplaceZeroWithNullFloat(info1.getUnknown1824(), info2.getUnknown1824()));
        sum.setUnknown2534(addAndReplaceZeroWithNullFloat(info1.getUnknown2534(), info2.getUnknown2534()));
        sum.setUnknown3544(addAndReplaceZeroWithNullFloat(info1.getUnknown3544(), info2.getUnknown3544()));
        sum.setUnknown4554(addAndReplaceZeroWithNullFloat(info1.getUnknown4554(), info2.getUnknown4554()));
        sum.setUnknown5564(addAndReplaceZeroWithNullFloat(info1.getUnknown5564(), info2.getUnknown5564()));
        sum.setUnknown65Plus(addAndReplaceZeroWithNullFloat(info1.getUnknown65Plus(), info2.getUnknown65Plus()));
        return sum;
    }

    private YouTubeGenders add(YouTubeGenders info1, YouTubeGenders info2) {
        YouTubeGenders sum = new YouTubeGenders();
        sum.setFemale(addAndReplaceZeroWithNullFloat(info1.getFemale(), info2.getFemale()));
        sum.setMale(addAndReplaceZeroWithNullFloat(info1.getMale(), info2.getMale()));
        sum.setUnknown(addAndReplaceZeroWithNullFloat(info1.getUnknown(), info2.getUnknown()));
        return sum;
    }

    private YouTubeTrafficSourceTypes add(YouTubeTrafficSourceTypes info1, YouTubeTrafficSourceTypes info2) {
        // @formatter:off each property on a single line
        YouTubeTrafficSourceTypes sum = new YouTubeTrafficSourceTypes();
        sum.setBrowseFeatures(addAndReplaceZeroWithNull(info1.getBrowseFeatures(), info2.getBrowseFeatures()));
        sum.setDirectOrUnknown(addAndReplaceZeroWithNull(info1.getDirectOrUnknown(), info2.getDirectOrUnknown()));
        sum.setExternal(addAndReplaceZeroWithNull(info1.getExternal(), info2.getExternal()));
        sum.setInteractiveVideoEndscreen(addAndReplaceZeroWithNull(info1.getInteractiveVideoEndscreen(), info2.getInteractiveVideoEndscreen()));
        sum.setNotifications(addAndReplaceZeroWithNull(info1.getNotifications(), info2.getNotifications()));
        sum.setOtherYoutubeFeatures(addAndReplaceZeroWithNull(info1.getOtherYoutubeFeatures(), info2.getOtherYoutubeFeatures()));
        sum.setPlaylistPages(addAndReplaceZeroWithNull(info1.getPlaylistPages(), info2.getPlaylistPages()));
        sum.setPlaylists(addAndReplaceZeroWithNull(info1.getPlaylists(), info2.getPlaylists()));
        sum.setProgrammingFromClaimedContent(addAndReplaceZeroWithNull(info1.getProgrammingFromClaimedContent(), info2.getProgrammingFromClaimedContent()));
        sum.setShorts(addAndReplaceZeroWithNull(info1.getShorts(), info2.getShorts()));
        sum.setStories(addAndReplaceZeroWithNull(info1.getStories(), info2.getStories()));
        sum.setSuggestedVideos(addAndReplaceZeroWithNull(info1.getSuggestedVideos(), info2.getSuggestedVideos()));
        sum.setVideoCardsAndAnnotations(addAndReplaceZeroWithNull(info1.getVideoCardsAndAnnotations(), info2.getVideoCardsAndAnnotations()));
        sum.setYoutubeAdvertising(addAndReplaceZeroWithNull(info1.getYoutubeAdvertising(), info2.getYoutubeAdvertising()));
        sum.setYoutubeChannels(addAndReplaceZeroWithNull(info1.getYoutubeChannels(), info2.getYoutubeChannels()));
        sum.setYoutubeSearch(addAndReplaceZeroWithNull(info1.getYoutubeSearch(), info2.getYoutubeSearch()));
        sum.setUnknown(addAndReplaceZeroWithNull(info1.getUnknown(), info2.getUnknown()));
        sum.setProductPages(addAndReplaceZeroWithNull(info1.getProductPages(), info2.getProductPages()));
        sum.setHashtagPages(addAndReplaceZeroWithNull(info1.getHashtagPages(), info2.getHashtagPages()));
        sum.setSoundPages(addAndReplaceZeroWithNull(info1.getSoundPages(), info2.getSoundPages()));
        // @formatter:on
        return sum;
    }

    private YouTubeOperatingSystem add(YouTubeOperatingSystem info1, YouTubeOperatingSystem info2) {
        YouTubeOperatingSystem sum = new YouTubeOperatingSystem();
        sum.setOther(addAndReplaceZeroWithNull(info1.getOther(), info2.getOther()));
        sum.setWindows(addAndReplaceZeroWithNull(info1.getWindows(), info2.getWindows()));
        sum.setWindowsMobile(addAndReplaceZeroWithNull(info1.getWindowsMobile(), info2.getWindowsMobile()));
        sum.setAndroid(addAndReplaceZeroWithNull(info1.getAndroid(), info2.getAndroid()));
        sum.setIos(addAndReplaceZeroWithNull(info1.getIos(), info2.getIos()));
        sum.setSymbian(addAndReplaceZeroWithNull(info1.getSymbian(), info2.getSymbian()));
        sum.setBlackberry(addAndReplaceZeroWithNull(info1.getBlackberry(), info2.getBlackberry()));
        sum.setMac(addAndReplaceZeroWithNull(info1.getMac(), info2.getMac()));
        sum.setPlaystation(addAndReplaceZeroWithNull(info1.getPlaystation(), info2.getPlaystation()));
        sum.setBada(addAndReplaceZeroWithNull(info1.getBada(), info2.getBada()));
        sum.setWebOs(addAndReplaceZeroWithNull(info1.getWebOs(), info2.getWebOs()));
        sum.setLinux(addAndReplaceZeroWithNull(info1.getLinux(), info2.getLinux()));
        sum.setHiptop(addAndReplaceZeroWithNull(info1.getHiptop(), info2.getHiptop()));
        sum.setMeego(addAndReplaceZeroWithNull(info1.getMeego(), info2.getMeego()));
        sum.setWii(addAndReplaceZeroWithNull(info1.getWii(), info2.getWii()));
        sum.setXbox(addAndReplaceZeroWithNull(info1.getXbox(), info2.getXbox()));
        sum.setPlaystationVita(addAndReplaceZeroWithNull(info1.getPlaystationVita(), info2.getPlaystationVita()));
        sum.setSmartTv(addAndReplaceZeroWithNull(info1.getSmartTv(), info2.getSmartTv()));
        sum.setNintendo3ds(addAndReplaceZeroWithNull(info1.getNintendo3ds(), info2.getNintendo3ds()));
        sum.setChromecast(addAndReplaceZeroWithNull(info1.getChromecast(), info2.getChromecast()));
        sum.setTizen(addAndReplaceZeroWithNull(info1.getTizen(), info2.getTizen()));
        sum.setFirefox(addAndReplaceZeroWithNull(info1.getFirefox(), info2.getFirefox()));
        sum.setRealmedia(addAndReplaceZeroWithNull(info1.getRealmedia(), info2.getRealmedia()));
        sum.setKaios(addAndReplaceZeroWithNull(info1.getKaios(), info2.getKaios()));
        sum.setRoku(addAndReplaceZeroWithNull(info1.getRoku(), info2.getRoku()));
        sum.setNintendoSwitch(addAndReplaceZeroWithNull(info1.getNintendoSwitch(), info2.getNintendoSwitch()));
        sum.setAppleTvos(addAndReplaceZeroWithNull(info1.getAppleTvos(), info2.getAppleTvos()));
        sum.setFireOs(addAndReplaceZeroWithNull(info1.getFireOs(), info2.getFireOs()));
        sum.setChromeOs(addAndReplaceZeroWithNull(info1.getChromeOs(), info2.getChromeOs()));
        sum.setUnknown(addAndReplaceZeroWithNull(info1.getUnknown(), info2.getUnknown()));
        sum.setVidaa(addAndReplaceZeroWithNull(info1.getVidaa(), info2.getVidaa()));
        return sum;
    }

    private YouTubeDeviceType add(YouTubeDeviceType info1, YouTubeDeviceType info2) {
        YouTubeDeviceType sum = new YouTubeDeviceType();
        sum.setDesktop(addAndReplaceZeroWithNull(info1.getDesktop(), info2.getDesktop()));
        sum.setMobile(addAndReplaceZeroWithNull(info1.getMobile(), info2.getMobile()));
        sum.setTv(addAndReplaceZeroWithNull(info1.getTv(), info2.getTv()));
        sum.setTablet(addAndReplaceZeroWithNull(info1.getTablet(), info2.getTablet()));
        sum.setGameConsole(addAndReplaceZeroWithNull(info1.getGameConsole(), info2.getGameConsole()));
        sum.setOther(addAndReplaceZeroWithNull(info1.getOther(), info2.getOther()));
        sum.setUnknown(addAndReplaceZeroWithNull(info1.getUnknown(), info2.getUnknown()));
        return sum;
    }

    public YouTubeSummaryItem copy(YouTubeSummaryItem s) {
        YouTubeSummaryItem copied = new YouTubeSummaryItem();
        copied.dimensions(youTubeDimensionsMapper.copy(s.getDimensions()));
        copied.metrics(mergeMetrics(Collections.singletonList(s)));
        return copied;
    }
}
