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.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import io.delphiplatform.api.v3.model.spotify.ChartTrackMetricsDimension;
import io.delphiplatform.api.v3.model.spotify.SpotifyTrackChartsSearchResult;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyChartDateTrackKey;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyChartTrackEntity;
import io.delphiplatform.api.v3.view.util.Params;

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

    private final SpotifyChartTrackService spotifyChartTrackService;
    private final SpotifyTracksChartsService spotifyTracksChartsService;
    private final SpotifyTracksChartChangesService spotifyTracksChartChangesService;

    @Autowired
    public SpotifyTracksChartAdditionsService(
        SpotifyChartTrackService spotifyChartTrackService,
        SpotifyTracksChartsService spotifyTracksChartsService,
        SpotifyTracksChartChangesService spotifyTracksChartChangesService
    ) {
        this.spotifyChartTrackService = spotifyChartTrackService;
        this.spotifyTracksChartsService = spotifyTracksChartsService;
        this.spotifyTracksChartChangesService = spotifyTracksChartChangesService;
    }

    /**
     * Additions are items that exist now, but do not exist in previous date.
     */
    public List<SpotifyTrackChartsSearchResult> getSpotifyChartTracksAdditions(Params params) {
        LocalDate requestedDate = spotifyTracksChartChangesService
            .getRequestedDateOrDefault(params.getDate(), params.getIsrc(), params.getTrackId());
        @Nullable
        LocalDate requestedPreviousDate = params.getPreviousDate();

        //if param.previousDate was passed - use its value for chart date comparison
        //else - use default previousDate values pair(!) for chart date comparison
        // (we would need to search for 'previous' facts at -1 day for DAILY charts and at -7 days for WEEKLY charts)
        LocalDate defaultPreviousDateDaily = spotifyTracksChartChangesService.getDefaultPreviousDateDaily(requestedDate);
        LocalDate defaultPreviousDateWeekly = spotifyTracksChartChangesService.getDefaultPreviousDateWeekly(requestedDate);

        List<SpotifyChartTrackEntity> prevDateTracksToExclude =
            spotifyChartTrackService.findTrackMetricsByDateConsideringChartBreakdown(
                Collections.emptySet(),
                params.getTrackId(),
                params.getIsrc(),
                requestedPreviousDate,
                defaultPreviousDateDaily,
                defaultPreviousDateWeekly,
                params.getMinPosition(),
                params.getMaxPosition(),
                params.getMinRank(),
                params.getMaxRank(),
                params.getCountryCode(),
                params.getBreakdowns(),
                params.getTypes(),
                params.getChartTrackMetricsDimension()
            );

        Set<SpotifyChartDateTrackKey> trackKeysToExclude = prevDateTracksToExclude
            .stream()
            .map(SpotifyChartTrackEntity::getChartDateTrackKey)
            .collect(Collectors.toSet());

        //if request dimension is ISRC - look for all possible 'siblings by isrc' for all found 'prevDateTracksToExclude' that exist at 'requestedDate',
        // so we can exclude such tracks from subsequent request
        if (params.getChartTrackMetricsDimension() == ChartTrackMetricsDimension.ISRC) {
            trackKeysToExclude.addAll(
                spotifyChartTrackService.findTrackKeysWithSameIsrcWithinChartForDate(
                    prevDateTracksToExclude
                        .stream()
                        .map(SpotifyChartTrackEntity::getChartDateTrackKey)
                        .collect(Collectors.toSet()),
                    requestedDate
                )
            );
        }

        //gather keys of 'tracks to exclude' but with 'date' part of key changed to 'requestedDate'
        Set<SpotifyChartDateTrackKey> trackKeysToExcludeFromSubsequentRequest = trackKeysToExclude
            .stream()
            .map(trackId -> {
                SpotifyChartDateTrackKey adjustedLookupTrackKey = new SpotifyChartDateTrackKey();
                adjustedLookupTrackKey.setSpotifyTrackId(trackId.getSpotifyTrackId());
                adjustedLookupTrackKey.setSpotifyChartId(trackId.getSpotifyChartId());
                adjustedLookupTrackKey.setChartDate(requestedDate);

                return adjustedLookupTrackKey;
            })
            .collect(Collectors.toSet());

        List<SpotifyChartTrackEntity> delta = spotifyChartTrackService.findTrackMetrics(
            trackKeysToExcludeFromSubsequentRequest,
            params.getTrackId(),
            params.getIsrc(),
            requestedDate,
            params.getMinPosition(),
            params.getMaxPosition(),
            params.getMinRank(),
            params.getMaxRank(),
            params.getCountryCode(),
            params.getBreakdowns(),
            params.getTypes(),
            params.getChartTrackMetricsDimension()
        );

        return spotifyTracksChartsService.getSpotifyTrackChartsSearchResults(params.getTrackStateIncludes(),
            params.getChartTrackMetricsDimension(), delta);
    }
}
