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

import com.google.common.collect.ImmutableMap;

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import io.delphiplatform.api.util.model.ModelUtils;
import io.delphiplatform.api.v3.model.ArtistSimple;
import io.delphiplatform.api.v3.model.Region;
import io.delphiplatform.api.v3.model.ads.AdDimensions;
import io.delphiplatform.api.v3.model.ads.AdSet;
import io.delphiplatform.api.v3.model.ads.CampaignSimple;
import io.delphiplatform.api.v3.model.ads.Category;
import io.delphiplatform.api.v3.model.ads.DecibelProjectSimple;
import io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance;
import io.delphiplatform.api.v3.model.ads.LinkfireLink;
import io.delphiplatform.api.v3.model.ads.LinkfireReferrer;
import io.delphiplatform.api.v3.model.ads.Objective;
import io.delphiplatform.api.v3.model.ads.Provider;
import io.delphiplatform.api.v3.model.ads.SubCategory;
import io.delphiplatform.api.v3.rdb.service.ArtistService;
import io.delphiplatform.api.v3.rdb.service.RegionService;
import io.delphiplatform.api.v3.rdb.service.dto.ReportQueryResponse;
import lombok.extern.slf4j.Slf4j;

import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.AGE_BAND;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.CHARTWEEK;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.DATE;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.GENDER;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.LINK_TYPE;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.MONTH;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.PLATFORM;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.REFERRER_DOMAIN;
import static io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance.WEEK;

@Slf4j
@Service
public class AdsResponseGroupByMappingService {


    private static final ImmutableMap<GroupByAdsPerformance, BiConsumer<AdDimensions, ?>> SEPARATE_VALUE_POPULATE =
        ImmutableMap.<GroupByAdsPerformance, BiConsumer<AdDimensions, ?>>builder()
            .put(AGE_BAND, ((adDimensions, o) -> adDimensions.ageBand((String) o)))
            .put(CHARTWEEK, ((adDimensions, o) -> adDimensions.chartweek(ModelUtils.toDate(o))))
            .put(GENDER, ((adDimensions, o) -> adDimensions.gender((String) o)))
            .put(MONTH, ((adDimensions, o) -> adDimensions.month(ModelUtils.toDate(o))))
            .put(LINK_TYPE, ((adDimensions, o) -> adDimensions.getLinkfireOrInit().linkType((String) o)))
            .put(REFERRER_DOMAIN, ((adDimensions, o) -> adDimensions.getLinkfireOrInit().referrerDomain((String) o)))
            .put(PLATFORM, ((adDimensions, o) -> adDimensions.platform((String) o)))
            .put(WEEK, ((adDimensions, o) -> adDimensions.week(ModelUtils.toDate(o))))
            .put(DATE, ((adDimensions, o) -> adDimensions.date(ModelUtils.toDate(o))))
            .build();

    private final ArtistService artistService;
    private final DecibelProjectService decibelProjectService;
    private final AdsAdSetService adsAdSetService;
    private final RegionService regionService;
    private final DecibelCampaignObjectiveService decibelCampaignObjectiveService;
    private final DecibelCampaignProviderService decibelCampaignProviderService;
    private final DecibelCampaignCategoryService decibelCampaignCategoryService;
    private final DecibelCampaignService decibelCampaignService;
    private final DecibelCampaignSubCategoryService decibelCampaignSubCategoryService;
    private final LinkfireDimReferrerService linkfireDimReferrerService;
    private final LinkfireDimLinkService linkfireDimLinkService;

    public AdsResponseGroupByMappingService(ArtistService artistService,
        DecibelProjectService decibelProjectService, AdsAdSetService adsAdSetService,
        DecibelCampaignObjectiveService decibelCampaignObjectiveService,
        DecibelCampaignProviderService decibelCampaignProviderService, RegionService regionService,
        DecibelCampaignCategoryService decibelCampaignCategoryService,
        DecibelCampaignService decibelCampaignService,
        DecibelCampaignSubCategoryService decibelCampaignSubCategoryService,
        LinkfireDimReferrerService linkfireDimReferrerService,
        LinkfireDimLinkService linkfireDimLinkService) {
        this.artistService = artistService;
        this.decibelProjectService = decibelProjectService;
        this.adsAdSetService = adsAdSetService;
        this.decibelCampaignObjectiveService = decibelCampaignObjectiveService;
        this.decibelCampaignProviderService = decibelCampaignProviderService;
        this.regionService = regionService;
        this.decibelCampaignCategoryService = decibelCampaignCategoryService;
        this.decibelCampaignService = decibelCampaignService;
        this.decibelCampaignSubCategoryService = decibelCampaignSubCategoryService;
        this.linkfireDimReferrerService = linkfireDimReferrerService;
        this.linkfireDimLinkService = linkfireDimLinkService;
    }

    public List<AdDimensions> mapAllDimensions(List<ReportQueryResponse> queryResponses,
        List<GroupByAdsPerformance> groupByList) {
        List<AdDimensions> dimensions = queryResponses.stream()
            .map(r -> new AdDimensions())
            .collect(Collectors.toList());

        for (int i = 0; i < groupByList.size(); i++) {
            GroupByAdsPerformance groupBy = groupByList.get(i);
            if (SEPARATE_VALUE_POPULATE.containsKey(groupBy)) {
                populateSeparatedValue(dimensions, queryResponses, i, SEPARATE_VALUE_POPULATE.get(groupBy));
            } else {
                switch (groupBy) {
                    case ARTIST:
                        populateArtist(dimensions, queryResponses, i);
                        break;
                    case PROJECT:
                        populateProject(dimensions, queryResponses, i);
                        break;
                    case AD_SET:
                        populateAdSet(dimensions, queryResponses, i);
                        break;
                    case REGION:
                        populateRegion(dimensions, queryResponses, i);
                        break;
                    case CAMPAIGN:
                        populateCampaign(dimensions, queryResponses, i);
                        break;
                    case OBJECTIVE:
                        populateObjective(dimensions, queryResponses, i);
                        break;
                    case PROVIDER:
                        populateProvider(dimensions, queryResponses, i);
                        break;
                    case SUB_CATEGORY:
                        populateSubCategory(dimensions, queryResponses, i);
                        break;
                    case CATEGORY:
                        populateCategory(dimensions, queryResponses, i);
                        break;
                    case LINK_URL:
                        populateLinkIdAndUrl(dimensions, queryResponses, i);
                        break;
                    case REFERRER_NAME:
                        populateReferrerNameAndDomain(dimensions, queryResponses, i);
                        break;

                    default:
                        break;
                }

            }
        }

        return dimensions;
    }

