package io.delphiplatform.api.v3.bigtable;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.RoundingMode;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.DateUtils;
import io.delphiplatform.api.util.JpaUtils;
import io.delphiplatform.api.util.model.ModelUtils;
import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.constant.DspConstants;
import io.delphiplatform.api.v3.model.IncludeTracklistPlaylist;
import io.delphiplatform.api.v3.model.PublicPlaylist;
import io.delphiplatform.api.v3.model.PublicPlaylistableModel;
import io.delphiplatform.api.v3.model.TrackAwareModel;
import io.delphiplatform.api.v3.model.TrackSimple;
import io.delphiplatform.api.v3.model.amazon.AmazonTrackShortInfo;
import io.delphiplatform.api.v3.model.apple.AppleTrack;
import io.delphiplatform.api.v3.model.spotify.SpotifyTrack;
import io.delphiplatform.api.v3.model.trackposition.PlaylistsTrackPositionsCurrentTracklist;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPosition;
import io.delphiplatform.api.v3.rdb.entity.BasePlaylistTrackLifetimePublicEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.ApplePlaylistTrackEntityId;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicPlaylistTrackLifetimePublicEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicPlaylistTrackLifetimePublicId;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyPlaylistTrackLifetimePublicEntity;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyPlaylistTrackLifetimePublicId;
import io.delphiplatform.api.v3.rdb.repository.amazon.AmazonTrackRepository;
import io.delphiplatform.api.v3.rdb.repository.apple.music.AppleMusicPlaylistTrackRepository;
import io.delphiplatform.api.v3.rdb.repository.spotify.SpotifyPlaylistTrackRepository;
import io.delphiplatform.api.v3.rdb.service.TrackService;
import io.delphiplatform.api.v3.rdb.service.amazon.AmazonPlaylistTrackService;
import io.delphiplatform.api.v3.rdb.service.apple.music.AppleMusicPlaylistTrackLifetimePublicService;
import io.delphiplatform.api.v3.rdb.service.apple.music.ApplePlaylistTrackService;
import io.delphiplatform.api.util.model.PlaylistIdIsrcCountryCodeKey;
import io.delphiplatform.api.util.model.PlaylistIdIsrcKey;
import io.delphiplatform.api.util.model.TrackIdCountryCodeKey;
import io.delphiplatform.api.v3.rdb.service.playlist_public.PlaylistPublicService;
import io.delphiplatform.api.v3.rdb.service.spotify.SpotifyPlaylistTrackLifetimePublicService;
import io.delphiplatform.api.v3.rdb.service.spotify.SpotifyPlaylistTrackService;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.util.CollectionUtils.firstNotNull;
import static io.delphiplatform.api.v3.constant.DspConstants.APPLE;
import static io.delphiplatform.api.v3.constant.DspConstants.SPOTIFY;

@Service
public class BigtableTrackPositionDataEnrichmentPublicService {

    private static final int LIFETIME_METRICS_BATCH_SIZE = 2000;

    private final PlaylistPublicService playlistPublicService;
    private final BigtableTrackPositionStreamsDataEnrichmentPublicService streamsDataEnrichmentPublicService;
    private final TrackService trackService;
    private final AmazonPlaylistTrackService amazonPlaylistTrackService;
    private final SpotifyPlaylistTrackLifetimePublicService spotifyPlaylistTrackLifetimePublicService;
    private final AppleMusicPlaylistTrackLifetimePublicService appleMusicPlaylistTrackLifetimePublicService;
    private final SpotifyPlaylistTrackService spotifyPlaylistTrackService;
    private final ApplePlaylistTrackService applePlaylistTrackService;

    public BigtableTrackPositionDataEnrichmentPublicService(
        PlaylistPublicService playlistPublicService,
        BigtableTrackPositionStreamsDataEnrichmentPublicService streamsDataEnrichmentPublicService,
        TrackService trackService,
        AmazonPlaylistTrackService amazonPlaylistTrackService,
        SpotifyPlaylistTrackLifetimePublicService spotifyPlaylistTrackLifetimePublicService,
        AppleMusicPlaylistTrackLifetimePublicService appleMusicPlaylistTrackLifetimePublicService,
        SpotifyPlaylistTrackService spotifyPlaylistTrackService,
        ApplePlaylistTrackService applePlaylistTrackService
    ) {
        this.playlistPublicService = playlistPublicService;
        this.streamsDataEnrichmentPublicService = streamsDataEnrichmentPublicService;
        this.trackService = trackService;
        this.amazonPlaylistTrackService = amazonPlaylistTrackService;
        this.spotifyPlaylistTrackLifetimePublicService = spotifyPlaylistTrackLifetimePublicService;
        this.appleMusicPlaylistTrackLifetimePublicService = appleMusicPlaylistTrackLifetimePublicService;
        this.spotifyPlaylistTrackService = spotifyPlaylistTrackService;
        this.applePlaylistTrackService = applePlaylistTrackService;
    }

