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

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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.Function;
import java.util.stream.Collectors;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.model.TrackStateIncludes;
import io.delphiplatform.api.v3.model.apple.music.AppleMusicChart;
import io.delphiplatform.api.v3.model.apple.music.AppleMusicTrackChartsSearchResult;
import io.delphiplatform.api.v3.model.charts.ChartTrackLifetimeMetrics;
import io.delphiplatform.api.v3.model.charts.ChartTrackMetadata;
import io.delphiplatform.api.v3.model.charts.ChartTrackStateMetrics;
import io.delphiplatform.api.v3.model.spotify.ChartTrackMetricsDimension;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicArtistLocalizedEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartDateKey;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartTrackLifetimeEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicIsrcAndChartAndTrackIds;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedId;
import io.delphiplatform.api.v3.rdb.service.dto.ChartIdDateIsrcKey;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.util.CollectionUtils.contains;

@Service
@Transactional(readOnly = true)
public class AppleMusicTracksChartsService {

    private final AppleMusicChartService appleMusicChartService;
    private final AppleMusicTrackService trackService;
    private final AppleMusicChartTrackLifetimeService chartTrackLifetimeService;
    private final AppleMusicChartTrackService chartTrackService;
    private final AppleMusicArtistService artistService;

    public AppleMusicTracksChartsService(
        AppleMusicChartService appleMusicChartService,
        AppleMusicTrackService trackService,
        AppleMusicChartTrackLifetimeService chartTrackLifetimeService,
        AppleMusicChartTrackService chartTrackService,
        AppleMusicArtistService artistService
    ) {
        this.appleMusicChartService = appleMusicChartService;
        this.trackService = trackService;
        this.chartTrackLifetimeService = chartTrackLifetimeService;
        this.chartTrackService = chartTrackService;
        this.artistService = artistService;
    }

    public List<AppleMusicTrackChartsSearchResult> getTrackCharts(Params params) {
        return getAppleTrackCharts(
            params.getDate(), params.getTrackIdSet(), params.getIsrcSet(),
            params.getMinPosition(), params.getMaxPosition(),
            params.getMinRank(), params.getMaxRank(),
            params.getCountryCode(),
            params.getTrackStateIncludes(),
            params.getChartTrackMetricsDimension()
        );
    }

    private List<AppleMusicTrackChartsSearchResult> getAppleTrackCharts(
        LocalDate date,
        Set<String> trackIds, Set<String> isrcs,
        Integer minPosition, Integer maxPosition,
        Integer minRank, Integer maxRank,
        Set<String> countryCode,
        Set<TrackStateIncludes> trackStateIncludes,
        ChartTrackMetricsDimension metricsDimension
    ) {
        List<AppleMusicChartTrackEntity> chartTrackMetrics;

        Set<AppleMusicChartDateKey> chartLastDateKeys = null;

        if (date == null) {
            chartLastDateKeys = chartTrackService.findLastChartDates()
                .stream()
                .map(AppleMusicChartDateKey::of)
                .collect(Collectors.toSet());
        }

        if (metricsDimension == null || metricsDimension == ChartTrackMetricsDimension.TRACK_ID) {
            chartTrackMetrics = chartTrackService.findChartTrackAtDateOrChartDateDimensionTrack(
                chartLastDateKeys,
                date, trackIds, isrcs, minPosition, maxPosition,
                minRank, maxRank, countryCode
            );
        } else {
            List<AppleMusicChartTrackEntity> metricsList = chartTrackService.findChartTrackAtDateOrChartDateDimensionIsrc(
                chartLastDateKeys,
                date, trackIds, isrcs, minPosition, maxPosition,
                minRank, maxRank, countryCode
            );

            //de-duplicate result list for case when we have >1 facts for same chartId/chartDate/isrc. Not done in DB to avoid complex query/DB load for rare case
            Map<ChartIdDateIsrcKey, AppleMusicChartTrackEntity> metricsByChartKey = new HashMap<>();

            for (AppleMusicChartTrackEntity metric : metricsList) {
                ChartIdDateIsrcKey key = ChartIdDateIsrcKey.of(metric);

                if (!metricsByChartKey.containsKey(key) || metricsByChartKey.get(key).getCurrentPosition() > metric.getCurrentPosition()) {
                    metricsByChartKey.put(key, metric);
                }
            }

            chartTrackMetrics = new ArrayList<>(metricsByChartKey.values());
        }

        populateNecessaryLazyFieldsForFactEntity(chartTrackMetrics);

        return getAppleTrackChartsSearchResults(trackStateIncludes, metricsDimension, chartTrackMetrics);
    }

