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

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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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.util.JpaUtils;
import io.delphiplatform.api.v3.model.spotify.ChartTrackMetricsDimension;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartDateKey;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartDateProjection;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartDateTrackKey;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicChartTrackEntity;
import io.delphiplatform.api.v3.rdb.entity.apple.music.AppleMusicTrackLocalizedId;
import io.delphiplatform.api.v3.rdb.repository.apple.music.AppleMusicChartTrackRepository;
import io.delphiplatform.api.v3.rdb.service.dto.JpaPropertyPath;
import io.delphiplatform.api.v3.rdb.service.specification.SpecificationProvider;

import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.APPLE_MUSIC_CHART_ID;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.APPLE_MUSIC_COUNTRY_ID;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.APPLE_MUSIC_TRACK_ID;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CHART;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CHART_DATE;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CHART_DATE_TRACK_KEY;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CHART_RANK;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.CURRENT_POSITION;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.ID_PROPERTY;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.ISRC;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.LOCALIZED_TRACK;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.MARKET_RANK;
import static io.delphiplatform.api.v3.rdb.entity.CommonJpaPropertyNames.TRACK;

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

    private final AppleMusicChartTrackRepository repository;
    private final SpecificationProvider<AppleMusicChartTrackEntity> specificationProvider;

    @Autowired
    public AppleMusicChartTrackService(AppleMusicChartTrackRepository repository,
        SpecificationProvider<AppleMusicChartTrackEntity> specificationProvider) {
        this.repository = repository;
        this.specificationProvider = specificationProvider;
    }

    public Set<AppleMusicChartDateTrackKey> findTrackWithSameIsrcForDateByKey(
        Set<AppleMusicChartDateTrackKey> chartDateTrackKeys,
        LocalDate chartDate
    ) {
        return repository.findTrackWithSameIsrcForDateByKey(chartDateTrackKeys, chartDate);
    }

    public Map<String, List<AppleMusicChartTrackEntity>> findChartTracks(
        String chartId, Set<String> trackIds,
        LocalDate startDate, LocalDate endDate
    ) {
        return deduplicateChartTrackPositions(
            repository.findAll(
                JpaUtils.and(Arrays.asList(
                    specificationProvider.singleValueSpec(APPLE_MUSIC_CHART_ID, chartId),
                    specificationProvider.greaterThanOrEqualToValueSpec(CHART_DATE, startDate),
                    specificationProvider.lessThanOrEqualToValueSpec(CHART_DATE, endDate),
                    specificationProvider.multiValueSpec(APPLE_MUSIC_TRACK_ID, trackIds)
                ))
            )
        ).stream()
            .collect(Collectors.groupingBy(AppleMusicChartTrackEntity::getAppleMusicTrackId));
    }

    public LocalDate findMaxDateByChartId(String chartId) {
        return repository.findMaxDateByChartId(chartId);
    }

    /**
     * @return tracks on this chart/date. Response is Map<key, List<entity>> instead of Map<key, entity> because same track may occupy
     * multiple positions on the chart at the same time, and this method does NOT deduplicate them
     */
    public Map<AppleMusicTrackLocalizedId, List<AppleMusicChartTrackEntity>> findTrackMetricsOnSpecificChartDate(
        Set<String> appleMusicTrackIds, String chartId, LocalDate date, Integer minPosition, Integer maxPosition) {
        return repository.findAll(JpaUtils.and(Arrays.asList(
                specificationProvider.multiValueSpec(APPLE_MUSIC_TRACK_ID, appleMusicTrackIds),
                specificationProvider.singleValueSpec(List.of(ID_PROPERTY, APPLE_MUSIC_CHART_ID), chartId),
                specificationProvider.singleValueSpec(List.of(ID_PROPERTY, CHART_DATE), date),
                specificationProvider.greaterThanOrEqualToValueSpec(CURRENT_POSITION, minPosition),
                specificationProvider.lessThanOrEqualToValueSpec(CURRENT_POSITION, maxPosition),

                specificationProvider.fetchesSpecOfPaths(List.of(JpaPropertyPath.ofJoins(LOCALIZED_TRACK, TRACK),
                    JpaPropertyPath.ofJoins(CHART)))
            ))).stream()
            .collect(Collectors.groupingBy(e -> e.getLocalizedTrack().getId()));
    }

    public Map<AppleMusicTrackLocalizedId, AppleMusicChartTrackEntity> findTrackMetricsOnSpecificChartDateExcludeTrackIds(
        Set<String> notInTrackIds, String chartId, LocalDate date, Integer minPosition, Integer maxPosition) {
        return repository.findAll(JpaUtils.and(Arrays.asList(
                specificationProvider.singleValueSpec(List.of(ID_PROPERTY, APPLE_MUSIC_CHART_ID), chartId),
                specificationProvider.singleValueSpec(List.of(ID_PROPERTY, CHART_DATE), date),
                specificationProvider.greaterThanOrEqualToValueSpec(CURRENT_POSITION, minPosition),
                specificationProvider.lessThanOrEqualToValueSpec(CURRENT_POSITION, maxPosition),
                specificationProvider.notMultiValueSpec(List.of(TRACK, APPLE_MUSIC_TRACK_ID), notInTrackIds),

                specificationProvider.fetchesSpecOfPaths(List.of(JpaPropertyPath.ofJoins(LOCALIZED_TRACK, TRACK)))
            ))).stream()
            .collect(Collectors.toMap(e -> e.getLocalizedTrack().getId(), Function.identity(), this::mergeDuplicates));
    }

    public List<AppleMusicChartTrackEntity> findChartTracks(Set<String> trackIds,
        Set<String> isrcs,
        LocalDate date, Integer minPosition,
        Integer maxPosition, Integer minRank, Integer maxRank, Set<String> countryCode,
        ChartTrackMetricsDimension metricsDimension) {
        return findChartTracks(Collections.emptySet(), trackIds, isrcs, date, minPosition, maxPosition,
            minRank, maxRank, countryCode, metricsDimension);
    }

    public List<AppleMusicChartTrackEntity> findChartTracks(
        Set<AppleMusicChartDateTrackKey> chartDateTrackKeysToExclude,
        Set<String> trackIds,
        Set<String> isrcs,
        LocalDate date, Integer minPosition,
        Integer maxPosition, Integer minRank, Integer maxRank, Set<String> countryCode,
        ChartTrackMetricsDimension metricsDimension) {
        List<String> isrcProperty = metricsDimension == null || metricsDimension == ChartTrackMetricsDimension.TRACK_ID
            ? List.of(TRACK, ISRC)
            : List.of(ISRC);

        return deduplicateChartTrackPositions(
            repository.findAll(JpaUtils.and(Arrays.asList(
                specificationProvider.multiValueSpec(APPLE_MUSIC_TRACK_ID, trackIds),
                specificationProvider.multiValueSpec(isrcProperty, isrcs),
                specificationProvider.notMultiValueSpec(CHART_DATE_TRACK_KEY, chartDateTrackKeysToExclude),
                specificationProvider.singleValueSpec(List.of(ID_PROPERTY, CHART_DATE), date),
                specificationProvider.greaterThanOrEqualToValueSpec(CURRENT_POSITION, minPosition),
                specificationProvider.lessThanOrEqualToValueSpec(CURRENT_POSITION, maxPosition),

                specificationProvider.greaterThanOrEqualToValueSpec(CHART_RANK, minRank, List.of(CHART, MARKET_RANK)),
                specificationProvider.lessThanOrEqualToValueSpec(CHART_RANK, maxRank, List.of(CHART, MARKET_RANK)),

                specificationProvider.multiValueSpec(APPLE_MUSIC_COUNTRY_ID, countryCode)
            )))
        );
    }

    public LocalDate getEarliestDateBefore(LocalDate dateToCompare) {
        if (dateToCompare == null) {
            return repository.findMaxDate();
        }
        return repository.findMaxDateBefore(dateToCompare);
    }

    public List<AppleMusicChartDateProjection> findLastChartDates() {
        return repository.findLastChartDates();
    }

    public List<AppleMusicChartTrackEntity> findChartTrackAtDateOrChartDateDimensionTrack(
        Set<AppleMusicChartDateKey> chartDateKeys, LocalDate date,
        Set<String> trackIds, Set<String> isrcs,
        Integer minPosition, Integer maxPosition, Integer minRank, Integer maxRank, Set<String> countryCode
    ) {
        return repository.findChartTrackAtDateOrChartDateDimensionTrack(chartDateKeys, date, trackIds, isrcs,
            minPosition, maxPosition, minRank, maxRank, countryCode
        );
    }

    public List<AppleMusicChartTrackEntity> findChartTrackAtDateOrChartDateDimensionIsrc(
        Set<AppleMusicChartDateKey> chartDateKeys, LocalDate date,
        Set<String> trackIds, Set<String> isrcs,
        Integer minPosition, Integer maxPosition, Integer minRank, Integer maxRank, Set<String> countryCode
    ) {
        return repository.findChartTrackAtDateOrChartDateDimensionIsrc(chartDateKeys, date, trackIds, isrcs,
            minPosition, maxPosition, minRank, maxRank, countryCode
        );
    }

    //more than one record with same trackId at single chart/date is possible in rare cases (DAPD-1638)
    private AppleMusicChartTrackEntity mergeDuplicates(AppleMusicChartTrackEntity e1, AppleMusicChartTrackEntity e2) {
        if (e1.getCreatedAt().equals(e2.getCreatedAt())) {
            return e1.getCurrentPosition() < e2.getCurrentPosition()
                ? e1
                : e2;
        }

        return e1.getCreatedAt().isAfter(e2.getCreatedAt())
            ? e1
            : e2;
    }

    private List<AppleMusicChartTrackEntity> deduplicateChartTrackPositions(List<AppleMusicChartTrackEntity> list) {
        return new ArrayList<>(
            list.stream()
                //deduplicate possibly repeated records for same day/chart/track (so same track on multiple positions)
                .collect(Collectors.toMap(
                    AppleMusicChartTrackEntity::getChartDateTrackKey, Function.identity(), this::mergeDuplicates))
                .values()
        );
    }
}