    private void populateLinkIdAndUrl(List<AdDimensions> dimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        Set<UUID> ids = queryResponses.stream()
            .map(response -> (UUID) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toSet());
        Map<String, LinkfireLink> idToModel = linkfireDimLinkService.findAllById(ids).stream()
            .collect(Collectors.toMap(LinkfireLink::getLinkId, Function.identity()));

        populateSeparatedValue(dimensions, queryResponses, groupByIndex,
            (d, v) -> {
                LinkfireLink model = idToModel.getOrDefault(v.toString(), null);
                if (model != null) {
                    d.getLinkfireOrInit().linkId(model.getLinkId());
                    d.getLinkfireOrInit().linkUrl(model.getLinkUrl());
                }
            });
    }

    private void populateReferrerNameAndDomain(List<AdDimensions> dimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        Set<Long> ids = queryResponses.stream()
            .map(response -> (Long) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toSet());
        Map<Long, LinkfireReferrer> idToModel = linkfireDimReferrerService.findAllById(ids).stream()
            .collect(Collectors.toMap(LinkfireReferrer::getReferrerKey, Function.identity()));

        populateSeparatedValue(dimensions, queryResponses, groupByIndex,
            (d, v) -> {
                LinkfireReferrer model = idToModel.getOrDefault(v, null);
                if (model != null) {
                    d.getLinkfireOrInit().referrer(model.getReferrer());
                    d.getLinkfireOrInit().referrerDomain(model.getReferrerDomain());
                }
            });
    }

    private void populateProvider(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> names = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, Provider> idToModel = decibelCampaignProviderService.findAllByNames(names).stream()
            .collect(Collectors.toMap(Provider::getProviderName, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.provider(idToModel.getOrDefault(v, null)));
    }

    private void populateObjective(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<Integer> ids = queryResponses.stream()
            .map(response -> (Integer) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<Integer, Objective> idToModel = decibelCampaignObjectiveService.findAllById(ids).stream()
            .collect(Collectors.toMap(Objective::getObjectiveId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.objective(idToModel.getOrDefault(v, null)));
    }


    private void populateCampaign(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> ids = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, CampaignSimple> idToModel = decibelCampaignService.findAllById(ids).stream()
            .collect(Collectors.toMap(CampaignSimple::getCampaignId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.campaign(idToModel.getOrDefault(v, new CampaignSimple().campaignId((String) v))));
    }

    private void populateRegion(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> ids = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, Region> idToModel = regionService.findAllById(ids).stream()
            .collect(Collectors.toMap(Region::getRegionId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.region(idToModel.getOrDefault(v, null)));
    }

    private void populateCategory(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<Integer> ids = queryResponses.stream()
            .map(response -> (Integer) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<Integer, Category> idToModel = decibelCampaignCategoryService.findAllById(ids).stream()
            .collect(Collectors.toMap(Category::getCategoryId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.category(idToModel.getOrDefault(v, null)));
    }

    private void populateSubCategory(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<Integer> ids = queryResponses.stream()
            .map(response -> (Integer) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<Integer, SubCategory> idToModel = decibelCampaignSubCategoryService.findAllById(ids).stream()
            .collect(Collectors.toMap(SubCategory::getSubCategoryId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.subCategory(idToModel.getOrDefault(v, null)));
    }

    private void populateAdSet(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> ids = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, AdSet> idToModel = adsAdSetService.findAllById(ids).stream()
            .collect(Collectors.toMap(AdSet::getAdSetId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.adSet(idToModel.getOrDefault(v, null)));
    }

    private void populateProject(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> projectIds = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, DecibelProjectSimple> projectIdToProject = decibelProjectService.findAllById(projectIds).stream()
            .collect(Collectors.toMap(DecibelProjectSimple::getProjectId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.project(projectIdToProject.getOrDefault(v, null)));
    }

    private void populateArtist(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex) {
        List<String> artistsIds = queryResponses.stream()
            .map(response -> (String) response.getGroupBys()[groupByIndex])
            .collect(Collectors.toList());
        Map<String, ArtistSimple> artistIdToArtist = artistService.findAllById(artistsIds).stream()
            .collect(Collectors.toMap(ArtistSimple::getArtistId, Function.identity()));

        populateSeparatedValue(allDimensions, queryResponses, groupByIndex,
            (d, v) -> d.artist(artistIdToArtist.getOrDefault(v, new ArtistSimple(String.valueOf(v)))));
    }

    private <T> void populateSeparatedValue(List<AdDimensions> allDimensions, List<ReportQueryResponse> queryResponses,
        int groupByIndex, BiConsumer<AdDimensions, T> populate) {
        for (int i = 0; i < allDimensions.size(); i++) {
            AdDimensions adDimensions = allDimensions.get(i);
            populate.accept(adDimensions, (T) queryResponses.get(i).getGroupBys()[groupByIndex]);
        }
    }

}