    public List<AppleMusicTrackChartsSearchResult> getAppleTrackChartsSearchResults(
        Set<TrackStateIncludes> trackStateIncludes,
        ChartTrackMetricsDimension metricsDimension,
        List<AppleMusicChartTrackEntity> chartTrackMetrics) {
        Map<AppleMusicIsrcAndChartAndTrackIds, AppleMusicChartTrackLifetimeEntity> lifetimeMetricsById =
            contains(trackStateIncludes, TrackStateIncludes.LIFETIME_METRICS) ?
                findLifetimeMetrics(chartTrackMetrics) :
                Collections.emptyMap();

        Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> artists =
            CollectionUtils.contains(trackStateIncludes, TrackStateIncludes.PUBLIC_META)
                ? artistService.findArtistsByChartTrackEntities(chartTrackMetrics)
                : Collections.emptyMap();

        return chartTrackMetrics.stream()
            .map(chartTrackMetric -> buildTrackChartsSearchResult(
                chartTrackMetric,
                artists.getOrDefault(chartTrackMetric.getLocalizedTrack().getId(), Collections.emptyList()),
                lifetimeMetricsById.getOrDefault(
                    new AppleMusicIsrcAndChartAndTrackIds(
                        chartTrackMetric.getAppleMusicChartId(),
                        chartTrackMetric.getAppleMusicTrackId(),
                        chartTrackMetric.getIsrc()
                    ), null),
                trackStateIncludes,
                metricsDimension))
            .collect(Collectors.toList());
    }

    public List<AppleMusicTrackChartsSearchResult> getAppleMusicTracksChartsLifetime(Params params) {
        return getAppleMusicTracksChartsLifetime(
            params.getTrackIdSet(), params.getIsrcSet(),
            params.getMinRank(), params.getMaxRank(),
            params.getCountryCode(),
            params.getTrackStateIncludes(),
            params.getChartTrackMetricsDimension()
        );
    }

    private List<AppleMusicTrackChartsSearchResult> getAppleMusicTracksChartsLifetime(
        Set<String> trackIds, Set<String> isrcs,
        Integer minRank, Integer maxRank,
        Set<String> countryCode,
        Set<TrackStateIncludes> trackStateIncludes,
        ChartTrackMetricsDimension metricsDimension
    ) {
        List<AppleMusicChartTrackLifetimeEntity> lifetimeMetrics = chartTrackLifetimeService.findChartTrackLifetimeMetrics(
            trackIds, isrcs,
            minRank, maxRank,
            countryCode
        );

        Map<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> localizedTracksById =
            trackService.findTrackIdToTrack(
                lifetimeMetrics
                    .stream()
                    .map(lm -> new AppleMusicTrackLocalizedId(lm.getAppleMusicTrackId(), lm.getChart().getAppleMusicCountryId()))
                    .collect(Collectors.toList())
            );

        Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> localizedArtistsById =
            CollectionUtils.contains(trackStateIncludes, TrackStateIncludes.PUBLIC_META)
                ? artistService.findArtists(localizedTracksById.values())
                : Collections.emptyMap();

        return lifetimeMetrics.stream()
            .map(lifetimeMetric -> buildTrackChartsSearchResultFromLifetime(
                lifetimeMetric,
                localizedTracksById,
                localizedArtistsById,
                trackStateIncludes,
                metricsDimension
            ))
            .collect(Collectors.toList());
    }

    private AppleMusicTrackChartsSearchResult buildTrackChartsSearchResultFromLifetime(
        AppleMusicChartTrackLifetimeEntity lifetimeMetricEntity,
        Map<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> localizedTracksById,
        Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> localizedArtistsByTrackId,
        Set<TrackStateIncludes> trackStateIncludes,
        ChartTrackMetricsDimension metricsDimension
    ) {
        AppleMusicTrackLocalizedId localizedTrackId = new AppleMusicTrackLocalizedId(
            lifetimeMetricEntity.getAppleMusicTrackId(), lifetimeMetricEntity.getChart().getAppleMusicCountryId()
        );

        AppleMusicChart chartMeta = contains(trackStateIncludes, TrackStateIncludes.CHART_META) ?
            AppleMusicChart.of(lifetimeMetricEntity.getChart()) : null;

        List<String> licensors = contains(trackStateIncludes, TrackStateIncludes.LICENSORS) ?
            Arrays.asList(lifetimeMetricEntity.getTrack().getLicensors()) : null;

        ChartTrackMetadata chartTrack = contains(trackStateIncludes, TrackStateIncludes.PUBLIC_META) ?
            ChartTrackMetadata.of(localizedTracksById.get(localizedTrackId), localizedArtistsByTrackId.get(localizedTrackId)) : null;

        ChartTrackLifetimeMetrics lifetimeMetric = contains(trackStateIncludes, TrackStateIncludes.LIFETIME_METRICS) ?
            ChartTrackLifetimeMetrics.of(lifetimeMetricEntity, metricsDimension) : null;

        return AppleMusicTrackChartsSearchResult
            .builder()
            .chartMeta(chartMeta)
            .licensors(licensors)
            .publicMeta(chartTrack)
            .lifetimeMetrics(lifetimeMetric)
            .build();
    }