    public Stream<PublicPlaylistTrackPosition> populatePlaylistStreamsForXDaysPeriods(
        Stream<PublicPlaylistTrackPosition> models, Params params
    ) {
        return streamsDataEnrichmentPublicService.populatePlaylistStreamsForXDaysPeriods(models, params);
    }

    public <T extends PublicPlaylistableModel> Stream<T> populateAmazonPlaylistsWithDspPlaylistId(
        Stream<T> models, Params params
    ) {
        if (CollectionUtils.contains(params.getInclude(), IncludeTracklistPlaylist.PLAYLISTS.getValue())) {
            List<T> playlistableModels = models.collect(Collectors.toList());

            if (playlistableModels.isEmpty()) {
                return Stream.empty();
            }

            List<PublicPlaylist> amazonPlaylists = playlistableModels.stream()
                .map(PublicPlaylistableModel::getPlaylist)
                .filter(Objects::nonNull)
                .filter(p -> DspConstants.AMAZON.equals(p.getDsp().getDspId()))
                .collect(Collectors.toList());

            playlistPublicService.populatePlaylistsWithAmazonPlaylistId(amazonPlaylists);

            return playlistableModels.stream();
        }

        return models;
    }

    public <T extends TrackAwareModel> Stream<T> populatePlaylistPositionTracks(Stream<T> models, Params params) {
        if (CollectionUtils.contains(params.getInclude(), IncludeTracklistPlaylist.GRAS_TRACK_INFO.getValue())) {
            List<T> trackAwareModels = models.collect(Collectors.toList());

            if (trackAwareModels.isEmpty()) {
                return Stream.empty();
            }

            Map<String, TrackSimple> trackModelsByIsrc = trackService.findMainProductSimpleTracksByIsrcs(
                trackAwareModels.stream()
                    .map(T::getIsrc)
                    .collect(Collectors.toSet())
            );

            trackAwareModels.forEach(position -> position.setTrack(trackModelsByIsrc.get(position.getIsrc())));

            return trackAwareModels.stream();
        } else {
            return models;
        }
    }

    @Transactional(readOnly = true)
    public Stream<PublicPlaylistTrackPosition> populatePlaylistPositionDspTracks(
        Stream<PublicPlaylistTrackPosition> trackPositionsStream,
        Params params
    ) {
        if (CollectionUtils.contains(params.getInclude(), IncludeTracklistPlaylist.TRACK_INFO.getValue())) {
            List<PublicPlaylistTrackPosition> positions = trackPositionsStream.collect(Collectors.toList());

            if (positions.isEmpty()) {
                return Stream.empty();
            }

            Set<PublicPlaylistTrackPosition> amazonPositions = positions.stream()
                .filter(position -> DspConstants.AMAZON.equals(position.getDsp()))
                .collect(Collectors.toSet());

            if (!amazonPositions.isEmpty()) {
                Map<String, AmazonTrackShortInfo> amazonTracksById = findAmazonTracksByPositions(amazonPositions);

                amazonPositions.forEach(position -> position.setAmazonTrack(
                    amazonTracksById.get(position.getAmazonTrackIdUnPrefixed()))
                );
            }

            Set<PublicPlaylistTrackPosition> applePositions = positions.stream()
                .filter(position -> DspConstants.APPLE.equals(position.getDsp()))
                .collect(Collectors.toSet());

            if (!applePositions.isEmpty()) {
                Map<TrackIdCountryCodeKey, AppleTrack> appleTracksById = findAppleTracksByPositions(applePositions);

                applePositions.forEach(position -> position.setAppleTrack(
                    appleTracksById.getOrDefault(
                        ModelUtils.composeTrackIdCountryCodeKeyFromValues(position.getDspTrackId(), position.getCountryCode()),
                        null
                    )
                ));
            }

            Set<PublicPlaylistTrackPosition> spotifyPositions = positions.stream()
                .filter(position -> DspConstants.SPOTIFY.equals(position.getDsp()))
                .collect(Collectors.toSet());

            if (!spotifyPositions.isEmpty()) {
                Map<String, SpotifyTrack> spotifyTracksById = findSpotifyTracksByPositions(spotifyPositions);

                spotifyPositions.forEach(position -> position.setSpotifyTrack(
                    spotifyTracksById.getOrDefault(position.getDspTrackId(), null)
                ));
            }

            return positions.stream();
        } else {
            return trackPositionsStream;
        }
    }

