package io.delphiplatform.api.v3.bigtable;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.transaction.NotSupportedException;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.util.DateUtils;
import io.delphiplatform.api.v3.bigtable.processing.PaginationService;
import io.delphiplatform.api.v3.bigtable.processing.PlaylistTrackPreviousPositionSortingPublicService;
import io.delphiplatform.api.v3.constant.DspConstants;
import io.delphiplatform.api.v3.dto.ModelsListWithMetadataDto;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPositionsMeta;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPreviousPosition;
import io.delphiplatform.api.v3.rdb.entity.PlaylistTrackLifetimePublicProjection;
import io.delphiplatform.api.v3.rdb.service.apple.music.AppleMusicPlaylistTrackLifetimePublicService;
import io.delphiplatform.api.v3.rdb.service.dto.PublicPlaylistLookupFilters;
import io.delphiplatform.api.v3.rdb.service.spotify.SpotifyPlaylistTrackLifetimePublicService;
import io.delphiplatform.api.v3.view.util.Params;

@Service
public class BigtableTrackPositionPreviousPublicService {

    private final PaginationService paginationService;

    private final BigtableTrackPositionPublicManager bigtableTrackPositionPublicManager;
    private final BigtableTrackPositionDataEnrichmentPublicService dataEnrichmentPublicService;
    private final SpotifyPlaylistTrackLifetimePublicService spotifyPlaylistTrackLifetimePublicService;
    private final AppleMusicPlaylistTrackLifetimePublicService appleMusicPlaylistTrackLifetimePublicService;
    private final PlaylistTrackPreviousPositionSortingPublicService playlistTrackPreviousPositionSortingPublicService;
    private final BigtableTrackPositionStreamsDataEnrichmentPublicService streamsDataEnrichmentPublicService;

    public BigtableTrackPositionPreviousPublicService(
        PaginationService paginationService,
        BigtableTrackPositionPublicManager bigtableTrackPositionPublicManager,
        BigtableTrackPositionDataEnrichmentPublicService dataEnrichmentPublicService,
        SpotifyPlaylistTrackLifetimePublicService spotifyPlaylistTrackLifetimePublicService,
        AppleMusicPlaylistTrackLifetimePublicService appleMusicPlaylistTrackLifetimePublicService,
        PlaylistTrackPreviousPositionSortingPublicService playlistTrackPreviousPositionSortingPublicService,
        BigtableTrackPositionStreamsDataEnrichmentPublicService streamsDataEnrichmentPublicService) {
        this.paginationService = paginationService;
        this.bigtableTrackPositionPublicManager = bigtableTrackPositionPublicManager;
        this.dataEnrichmentPublicService = dataEnrichmentPublicService;
        this.spotifyPlaylistTrackLifetimePublicService = spotifyPlaylistTrackLifetimePublicService;
        this.appleMusicPlaylistTrackLifetimePublicService = appleMusicPlaylistTrackLifetimePublicService;
        this.playlistTrackPreviousPositionSortingPublicService = playlistTrackPreviousPositionSortingPublicService;
        this.streamsDataEnrichmentPublicService = streamsDataEnrichmentPublicService;
    }

