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

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.Arrays;
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.spotify.SpotifyChartTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyChartTrackLifetimeEntity;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyTrackId;
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 SpotifyChartTrackStateService {

    private final SpotifyChartTrackLifetimeService trackLifetimeService;
    private final SpotifyChartTrackService chartTrackService;
    private final SpotifyTrackService spotifyTrackService;

    @Autowired
    public SpotifyChartTrackStateService(
        SpotifyChartTrackLifetimeService trackLifetimeService,
        SpotifyChartTrackService chartTrackService,
        SpotifyTrackService spotifyTrackService) {
        this.trackLifetimeService = trackLifetimeService;
        this.chartTrackService = chartTrackService;
        this.spotifyTrackService = spotifyTrackService;
    }

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

    public List<ChartTrackState> getSpotifyChartTracks(
        List<String> requestTrackIds,
        List<String> isrcs,
        String chartId,
        LocalDate date,
        Integer minPosition,
        Integer maxPosition,
        Set<ChartTrackStateIncludes> chartTrackStateIncludes,
        ChartTrackMetricsDimension chartTrackMetricsDimension) {
        LocalDate requestDateOrLatest = date == null ? chartTrackService.findMaxDateByChartId(chartId) : date;

        Map<SpotifyTrackId, List<SpotifyChartTrackEntity>> trackIdToTrackMetrics = chartTrackService.findTrackMetrics(
            requestTrackIds, isrcs, chartId, requestDateOrLatest, minPosition, maxPosition, chartTrackMetricsDimension
        );

        return getSpotifyChartTrackStates(chartId, chartTrackStateIncludes, chartTrackMetricsDimension, trackIdToTrackMetrics);
    }

    public List<ChartTrackState> getSpotifyChartTrackStatesDistinct(
        String chartId,
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<SpotifyTrackId, SpotifyChartTrackEntity> trackIdToTrackMetrics
    ) {
        return getSpotifyChartTrackStates(chartId, 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()))));
    }

    public List<ChartTrackState> getSpotifyChartTrackStates(
        String chartId,
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<SpotifyTrackId, List<SpotifyChartTrackEntity>> trackIdToTrackMetrics
    ) {
        Set<SpotifyTrackId> baseTrackIds = trackIdToTrackMetrics.keySet();

        Set<String> spotifyTrackIds = baseTrackIds.stream()
            .map(SpotifyTrackId::getSpotifyTrackId).collect(Collectors.toSet());

        Map<SpotifyTrackId, SpotifyChartTrackLifetimeEntity> trackIdToLifetimeMetrics =
            CollectionUtils.contains(includes, ChartTrackStateIncludes.LIFETIME_METRICS)
                ? trackLifetimeService.findTrackLifetimeMetrics(spotifyTrackIds, chartId)
                : Collections.emptyMap();

        Map<SpotifyTrackId, SpotifyTrackEntity> trackIdToTrack =
            containsAny(includes, List.of(ChartTrackStateIncludes.PUBLIC_META, ChartTrackStateIncludes.LICENSORS))
                ? spotifyTrackService.findTrackIdToTrack(baseTrackIds)
                : Collections.emptyMap();

        return baseTrackIds.stream()
            .sorted(Comparator.comparing(SpotifyTrackId::getSpotifyTrackId))
            .flatMap(trackId -> createChartTrackState(
                trackId, includes, metricsDimension,
                trackIdToTrackMetrics,
                trackIdToLifetimeMetrics,
                trackIdToTrack
            ))
            .collect(Collectors.toList());
    }

    private Stream<ChartTrackState> createChartTrackState(
        SpotifyTrackId trackId,
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension,
        Map<SpotifyTrackId, List<SpotifyChartTrackEntity>> trackIdToTrackMetrics,
        Map<SpotifyTrackId, SpotifyChartTrackLifetimeEntity> trackIdToLifetimeMetrics,
        Map<SpotifyTrackId, SpotifyTrackEntity> trackIdToTrack) {
        SpotifyTrackEntity publicMeta = contains(includes, ChartTrackStateIncludes.PUBLIC_META) ?
            trackIdToTrack.get(trackId) : null;

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

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

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

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

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

}
