package io.delphiplatform.api.extapi;

import org.springframework.stereotype.Service;

import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import io.delphiplatform.api.util.DateUtils;
import io.delphiplatform.api.v3.constant.DspConstants;
import io.delphiplatform.api.v3.model.spotify.SpotifyAlbum;
import io.delphiplatform.api.v3.model.spotify.SpotifyArtist;
import io.delphiplatform.api.v3.model.spotify.SpotifyTrack;
import io.delphiplatform.api.v3.model.trackposition.PublicPlaylistTrackPosition;
import io.delphiplatform.api.v3.rdb.entity.spotify.SpotifyAlbumUpcProjection;
import lombok.extern.slf4j.Slf4j;
import se.michaelthelin.spotify.enums.ReleaseDatePrecision;
import se.michaelthelin.spotify.model_objects.specification.Album;
import se.michaelthelin.spotify.model_objects.specification.ExternalId;
import se.michaelthelin.spotify.model_objects.specification.Image;
import se.michaelthelin.spotify.model_objects.specification.PlaylistTrack;
import se.michaelthelin.spotify.model_objects.specification.Track;

@Slf4j
@Service
public class SpotifyApiPlaylistTrackConvertor {

    public static final String DEFAULT_MONTH_POSTFIX = "-01";
    public static final String DEFAULT_DAY_POSTFIX = "-01";

    private static LocalDate parseDate(String date) {
        LocalDate result = null;
        try {
            result = DateUtils.parseSafe(date);
        } catch (DateTimeException e) {
            log.debug("Could not recognize date format: " + date, e);
        }
        return result;
    }

    public List<PublicPlaylistTrackPosition> toPublicPlaylistTrackPosition(String spotifyPlaylistId, List<PlaylistTrack> playlistTracks) {
        return IntStream.range(0, playlistTracks.size())
            .mapToObj(i -> {
                PlaylistTrack playlistTrack = playlistTracks.get(i);
                Track track = (Track) playlistTrack.getTrack();

                List<SpotifyArtist> artists = Arrays.stream(track.getArtists())
                    .map(a -> SpotifyArtist.builder()
                        .artistId(a.getId())
                        .name(a.getName())
                        .url(a.getUri())
                        .build())
                    .collect(Collectors.toList());

                String albumImage = Arrays.stream(track.getAlbum().getImages())
                    .map(Image::getUrl)
                    .findFirst().orElse(null);
                LocalDate albumReleaseDate = parseDate(
                    formatDateStringWithPrecission(track.getAlbum().getReleaseDate(), track.getAlbum().getReleaseDatePrecision())
                );

                SpotifyAlbum album = new SpotifyAlbum();
                album.setAlbumId(track.getAlbum().getId());
                album.setName(track.getAlbum().getName());
                album.setImage(albumImage);
                album.setUpc(null);
                album.setReleaseDate(albumReleaseDate);

                SpotifyTrack spotifyTrack = new SpotifyTrack();
                spotifyTrack.setTrackId(track.getId());
                spotifyTrack.setName(track.getName());
                spotifyTrack.setImageUrl(albumImage);
                spotifyTrack.setReleaseDate(albumReleaseDate);
                spotifyTrack.setIsrc(track.getExternalIds().getExternalIds().get("isrc"));
                spotifyTrack.setTrackPopularity(Long.valueOf(Optional.ofNullable(track.getPopularity()).orElse(0)));
                spotifyTrack.setArtists(artists);
                spotifyTrack.setAlbum(album);

                PublicPlaylistTrackPosition playlistTrackPosition = new PublicPlaylistTrackPosition();
                playlistTrackPosition.trackId(spotifyTrack.getTrackId());
                playlistTrackPosition.setSpotifyTrack(spotifyTrack);
                playlistTrackPosition.isrc(
                    Optional.ofNullable(spotifyTrack.getIsrc())
                        .map(String::toUpperCase)
                        .orElse(null));
                playlistTrackPosition.setCurrent((long) i);
                playlistTrackPosition.setPlaylistId(spotifyPlaylistId);
                playlistTrackPosition.dsp(DspConstants.SPOTIFY);
                if (playlistTrack.getAddedAt() != null) {
                    playlistTrackPosition.setEarliestPositionDateTime(playlistTrack.getAddedAt()
                        .toInstant()
                        .atOffset(ZoneOffset.UTC)
                        .toLocalDateTime());
                }

                return playlistTrackPosition;
            })
            .collect(Collectors.toList());
    }

    public Stream<PublicPlaylistTrackPosition> populateAlbumUpc(Stream<PublicPlaylistTrackPosition> models,
        Function<Collection<String>, List<Album>> albumFromSpotifySupplier,
        Function<Collection<String>, List<SpotifyAlbumUpcProjection>> albumFromDbSupplier) {
        List<PublicPlaylistTrackPosition> trackPositions = models.collect(Collectors.toList());
        Set<String> albumIds = trackPositions.stream()
            .filter(p -> Optional.ofNullable(p.getSpotifyTrack())
                .map(SpotifyTrack::getAlbum)
                .map(SpotifyAlbum::getAlbumId)
                .isPresent())
            .map(p -> p.getSpotifyTrack().getAlbum().getAlbumId())
            .collect(Collectors.toSet());

        Map<String, String> albumIdToUpcMapFromDb = albumFromDbSupplier.apply(albumIds).stream()
            .collect(Collectors.toMap(SpotifyAlbumUpcProjection::getAlbumId, SpotifyAlbumUpcProjection::getUpc));
        albumIds.removeAll(albumIdToUpcMapFromDb.keySet());
        Map<String, String> albumIdToUpcMapFromSpotify = albumFromSpotifySupplier.apply(albumIds).stream()
            .filter(album -> Optional.ofNullable(album.getExternalIds())
                .map(ExternalId::getExternalIds)
                .map(externalIdMap -> externalIdMap.get("upc"))
                .isPresent())
            .collect(Collectors.toMap(Album::getId, album -> album.getExternalIds().getExternalIds().get("upc")));

        Map<String, String> albumIdToUpcMap = new HashMap<>();
        albumIdToUpcMap.putAll(albumIdToUpcMapFromDb);
        albumIdToUpcMap.putAll(albumIdToUpcMapFromSpotify);
        return trackPositions.stream()
            .peek(p -> Optional.ofNullable(p.getSpotifyTrack())
                .map(SpotifyTrack::getAlbum)
                .ifPresent(album -> album.setUpc(albumIdToUpcMap.getOrDefault(album.getAlbumId(), null)))
            );
    }

    public static String formatDateStringWithPrecission(String date, ReleaseDatePrecision datePrecision) {
        if (date == null || datePrecision == null) {
            return date;
        }

        switch (datePrecision) {
            case YEAR:
                return date + DEFAULT_MONTH_POSTFIX + DEFAULT_DAY_POSTFIX;
            case MONTH:
                return date + DEFAULT_DAY_POSTFIX;
            default:
                return date;
        }
    }
}