    private Map<String, AmazonTrackShortInfo> findAmazonTracksByPositions(
        Collection<PublicPlaylistTrackPosition> positions
    ) {
        return amazonPlaylistTrackService.findById(
                positions.stream()
                    .filter(position -> DspConstants.AMAZON.equals(position.getDsp()))
                    .map(PublicPlaylistTrackPosition::getAmazonTrackIdUnPrefixed)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet())
            )
            .stream()
            .collect(Collectors.toMap(AmazonTrackShortInfo::getAmazonTrackId, Function.identity()));
    }

    private Map<TrackIdCountryCodeKey, AppleTrack> findAppleTracksByPositions(
        Collection<PublicPlaylistTrackPosition> positions
    ) {
        return applePlaylistTrackService.findAllByIdWithFetches(
                positions.stream()
                    .filter(position -> DspConstants.APPLE.equals(position.getDsp()))
                    .filter(p -> p.getDspTrackId() != null)
                    .map(p -> new ApplePlaylistTrackEntityId(p.getDspTrackId(), p.getCountryCode()))
                    .collect(Collectors.toSet())
            )
            .stream()
            .collect(Collectors.toMap(
                e -> ModelUtils.composeTrackIdCountryCodeKeyFromValues(e.getTrackId(), e.getCountryCode()),
                Function.identity()
            ));
    }

    private Map<String, SpotifyTrack> findSpotifyTracksByPositions(
        Collection<PublicPlaylistTrackPosition> positions
    ) {
        return spotifyPlaylistTrackService.findByIdWithFetches(
                positions.stream()
                    .filter(position -> DspConstants.SPOTIFY.equals(position.getDsp()))
                    .map(PublicPlaylistTrackPosition::getDspTrackId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet())
            )
            .stream()
            .collect(Collectors.toMap(SpotifyTrack::getTrackId, Function.identity()));
    }

    public <E extends BasePlaylistTrackLifetimePublicEntity, ID> Stream<PublicPlaylistTrackPosition> populateFieldsFromLifetimeMetrics(
        Stream<PublicPlaylistTrackPosition> trackPositionsStream,
        String dsp,
        Function<Set<ID>, Map<ID, E>> getPlaylistTrackLifetimesByIds,
        Function<PublicPlaylistTrackPosition, ID> getPlaylistTrackLifetimeIdFromPosition
    ) {
        List<PublicPlaylistTrackPosition> models = trackPositionsStream.collect(Collectors.toList());

        List<PublicPlaylistTrackPosition> positions = models.stream()
            .filter(position -> dsp.equals(position.getDsp()))
            .collect(Collectors.toList());

        if (!positions.isEmpty()) {
            Set<ID> playlistTrackLifetimeIds = positions.stream()
                .map(getPlaylistTrackLifetimeIdFromPosition)
                .collect(Collectors.toSet());

            Map<ID, E> lifetimePlaylistTracksById =
                getPlaylistTrackLifetimesByIds.apply(playlistTrackLifetimeIds);

            positions.forEach(position -> {
                E playlistTrackLifetime = lifetimePlaylistTracksById.get(
                    getPlaylistTrackLifetimeIdFromPosition.apply(position)
                );

                if (playlistTrackLifetime != null) {
                    position.setEarliestPositionDateTime(playlistTrackLifetime.getEarliestDateTime());
                    position.setLatestPositionDateTime(playlistTrackLifetime.getLatestDateTime());

                    position.setDaysInPlaylist(ModelUtils.getTrackDaysOnPlaylist(
                        playlistTrackLifetime.getEarliestDateTime(),
                        playlistTrackLifetime.getLatestDateTime()
                    ));

                    position.setLastAddedDateTime(playlistTrackLifetime.getAddedDateTime());

                    if (position.getEarliestPosition() == null) {
                        position.setEarliestPosition(ModelUtils.toLong(playlistTrackLifetime.getEarliestPosition()));
                    }
                }
            });
        }

        return models.stream();
    }

    public Stream<PublicPlaylistTrackPosition> populateNumWeeksOnPlaylist(
        Stream<PublicPlaylistTrackPosition> trackPositionsStream) {
        return trackPositionsStream.peek(position -> {
            Long daysInPlaylist = position.getDaysInPlaylist();

            if (daysInPlaylist != null) {
                position.setNumWeeksOn(
                    ModelUtils.toBigDecimal(daysInPlaylist)
                        .divide(ModelUtils.toBigDecimal(ApplicationConstants.DAYS_IN_WEEK), RoundingMode.FLOOR)
                        .intValue()
                );
            }
        });
    }

    public Map<SpotifyPlaylistTrackLifetimePublicId, SpotifyPlaylistTrackLifetimePublicEntity> getPlaylistTrackLifetimesSpotify(
        Set<SpotifyPlaylistTrackLifetimePublicId> playlistTrackLifetimeIds
    ) {
        return JpaUtils.splitExecutionByCollectionSize(playlistTrackLifetimeIds,
            spotifyPlaylistTrackLifetimePublicService::findAllById,
            LIFETIME_METRICS_BATCH_SIZE,
            Collectors.toMap(SpotifyPlaylistTrackLifetimePublicEntity::getId, Function.identity()));
    }

    public Map<AppleMusicPlaylistTrackLifetimePublicId, AppleMusicPlaylistTrackLifetimePublicEntity> getPlaylistTrackLifetimesAppleMusic(
        Set<AppleMusicPlaylistTrackLifetimePublicId> playlistTrackLifetimeIds
    ) {
        return JpaUtils.splitExecutionByCollectionSize(playlistTrackLifetimeIds,
            appleMusicPlaylistTrackLifetimePublicService::findAllById,
            LIFETIME_METRICS_BATCH_SIZE,
            Collectors.toMap(AppleMusicPlaylistTrackLifetimePublicEntity::getId, Function.identity()));
    }

    @Transactional(readOnly = true)
    public Stream<PlaylistsTrackPositionsCurrentTracklist> populatePlaylistTrackInfoForCurrentTrackPositions(
        Stream<PlaylistsTrackPositionsCurrentTracklist> trackPositions,
        List<String> spotifyRegularPlaylists,
        Params params) {

        List<PlaylistsTrackPositionsCurrentTracklist> playlistTrackPositions = trackPositions.collect(Collectors.toList());

        boolean includeTrackInfo = CollectionUtils.contains(params.getInclude(), IncludeTracklistPlaylist.TRACK_INFO.getValue());
        if (includeTrackInfo) {
            Set<PlaylistsTrackPositionsCurrentTracklist> applePositionsCurrentTracklists = playlistTrackPositions.stream()
                .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), DspConstants.APPLE))
                .collect(Collectors.toSet());

            if (!applePositionsCurrentTracklists.isEmpty()) {
                Map<TrackIdCountryCodeKey, AppleTrack> applePlaylistTracksMap =
                    JpaUtils.splitRepositoryMethodCall(
                        applePositionsCurrentTracklists.stream()
                            .map(p -> new ApplePlaylistTrackEntityId(p.getTrackId(), p.getCountryCode()))
                            .collect(Collectors.toSet()),
                        applePlaylistTrackService::findAllByIdWithFetches,
                        Collectors.toMap(
                            e -> ModelUtils.composeTrackIdCountryCodeKeyFromValues(e.getTrackId(), e.getCountryCode()),
                            Function.identity()
                        )
                    );

                applePositionsCurrentTracklists.forEach(p -> {
                    TrackIdCountryCodeKey key = ModelUtils.composeTrackIdCountryCodeKeyFromValues(p.getTrackId(), p.getCountryCode());
                    if (applePlaylistTracksMap.containsKey(key)) {
                        p.setAppleTrack(applePlaylistTracksMap.get(key));
                    }
                });
            }

            Set<PlaylistsTrackPositionsCurrentTracklist> spotifyPositionsCurrentTracklists = playlistTrackPositions.stream()
                .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), DspConstants.SPOTIFY))
                .filter(p -> !spotifyRegularPlaylists.contains(p.getPlaylistId()))
                .collect(Collectors.toSet());

            if (!spotifyPositionsCurrentTracklists.isEmpty()) {
                Map<String, SpotifyTrack> spotifyPlaylistTracksMap =
                    JpaUtils.splitRepositoryMethodCall(
                        spotifyPositionsCurrentTracklists.stream()
                            .map(PlaylistsTrackPositionsCurrentTracklist::getTrackId)
                            .collect(Collectors.toSet()),
                        spotifyPlaylistTrackService::findByIdWithFetches,
                        Collectors.toMap(SpotifyTrack::getTrackId, Function.identity())
                    );

                spotifyPositionsCurrentTracklists.forEach(p -> {
                    String key = p.getTrackId();
                    if (spotifyPlaylistTracksMap.containsKey(key)) {
                        p.setSpotifyTrack(spotifyPlaylistTracksMap.get(key));
                    }
                });
            }
        }

        // for positions sourced from Spotify API
        playlistTrackPositions.stream()
            .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), DspConstants.SPOTIFY))
            .filter(p -> spotifyRegularPlaylists.contains(p.getPlaylistId()))
            .collect(Collectors.groupingBy(PlaylistsTrackPositionsCurrentTracklist::getPlaylistId))
            .forEach((key, currentTracklistPositions) -> currentTracklistPositions.forEach(tp -> {
                    if (!includeTrackInfo) {
                        tp.setSpotifyTrack(null);
                    }
                }
            ));

        return playlistTrackPositions.stream();
    }

    public Stream<PlaylistsTrackPositionsCurrentTracklist> populateCurrentTrackPositionsMetrics(
        Stream<PlaylistsTrackPositionsCurrentTracklist> models
    ) {
        List<PlaylistsTrackPositionsCurrentTracklist> trackPositions = models.collect(Collectors.toList());
        Map<PlaylistIdIsrcKey, List<PlaylistsTrackPositionsCurrentTracklist>> spotifyTrackPositionsByPlaylistAndIsrc =
            trackPositions.stream()
                .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), SPOTIFY))
                .filter(position -> position.getIsrc() != null)
                .collect(Collectors.groupingBy(tp -> ModelUtils.composePlaylistIsrcKeyFromValues(tp.getPlaylistId(), tp.getIsrc())));

        Set<SpotifyPlaylistTrackLifetimePublicId> spotifyIds = trackPositions.stream()
            .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), SPOTIFY))
            .map(ModelUtils::composeSpotifyPlaylistTrackLifetimePublicIdFromCurrentTracklist)
            .collect(Collectors.toSet());

        Map<SpotifyPlaylistTrackLifetimePublicId, SpotifyPlaylistTrackLifetimePublicEntity> spotifyMetrics =
            JpaUtils.splitExecutionByCollectionSize(spotifyIds,
                spotifyPlaylistTrackLifetimePublicService::findAllById,
                LIFETIME_METRICS_BATCH_SIZE,
                Collectors.toMap(SpotifyPlaylistTrackLifetimePublicEntity::getId, Function.identity()));

        Map<PlaylistIdIsrcCountryCodeKey, List<PlaylistsTrackPositionsCurrentTracklist>> appleTrackPositionsByPlaylistAndIsrcAndCountry =
            trackPositions.stream()
                .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), APPLE))
                .filter(position -> position.getIsrc() != null && position.getCountryCode() != null)
                .collect(Collectors.groupingBy(tp -> ModelUtils.composePlaylistIsrcCountryCodeKeyFromValues(
                    tp.getPlaylistId(), tp.getIsrc(), tp.getCountryCode())));

        Set<AppleMusicPlaylistTrackLifetimePublicId> appleIds = trackPositions.stream()
            .filter(p -> ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), APPLE))
            .map(ModelUtils::composeApplePlaylistTrackLifetimePublicIdFromCurrentTracklist)
            .collect(Collectors.toSet());

        Map<AppleMusicPlaylistTrackLifetimePublicId, AppleMusicPlaylistTrackLifetimePublicEntity> appleMetrics =
            JpaUtils.splitExecutionByCollectionSize(appleIds,
                appleMusicPlaylistTrackLifetimePublicService::findAllById,
                LIFETIME_METRICS_BATCH_SIZE,
                Collectors.toMap(AppleMusicPlaylistTrackLifetimePublicEntity::getId, Function.identity()));

        return trackPositions.stream()
            .peek(p -> {
                if (ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), DspConstants.SPOTIFY)) {
                    PlaylistIdIsrcKey playlistIdIsrcKey = ModelUtils.composePlaylistIsrcKeyFromValues(p.getPlaylistId(), p.getIsrc());
                    Optional.ofNullable(spotifyTrackPositionsByPlaylistAndIsrc.get(playlistIdIsrcKey))
                        .ifPresent(positions -> {
                            if (positions.size() > 0) {
                                positions.sort(Comparator.comparing(PlaylistsTrackPositionsCurrentTracklist::getCurrent));
                                p.setIsTopTrackForIsrc(p.getCurrent().equals(positions.get(0).getCurrent()));
                            }
                        });

                    SpotifyPlaylistTrackLifetimePublicId key = ModelUtils.composeSpotifyPlaylistTrackLifetimePublicIdFromCurrentTracklist(p);
                    if (spotifyMetrics.containsKey(key)) {
                        SpotifyPlaylistTrackLifetimePublicEntity spotifyTrackLifetime = spotifyMetrics.get(key);
                        p.setEarliestPositionDateTime(firstNotNull(spotifyTrackLifetime.getEarliestDateTime(),
                            p.getEarliestPositionDateTime(), DateUtils.getCurrentDateTime()));
                        p.setLastAddedDateTime(firstNotNull(spotifyTrackLifetime.getAddedDateTime(),
                            p.getEarliestPositionDateTime()));

                        if (p.getEarliestPosition() == null) {
                            p.setEarliestPosition(ModelUtils.toLong(spotifyTrackLifetime.getEarliestPosition()));
                        }
                    } else {
                        p.setEarliestPositionDateTime(firstNotNull(p.getEarliestPositionDateTime(), DateUtils.getCurrentDateTime()));
                        p.setLastAddedDateTime(p.getEarliestPositionDateTime());
                    }
                    p.setNumDaysOn(firstNotNull(p.getNumDaysOn(),
                        ChronoUnit.DAYS.between(p.getEarliestPositionDateTime().toLocalDate(), DateUtils.getCurrentDate())));
                }

                if (ModelUtils.isPlaylistOfDsp(p.getPlaylistId(), DspConstants.APPLE)) {
                    PlaylistIdIsrcCountryCodeKey playlistIdIsrcCountryCodeKey = ModelUtils
                        .composePlaylistIsrcCountryCodeKeyFromValues(p.getPlaylistId(), p.getIsrc(), p.getCountryCode());
                    Optional.ofNullable(appleTrackPositionsByPlaylistAndIsrcAndCountry.get(playlistIdIsrcCountryCodeKey))
                        .ifPresent(positions -> {
                            if (positions.size() > 0) {
                                positions.sort(Comparator.comparing(PlaylistsTrackPositionsCurrentTracklist::getCurrent));
                                p.setIsTopTrackForIsrc(p.getCurrent().equals(positions.get(0).getCurrent()));
                            }
                        });

                    AppleMusicPlaylistTrackLifetimePublicId key = ModelUtils.composeApplePlaylistTrackLifetimePublicIdFromCurrentTracklist(p);
                    if (appleMetrics.containsKey(key)) {
                        AppleMusicPlaylistTrackLifetimePublicEntity appleTrackLifetime = appleMetrics.get(key);
                        p.setEarliestPositionDateTime(firstNotNull(appleTrackLifetime.getEarliestDateTime(),
                            p.getEarliestPositionDateTime(), DateUtils.getCurrentDateTime()));
                        p.setLastAddedDateTime(firstNotNull(appleTrackLifetime.getAddedDateTime(),
                            p.getEarliestPositionDateTime()));

                        if (p.getEarliestPosition() == null) {
                            p.setEarliestPosition(ModelUtils.toLong(appleTrackLifetime.getEarliestPosition()));
                        }
                    } else {
                        p.setEarliestPositionDateTime(firstNotNull(p.getEarliestPositionDateTime(), DateUtils.getCurrentDateTime()));
                        p.setLastAddedDateTime(p.getEarliestPositionDateTime());
                    }
                    p.setNumDaysOn(firstNotNull(p.getNumDaysOn(),
                        ChronoUnit.DAYS.between(p.getEarliestPositionDateTime().toLocalDate(), DateUtils.getCurrentDate())));
                }
            });
    }
}
