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

import org.springframework.beans.factory.annotation.Autowired;
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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.model.ChartTrackStateIncludes;
import io.delphiplatform.api.v3.model.charts.ChartTrackState;
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.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.AppleMusicTrackLocalizedEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedId;
import io.delphiplatform.api.v3.view.util.Params;

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

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

    private final AppleMusicTrackService trackService;
    private final AppleMusicChartTrackService chartTrackService;
    private final AppleMusicChartTrackLifetimeService trackLifetimeService;
    private final AppleMusicArtistService artistService;

    @Autowired
    public AppleMusicChartTrackStateService(
        AppleMusicTrackService trackService, AppleMusicChartTrackService chartTrackService,
        AppleMusicChartTrackLifetimeService trackLifetimeService, AppleMusicArtistService artistService
    ) {
        this.trackService = trackService;
        this.chartTrackService = chartTrackService;
        this.trackLifetimeService = trackLifetimeService;
        this.artistService = artistService;
    }

    public List<ChartTrackState> getAppleMusicChartTracks(Params params) {
        return getAppleMusicChartTracks(params.getTrackId(), params.getIsrc(), params.getSingleChartId(),
            params.getDate(),
            params.getMinPosition(), params.getMaxPosition(), params.getChartTrackStateIncludes(),
            params.getChartTrackMetricsDimension());
    }

    public List<ChartTrackState> getAppleMusicChartTracks(
        List<String> requestTrackIds,
        List<String> isrcs,
        String chartId,
        LocalDate date,
        Integer minPosition,
        Integer maxPosition,
        Set<ChartTrackStateIncludes> chartTrackStateIncludes,
        ChartTrackMetricsDimension metricsDimension) {

        Set<String> appleMusicTrackIdsToQuery = trackService.findAppleMusicTrackIds(requestTrackIds, isrcs);

        if (CollectionUtils.isEmpty(appleMusicTrackIdsToQuery)
            && (CollectionUtils.isNotEmpty(requestTrackIds) || CollectionUtils.isNotEmpty(isrcs))) {

            return Collections.emptyList();
        }

        LocalDate requestDateOrLatest = date == null ? chartTrackService.findMaxDateByChartId(chartId) : date;

        Map<AppleMusicTrackLocalizedId, List<AppleMusicChartTrackEntity>> trackIdToTrackMetrics = chartTrackService
            .findTrackMetricsOnSpecificChartDate(appleMusicTrackIdsToQuery, chartId, requestDateOrLatest, minPosition, maxPosition);

        return getAppleMusicChartTrackStates(chartTrackStateIncludes, metricsDimension, trackIdToTrackMetrics);
    }

    public List<ChartTrackState> getAppleMusicChartTrackStatesDistinct(
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<AppleMusicTrackLocalizedId, AppleMusicChartTrackEntity> trackIdToTrackMetrics
    ) {
        return getAppleMusicChartTrackStates(includes, metricsDimension,
            //repack Map<key, entity> as Map<key, List<entity>> to satisfy interface
            trackIdToTrackMetrics
                .entrySet()
                .stream()
                .collect(Collectors.toMap(Entry::getKey, e -> List.of(e.getValue()))));
    }

    /**
     * @param trackIdToTrackMetrics normally will contain single entity per trackId but in some cases might be multiple (same track on
     *                              several positions at the same time)
     */
    public List<ChartTrackState> getAppleMusicChartTrackStates(
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<AppleMusicTrackLocalizedId, List<AppleMusicChartTrackEntity>> trackIdToTrackMetrics
    ) {
        List<AppleMusicChartTrackEntity> trackEntities = trackIdToTrackMetrics.values()
            .stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

        Map<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> trackIdToTrack =
            containsAny(includes, List.of(ChartTrackStateIncludes.PUBLIC_META, ChartTrackStateIncludes.LICENSORS))
                ? trackService.findTrackIdToTrack(new ArrayList<>(trackIdToTrackMetrics.keySet()))
                : Collections.emptyMap();

        Map<AppleMusicTrackLocalizedId, AppleMusicChartTrackLifetimeEntity> trackIdToLifetimeMetrics =
            contains(includes, ChartTrackStateIncludes.LIFETIME_METRICS) ?
                findLifetimeMetrics(trackEntities) :
                Collections.emptyMap();

        Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> trackToArtists =
            contains(includes, ChartTrackStateIncludes.PUBLIC_META)
                ? artistService.findArtistsByChartTrackEntities(trackEntities)
                : Collections.emptyMap();

        return trackIdToTrackMetrics.keySet().stream()
            .sorted(Comparator.comparing(AppleMusicTrackLocalizedId::getAppleMusicTrackId))
            .flatMap(trackId -> createChartTrackState(
                trackId, includes,
                metricsDimension,
                trackIdToTrackMetrics,
                trackIdToLifetimeMetrics,
                trackIdToTrack,
                trackToArtists
            ))
            .collect(Collectors.toList());
    }

    private Stream<ChartTrackState> createChartTrackState(
        AppleMusicTrackLocalizedId trackId,
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<AppleMusicTrackLocalizedId, List<AppleMusicChartTrackEntity>> trackIdToTrackMetrics,
        Map<AppleMusicTrackLocalizedId, AppleMusicChartTrackLifetimeEntity> trackIdToLifetimeMetrics,
        Map<AppleMusicTrackLocalizedId, AppleMusicTrackLocalizedEntity> trackIdToTrack,
        Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> trackToArtists) {

        List<AppleMusicArtistLocalizedEntity> artists;
        AppleMusicTrackLocalizedEntity publicMeta;

        if (contains(includes, ChartTrackStateIncludes.PUBLIC_META)) {
            publicMeta = trackIdToTrack.get(trackId);
            artists = trackToArtists.get(publicMeta.getId());
        } else {
            artists = null;
            publicMeta = null;
        }

        //normally there will be single entity per trackId but in some cases might be multiple (same track on different positions)
        List<AppleMusicChartTrackEntity> metrics = contains(includes, ChartTrackStateIncludes.METRICS)
            ? trackIdToTrackMetrics.get(trackId) : null;

        AppleMusicChartTrackLifetimeEntity lifetimeMetrics =
            contains(includes, ChartTrackStateIncludes.LIFETIME_METRICS)
                ? trackIdToLifetimeMetrics.get(trackId) : null;

        List<String> licensors = contains(includes, ChartTrackStateIncludes.LICENSORS)
            ? Arrays.asList(trackIdToTrack.get(trackId).getTrack().getLicensors()) : null;

        if (CollectionUtils.isNotEmpty(metrics)) {
            return metrics.stream()
                .map(metric -> ChartTrackState.of(publicMeta, artists, metric, lifetimeMetrics, licensors, metricsDimension));
        }

        return Stream.of(ChartTrackState.of(publicMeta, artists, null, lifetimeMetrics, licensors, metricsDimension));
    }

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

        return trackLifetimeService.findChartTrackLifetimeMetrics(trackLifetimeIds);
    }

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

}