    private AppleMusicTrackChartsSearchResult buildTrackChartsSearchResult(
        AppleMusicChartTrackEntity chartTrackMetricEntity,
        List<AppleMusicArtistLocalizedEntity> artists,
        AppleMusicChartTrackLifetimeEntity lifetimeMetricEntity,
        Set<TrackStateIncludes> trackStateIncludes,
        ChartTrackMetricsDimension metricsDimension) {
        AppleMusicChart chartMeta = contains(trackStateIncludes, TrackStateIncludes.CHART_META) ?
            AppleMusicChart.of(chartTrackMetricEntity.getChart()) : null;

        List<String> licensors = contains(trackStateIncludes, TrackStateIncludes.LICENSORS) ?
            Arrays.asList(chartTrackMetricEntity.getTrack().getLicensors()) : null;

        ChartTrackMetadata chartTrack = contains(trackStateIncludes, TrackStateIncludes.PUBLIC_META) ?
            ChartTrackMetadata.of(chartTrackMetricEntity.getLocalizedTrack(), artists) : null;

        ChartTrackStateMetrics metrics = contains(trackStateIncludes, TrackStateIncludes.METRICS)
            ? ChartTrackStateMetrics.of(chartTrackMetricEntity, metricsDimension) : null;

        ChartTrackLifetimeMetrics lifetimeMetric = ChartTrackLifetimeMetrics.of(lifetimeMetricEntity, metricsDimension);

        return AppleMusicTrackChartsSearchResult
            .builder()
            .chartMeta(chartMeta)
            .licensors(licensors)
            .publicMeta(chartTrack)
            .metrics(metrics)
            .lifetimeMetrics(lifetimeMetric)
            .build();
    }

    private Map<AppleMusicIsrcAndChartAndTrackIds, AppleMusicChartTrackLifetimeEntity> findLifetimeMetrics(
        List<AppleMusicChartTrackEntity> chartTrackMetrics) {
        List<AppleMusicIsrcAndChartAndTrackIds> trackLifetimeIds = buildAppleChartTrackLifetimeIds(chartTrackMetrics);

        return chartTrackLifetimeService.findChartTrackLifetimeMetricsByIds(trackLifetimeIds);
    }

    private List<AppleMusicIsrcAndChartAndTrackIds> buildAppleChartTrackLifetimeIds(
        List<AppleMusicChartTrackEntity> chartTrackMetrics) {
        return chartTrackMetrics.stream()
            .map(e -> new AppleMusicIsrcAndChartAndTrackIds(e.getAppleMusicChartId(), e.getAppleMusicTrackId(),
                e.getIsrc()))
            .collect(Collectors.toList());
    }

    private void populateNecessaryLazyFieldsForFactEntity(List<AppleMusicChartTrackEntity> chartTrackMetrics) {
        if (CollectionUtils.isNotEmpty(chartTrackMetrics)) {
            Map<String, AppleMusicChartEntity> allChartsById = appleMusicChartService.findChartsByIdsWithMarketRank(
                    chartTrackMetrics.stream()
                        .map(AppleMusicChartTrackEntity::getAppleMusicChartId)
                        .collect(Collectors.toList())
                )
                .stream().collect(Collectors.toMap(AppleMusicChartEntity::getAppleMusicChartId, Function.identity()));

            Map<String, AppleMusicTrackEntity> allTracksById = trackService.findTracksWithArtistsByTrackIds(
                    chartTrackMetrics.stream()
                        .map(AppleMusicChartTrackEntity::getAppleMusicTrackId)
                        .collect(Collectors.toSet())
                )
                .stream().collect(Collectors.toMap(AppleMusicTrackEntity::getAppleMusicTrackId, Function.identity()));

            Map<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> allLocalizedTracksById = trackService.findTrackIdToTrack(
                chartTrackMetrics.stream()
                    .map(fact -> new AppleMusicTrackLocalizedId(fact.getAppleMusicTrackId(), fact.getAppleMusicCountryId()))
                    .collect(Collectors.toList())
            );

            for (Entry<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> entry : allLocalizedTracksById.entrySet()) {
                AppleMusicTrackLocalizedEntity localizedTrack = entry.getValue();

                localizedTrack.setTrack(
                    allTracksById.get(localizedTrack.getId().getAppleMusicTrackId())
                );
            }

            for (AppleMusicChartTrackEntity fact : chartTrackMetrics) {
                fact.setChart(allChartsById.get(fact.getAppleMusicChartId()));
                fact.setTrack(allTracksById.get(fact.getAppleMusicTrackId()));
                fact.setLocalizedTrack(
                    allLocalizedTracksById.get(new AppleMusicTrackLocalizedId(fact.getAppleMusicTrackId(), fact.getAppleMusicCountryId())));
            }
        }
    }
}