    public CompletableFuture<ModelsListWithMetadataDto<PublicPlaylistTrackPreviousPosition, PublicPlaylistTrackPositionsMeta>>
    getPlaylistTrackPreviousPositionsModels(Params params) throws NotSupportedException {
        boolean spotifyRequested = CollectionUtils.isEmpty(params.getDsp())
            || params.getDsp().contains(DspConstants.SPOTIFY);
        boolean appleRequested = CollectionUtils.isEmpty(params.getDsp())
            || params.getDsp().contains(DspConstants.APPLE);

        if (!spotifyRequested && !appleRequested) {
            return CompletableFuture.completedFuture(
                ModelsListWithMetadataDto.<PublicPlaylistTrackPreviousPosition, PublicPlaylistTrackPositionsMeta>builder()
                    .models(Stream.empty())
                    .build());
        }

        /* Find playlists which contained given isrc at any point, except for those that contain it now */

        List<PlaylistTrackLifetimePublicProjection> spotifyLifetimeFactsExceptCurrent = new ArrayList<>();
        List<PlaylistTrackLifetimePublicProjection> appleLifetimeFactsExceptCurrent = new ArrayList<>();

        List<CompletableFuture> futures = Collections.synchronizedList(new ArrayList<>());

        if (spotifyRequested) {
            futures.add(
                CompletableFuture.supplyAsync(() ->
                    spotifyLifetimeFactsExceptCurrent.addAll(
                        spotifyPlaylistTrackLifetimePublicService.findByRelatedIsrcsExcludeCurrentPositions(
                            params.getIsrc(), params.getPlaylistId(), params.getCountryCode(), params.getMinRemovedDate()
                        )
                    )));
        }

        if (appleRequested) {
            Set<String> appleCountryCodes = new HashSet<>();
            Optional.ofNullable(params.getCountryCode()).ifPresent(codes -> appleCountryCodes.addAll(codes));
            Optional.ofNullable(params.getAppleCountryCode()).ifPresent(codes -> appleCountryCodes.addAll(codes));

            futures.add(
                CompletableFuture.supplyAsync(() ->
                    appleLifetimeFactsExceptCurrent.addAll(
                        appleMusicPlaylistTrackLifetimePublicService.findByRelatedIsrcsExcludeCurrentPositions(
                            params.getIsrc(), params.getPlaylistId(), appleCountryCodes, params.getMinRemovedDate()
                        )
                    )));
        }
        CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();

        return CompletableFuture.completedFuture(Stream.concat(
                spotifyLifetimeFactsExceptCurrent.stream().map(PublicPlaylistTrackPreviousPosition::from),
                appleLifetimeFactsExceptCurrent.stream().map(PublicPlaylistTrackPreviousPosition::from)
            ))
            //filter models list and populate with playlists
            .thenApply(models -> filterPreviousPositionsByPlaylistPropertiesAndPopulatePlaylist(models, params))
            //perform sorting/paging
            .thenApply(models -> playlistTrackPreviousPositionSortingPublicService
                .sortPreviousPlaylistPositions(models, params.getSortList()))
            .thenApply(models -> {
                List<PublicPlaylistTrackPreviousPosition> unpagedModels = models.collect(Collectors.toList());

                return ModelsListWithMetadataDto.<PublicPlaylistTrackPreviousPosition, PublicPlaylistTrackPositionsMeta>builder()
                    .models(paginationService.getPage(unpagedModels.stream(), params))
                    .meta(bigtableTrackPositionPublicManager.createPublicTrackPositionsListMeta(unpagedModels, params.getInclude()))
                    .build();
            })
            //populate rest of the fields
            .thenApply(modelsListDTO -> modelsListDTO.updateModelsList(
                dataEnrichmentPublicService.populatePlaylistPositionTracks(modelsListDTO.getModels(), params)))
            .thenApply(modelsListDTO -> modelsListDTO.updateModelsList(
                bigtableTrackPositionPublicManager.populatePlaylistDates(modelsListDTO.getModels(), params)))
            .thenApply(modelsListDTO -> modelsListDTO.updateModelsList(
                streamsDataEnrichmentPublicService.populatePlaylistStreamsForXDaysPeriods(
                    modelsListDTO.getModels(),
                    DateUtils.getCurrentDate(),
                    params.getStreamsCountryCode(),
                    params.getDsp(),
                    params.getInclude(),
                    false
                )));
    }

    private Stream<PublicPlaylistTrackPreviousPosition> filterPreviousPositionsByPlaylistPropertiesAndPopulatePlaylist(
        Stream<PublicPlaylistTrackPreviousPosition> modelsStream, Params params
    ) {
        List<PublicPlaylistTrackPreviousPosition> models = modelsStream.collect(Collectors.toList());

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

        return bigtableTrackPositionPublicManager.populatePlaylistsFiltered(
                models.stream(),
                params.getInclude(),
                PublicPlaylistLookupFilters.builder()
                    .requireSaveHistory(true)
                    .requireSaveTrackList(true)
                    .includeIgnored(params.getIncludeIgnored())
                    .includeRemoved(params.getIncludeRemoved())
                    .playlistTypes(params.getPlaylistTypes())
                    .playlistNameContains(params.getPlaylistNameContains())
                    .playlistOwnerCategoryId(params.getPlaylistOwnerCategoryId())
                    .build()
            )
            //ignore model which playlist haven't passed filters
            .filter(position -> position.getTransientPlaylist() != null);
    }
}
