package io.delphiplatform.api.v3.rdb.service.apple.music;

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

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import io.delphiplatform.api.exception.DataLocalizationException;
import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicArtistEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicArtistLocalizedEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicArtistLocalizedId;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedId;
import io.delphiplatform.api.v3.rdb.repository.apple.music.AppleMusicArtistLocalizedRepository;
import lombok.extern.slf4j.Slf4j;

@Service
@Transactional(readOnly = true)
@Slf4j
public class AppleMusicArtistService {

    private final AppleMusicArtistLocalizedRepository localizedRepository;

    public AppleMusicArtistService(AppleMusicArtistLocalizedRepository localizedRepository) {
        this.localizedRepository = localizedRepository;
    }

    public Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> findArtistsByChartTrackEntities(
        Collection<AppleMusicChartTrackEntity> chartTrackEntities
    ) {
        return findArtists(
            chartTrackEntities.stream()
                .map(AppleMusicChartTrackEntity::getLocalizedTrack)
                .collect(Collectors.toSet())
        );
    }

    public Map<AppleMusicTrackLocalizedId, List<AppleMusicArtistLocalizedEntity>> findArtists(
        Collection<AppleMusicTrackLocalizedEntity> localizedTracks
    ) {
        Set<AppleMusicArtistLocalizedId> localizedArtistIds = localizedTracks.stream().flatMap(localizedTrack -> {
                String countryId = localizedTrack.getAppleMusicCountryId();
                Set<String> artistIds = localizedTrack.getTrack().getArtists().stream()
                    .map(AppleMusicArtistEntity::getAppleMusicArtistId)
                    .collect(Collectors.toSet());
                return artistIds.stream().map(artistId -> new AppleMusicArtistLocalizedId(artistId, countryId));
            }
        ).collect(Collectors.toSet());

        Map<AppleMusicArtistLocalizedId, AppleMusicArtistLocalizedEntity> localizedArtistsById =
            localizedRepository.findAllById(localizedArtistIds).stream().collect(Collectors.toMap(
                AppleMusicArtistLocalizedEntity::getId, Function.identity()));

        //check if we have any artists without proper locale record and if so - look for fallback locale record (for any country)
        Map<String, List<AppleMusicArtistLocalizedEntity>> fallbackArtistLocalizationsByArtistId = new HashMap<>();

        Set<String> artistIdsWithoutProperLocale = localizedArtistIds
            .stream()
            .filter(id -> !localizedArtistsById.containsKey(id))
            .map(AppleMusicArtistLocalizedId::getAppleMusicArtistId)
            .collect(Collectors.toSet());

        if (CollectionUtils.isNotEmpty(artistIdsWithoutProperLocale)) {
            fallbackArtistLocalizationsByArtistId.putAll(
                localizedRepository.findByArtistIds(artistIdsWithoutProperLocale)
                    .stream().collect(Collectors.groupingBy(a -> a.getId().getAppleMusicArtistId()))
            );
        }

        return localizedTracks.stream()
            .collect(Collectors.toMap(AppleMusicTrackLocalizedEntity::getId,
                track -> getArtistsForTrack(localizedArtistsById, fallbackArtistLocalizationsByArtistId, track)));
    }

    private List<AppleMusicArtistLocalizedEntity> getArtistsForTrack(
        Map<AppleMusicArtistLocalizedId, AppleMusicArtistLocalizedEntity> localizedArtistsById,
        Map<String, List<AppleMusicArtistLocalizedEntity>> fallbackArtistLocalizationsByArtistId,
        AppleMusicTrackLocalizedEntity localizedTrack
    ) {
        return localizedTrack.getTrack().getArtists().stream()
            .map(a -> {
                String artistId = a.getAppleMusicArtistId();

                AppleMusicArtistLocalizedEntity localizedArtist = localizedArtistsById.get(
                    new AppleMusicArtistLocalizedId(artistId, localizedTrack.getAppleMusicCountryId())
                );

                //if localized record is missing for particular artist / country - try to find it for any other country as a fallback
                if (localizedArtist == null) {
                    localizedArtist = findFallbackArtistLocalization(artistId, fallbackArtistLocalizationsByArtistId,
                        localizedTrack.getId());
                }

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

    private AppleMusicArtistLocalizedEntity findFallbackArtistLocalization(
        String artistId,
        Map<String, List<AppleMusicArtistLocalizedEntity>> fallbackArtistLocalizationsByArtistId,
        AppleMusicTrackLocalizedId trackLocalizedId
    ) {
        List<AppleMusicArtistLocalizedEntity> fallbackArtistLocales = fallbackArtistLocalizationsByArtistId.get(artistId);

        if (CollectionUtils.isNotEmpty(fallbackArtistLocales)) {
            AppleMusicArtistLocalizedEntity fallbackArtistLocale = fallbackArtistLocales.iterator().next();
            log.debug("Failed to find localized artist record for artistId '{}', trackId: '{}'. Returning fallback: '{}'", artistId,
                trackLocalizedId, fallbackArtistLocale.getId());

            return fallbackArtistLocale;

        } else {
            String errorMsg = String.format("Failed to find ANY localized artist record for artistId '%s', trackId: '%s'", artistId,
                trackLocalizedId);
            log.error(errorMsg);

            throw new DataLocalizationException(errorMsg);
        }
    }
}
