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.HashSet;
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.SpotifyChartBreakdown;
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 SpotifyTracksChartRemovalsService {

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

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

    /**
     * Removals are items that existed in previous date, but do not exist now.
     */
    public List<SpotifyTrackChartsSearchResult> getSpotifyChartTracksRemovals(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> reqDateTracksToExclude = spotifyChartTrackService.findTrackMetrics(
            params.getTrackId(),
            params.getIsrc(),
            requestedDate,
            params.getMinPosition(),
            params.getMaxPosition(),
            params.getMinRank(),
            params.getMaxRank(),
            params.getCountryCode(),
            params.getBreakdowns(),
            params.getTypes(),
            params.getChartTrackMetricsDimension());

        Set<SpotifyChartTrackEntity> tracksToExcludeFromSubsequentRequest = new HashSet<>(reqDateTracksToExclude);

        //if request dimension is ISRC - look for all possible 'siblings by isrc' for all found 'reqDateTracksToExclude' that exist at 'requestedPreviousDate'(s),
        // so we can exclude such tracks from subsequent request
        if (params.getChartTrackMetricsDimension() == ChartTrackMetricsDimension.ISRC) {
            if (requestedPreviousDate != null) {
                tracksToExcludeFromSubsequentRequest.addAll(
                    spotifyChartTrackService.findTracksWithSameIsrcWithinChartForDate(
                        reqDateTracksToExclude
                            .stream()
                            .map(SpotifyChartTrackEntity::getChartDateTrackKey)
                            .collect(Collectors.toSet()),
                        requestedPreviousDate
                    )
                );
            } else {
                //need to separately look for isrc siblings of tracks from daily and weekly charts
                tracksToExcludeFromSubsequentRequest.addAll(
                    spotifyChartTrackService.findTracksWithSameIsrcWithinChartForDates(
                        reqDateTracksToExclude
                            .stream()
                            .filter(track -> track.getChart().getBreakdown() == SpotifyChartBreakdown.DAILY)
                            .map(SpotifyChartTrackEntity::getChartDateTrackKey)
                            .collect(Collectors.toSet()),
                        reqDateTracksToExclude
                            .stream()
                            .filter(track -> track.getChart().getBreakdown() == SpotifyChartBreakdown.WEEKLY)
                            .map(SpotifyChartTrackEntity::getChartDateTrackKey)
                            .collect(Collectors.toSet()),
                        defaultPreviousDateDaily,
                        defaultPreviousDateWeekly
                    )
                );
            }
        }

        //gather keys of 'tracks to exclude' but with 'date' part of key changed to 'previousDate'
        //(which will be a single date passed in param.previousDate OR one of values from pair(!) of default dates that we computed)
        Set<SpotifyChartDateTrackKey> trackIdsToExcludeFromSubsequentRequest = tracksToExcludeFromSubsequentRequest
            .stream()
            .map(track -> {
                LocalDate chartDateForLookup;

                //if param.previousDate was passed - this single date would be set here to later exclude fact from results list.
                //but if it wasn't passed - we must set 'daily' default date for DAILY charts ids and 'weekly' default date for WEEKLY charts ids
                if (requestedPreviousDate != null) {
                    chartDateForLookup = requestedPreviousDate;
                } else {
                    if (track.getChart().getBreakdown() == SpotifyChartBreakdown.DAILY) {
                        chartDateForLookup = defaultPreviousDateDaily;
                    } else {
                        chartDateForLookup = defaultPreviousDateWeekly;
                    }
                }

                SpotifyChartDateTrackKey origTrackKey = track.getChartDateTrackKey();

                SpotifyChartDateTrackKey adjustedLookupTrackKey = new SpotifyChartDateTrackKey();
                adjustedLookupTrackKey.setSpotifyTrackId(origTrackKey.getSpotifyTrackId());
                adjustedLookupTrackKey.setSpotifyChartId(origTrackKey.getSpotifyChartId());
                adjustedLookupTrackKey.setChartDate(chartDateForLookup);

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

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

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