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.Map;
import java.util.Objects;
import java.util.Set;

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.SpotifyTrackId;
import io.delphiplatform.api.v3.view.util.Params;

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

    private static final LocalDate DEFAULT_PREV_DATE_IF_NO_DATA = LocalDate.EPOCH;

    private final SpotifyChartTrackStateService spotifyChartTrackStateService;
    private final SpotifyChartTrackService spotifyChartTrackService;
    private final SpotifyTrackService spotifyTrackService;

    @Autowired
    public SpotifyChartTrackChangesService(
        SpotifyChartTrackStateService spotifyChartTrackStateService,
        SpotifyChartTrackService spotifyChartTrackService,
        SpotifyTrackService spotifyTrackService) {
        this.spotifyChartTrackStateService = spotifyChartTrackStateService;

        this.spotifyChartTrackService = spotifyChartTrackService;
        this.spotifyTrackService = spotifyTrackService;
    }

    /**
     * Additions are items that exist now, but do not exist in previous date.
     */
    public List<ChartTrackState> getSpotifyChartTracksAdditions(Params params) {
        String chartId = params.getSingleChartId();
        Integer minPosition = params.getMinPosition();
        Integer maxPosition = params.getMaxPosition();
        LocalDate currentDate = params.getDate();
        LocalDate previousDate = getPreviousDateOrDefault(params.getPreviousDate(), params.getSingleChartId(),
            params.getDate());

        return Objects.isNull(previousDate) ? Collections.emptyList() :
            getSpotifyChartTracksChanges(chartId, minPosition, maxPosition, previousDate, currentDate,
                params.getChartTrackStateIncludes(), params.getChartTrackMetricsDimension());
    }

    /**
     * Removals are items that existed in previous date, but do not exist now.
     */
    public List<ChartTrackState> getSpotifyChartTracksRemovals(Params params) {
        String chartId = params.getSingleChartId();
        Integer minPosition = params.getMinPosition();
        Integer maxPosition = params.getMaxPosition();
        LocalDate currentDate = params.getDate();
        LocalDate previousDate = getPreviousDateOrDefault(params.getPreviousDate(), params.getSingleChartId(),
            params.getDate());

        return Objects.isNull(previousDate) ? Collections.emptyList() :
            getSpotifyChartTracksChanges(chartId, minPosition, maxPosition, currentDate, previousDate,
                params.getChartTrackStateIncludes(), params.getChartTrackMetricsDimension());
    }

    private LocalDate getPreviousDateOrDefault(LocalDate previousDate, String singleChartId,
        LocalDate date) {
        if (previousDate != null) {
            return previousDate;
        }

        LocalDate earliestDateBeforeFromData = spotifyChartTrackService.getEarliestDateBefore(date, singleChartId);
        if (earliestDateBeforeFromData != null) {
            return earliestDateBeforeFromData;
        }
        return DEFAULT_PREV_DATE_IF_NO_DATA;
    }

    private List<ChartTrackState> getSpotifyChartTracksChanges(String chartId,
        Integer minPosition, Integer maxPosition, LocalDate dateToExclude, LocalDate baseDate,
        Set<ChartTrackStateIncludes> includes,
        ChartTrackMetricsDimension metricsDimension) {

        Map<SpotifyTrackId, SpotifyChartTrackEntity> tracksToExclude = spotifyChartTrackService.findTrackMetricsDistinctTrack(
            Collections.emptySet(), Collections.emptySet(), chartId, dateToExclude, minPosition,
            maxPosition, metricsDimension);

        Set<SpotifyTrackId> trackIdsToExclude = getTrackIdsToExclude(tracksToExclude.keySet(), metricsDimension);

        Map<SpotifyTrackId, SpotifyChartTrackEntity> delta = spotifyChartTrackService.findTrackMetricsExcludeTrackIds(
            trackIdsToExclude, chartId,
            baseDate, minPosition, maxPosition);

        return spotifyChartTrackStateService.getSpotifyChartTrackStatesDistinct(
            chartId, includes, metricsDimension, delta
        );
    }

    private Set<SpotifyTrackId> getTrackIdsToExclude(Set<SpotifyTrackId> trackIds,
        ChartTrackMetricsDimension metricsDimension) {
        if (metricsDimension == null || metricsDimension == ChartTrackMetricsDimension.TRACK_ID) {
            return trackIds;
        }

        return spotifyTrackService.findTracksWithSameIsrc(trackIds);
    }
}
