package io.delphiplatform.api.v3.view;

import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.time.LocalDate;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.model.AnalyticsMetrics;
import io.delphiplatform.api.v3.model.ChartTrackStateIncludes;
import io.delphiplatform.api.v3.model.TrackStateIncludes;
import io.delphiplatform.api.v3.model.apple.music.AppleMusicChartTrackStates;
import io.delphiplatform.api.v3.model.apple.music.AppleMusicCharts;
import io.delphiplatform.api.v3.model.apple.music.AppleMusicTrackChartsSearchResults;
import io.delphiplatform.api.v3.model.charts.ChartTracksAnalyticsResults;
import io.delphiplatform.api.v3.model.datahealth.AppleDataHealthStatusSummary;
import io.delphiplatform.api.v3.model.datahealth.AppleMusicChartsDataHealthStatus;
import io.delphiplatform.api.v3.model.datahealth.ChartsDataHealthStatusReport;
import io.delphiplatform.api.v3.model.spotify.ChartTrackMetricsDimension;
import io.delphiplatform.api.v3.view.interceptor.OnlyDeclaredParams;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.v3.constant.ApplicationConstants.COUNTRY_CODE_PATTERN;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.ISRC_PATTERN;
import static io.delphiplatform.api.v3.constant.RequestConstants.METRICS_DIMENSION;

@Validated
public interface AppleMusicApi {

    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts", produces = {"application/json"})
    default ResponseEntity<AppleMusicCharts> getAppleMusicCharts(
        @RequestParam(value = "chart_id", required = false) List<String> chartIds,
        @RequestParam(value = "country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCodes,
        @RequestParam(value = "min_rank", required = false) @Min(0) Integer minRank,
        @RequestParam(value = "max_rank", required = false) @Min(0) Integer maxRank
    ) {
        Params params = Params.builder()
            .chartId(chartIds)
            .countryCode(countryCodes)
            .minRank(minRank)
            .maxRank(maxRank)
            .build();
        return getAppleMusicCharts(params);
    }

    ResponseEntity<AppleMusicCharts> getAppleMusicCharts(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_TRACK_ID_OR_ISRC,
        script = ApplicationConstants.SCRIPT_CHECK_TRACK_ID_OR_ISRC
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts/tracks", produces = {"application/json"})
    default ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartTracks(
        @RequestParam(value = "chart_id") String chartId,
        @RequestParam(value = "date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false) Set<ChartTrackStateIncludes> chartTrackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,

        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "min_position", required = false) @Min(0) Integer minPosition,
        @RequestParam(value = "max_position", required = false) @Min(0) Integer maxPosition
    ) {
        Params params = Params.builder()
            .singleChartId(chartId)
            .date(date)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .trackId(trackIds)
            .isrc(isrc)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getAppleMusicChartTracks(params);
    }

    ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartTracks(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
        script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts/tracks/additions", produces = {"application/json"})
    default ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartsTracksAdditions(
        @RequestParam(value = "chart_id") String chartId,
        @RequestParam(value = "date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false) Set<ChartTrackStateIncludes> chartTrackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,

        @RequestParam(value = "previous_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate previousDate,
        @RequestParam(value = "min_position", required = false) @Min(0) Integer minPosition,
        @RequestParam(value = "max_position", required = false) @Min(0) Integer maxPosition
    ) {
        Params params = Params.builder()
            .singleChartId(chartId)
            .date(date)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .previousDate(previousDate)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getAppleMusicChartsTracksAdditions(params);
    }

    ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartsTracksAdditions(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
        script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts/tracks/removals", produces = {"application/json"})
    default ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartsTracksRemovals(
        @RequestParam(value = "chart_id") String chartId,
        @RequestParam(value = "date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false) Set<ChartTrackStateIncludes> chartTrackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,
        @RequestParam(value = "previous_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate previousDate,
        @RequestParam(value = "min_position", required = false) @Min(0) Integer minPosition,
        @RequestParam(value = "max_position", required = false) @Min(0) Integer maxPosition
    ) {
        Params params = Params.builder()
            .singleChartId(chartId)
            .date(date)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .previousDate(previousDate)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getAppleMusicChartsTracksRemovals(params);
    }

    ResponseEntity<AppleMusicChartTrackStates> getAppleMusicChartsTracksRemovals(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_EITHER_TRACK_ID_OR_ISRC,
        script = ApplicationConstants.SCRIPT_CHECK_EITHER_TRACK_ID_OR_ISRC
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/tracks/charts", produces = {"application/json"})
    default ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksCharts(
        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false)
            Set<TrackStateIncludes> trackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,
        @RequestParam(value = "chart_country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> chartCountryCode,
        @RequestParam(value = "chart_min_rank", required = false) @Min(0) Integer chartMinRank,
        @RequestParam(value = "chart_max_rank", required = false) @Min(0) Integer chartMaxRank,
        @RequestParam(value = "min_track_position", required = false) @Min(0) Integer minTrackPosition,
        @RequestParam(value = "max_track_position", required = false) @Min(0) Integer maxTrackPosition
    ) {
        Params params = Params.builder()
            .trackId(trackIds)
            .isrc(isrc)
            .date(date)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getAppleMusicTracksCharts(params);
    }

    ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksCharts(Params params);

    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_EITHER_TRACK_ID_OR_ISRC,
            script = ApplicationConstants.SCRIPT_CHECK_EITHER_TRACK_ID_OR_ISRC
        ),
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
            script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
        )
    })
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/tracks/charts/additions", produces = {"application/json"})
    default ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsAdditions(
        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "previous_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate previousDate,
        @RequestParam(value = "date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false)
            Set<TrackStateIncludes> trackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,
        @RequestParam(value = "chart_country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> chartCountryCode,
        @RequestParam(value = "chart_min_rank", required = false) @Min(0) Integer chartMinRank,
        @RequestParam(value = "chart_max_rank", required = false) @Min(0) Integer chartMaxRank,
        @RequestParam(value = "min_track_position", required = false) @Min(0) Integer minTrackPosition,
        @RequestParam(value = "max_track_position", required = false) @Min(0) Integer maxTrackPosition
    ) {
        Params params = Params.builder()
            .trackId(trackIds)
            .isrc(isrc)
            .previousDate(previousDate)
            .date(date)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getAppleMusicTracksChartsAdditions(params);
    }

    ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsAdditions(Params params);

    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_EITHER_TRACK_ID_OR_ISRC,
            script = ApplicationConstants.SCRIPT_CHECK_EITHER_TRACK_ID_OR_ISRC
        ),
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
            script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
        )
    })
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/tracks/charts/removals", produces = {"application/json"})
    default ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsRemovals(
        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "previous_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate previousDate,
        @RequestParam(value = "date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
        @RequestParam(value = "track_state_includes", required = false)
            Set<TrackStateIncludes> trackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,
        @RequestParam(value = "chart_country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> chartCountryCode,
        @RequestParam(value = "chart_min_rank", required = false) @Min(0) Integer chartMinRank,
        @RequestParam(value = "chart_max_rank", required = false) @Min(0) Integer chartMaxRank,
        @RequestParam(value = "min_track_position", required = false) @Min(0) Integer minTrackPosition,
        @RequestParam(value = "max_track_position", required = false) @Min(0) Integer maxTrackPosition
    ) {
        Params params = Params.builder()
            .trackId(trackIds)
            .isrc(isrc)
            .previousDate(previousDate)
            .date(date)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getAppleMusicTracksChartsRemovals(params);
    }

    ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsRemovals(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_EITHER_TRACK_ID_OR_ISRC,
        script = ApplicationConstants.SCRIPT_CHECK_EITHER_TRACK_ID_OR_ISRC
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/tracks/charts/lifetime", produces = {"application/json"})
    default ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsLifetime(
        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "track_state_includes", required = false) Set<TrackStateIncludes> trackStateIncludes,
        @RequestParam(value = METRICS_DIMENSION, required = false, defaultValue = "track_id") ChartTrackMetricsDimension metricsDimension,
        @RequestParam(value = "chart_country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> chartCountryCode,
        @RequestParam(value = "chart_min_rank", required = false) @Min(0) Integer chartMinRank,
        @RequestParam(value = "chart_max_rank", required = false) @Min(0) Integer chartMaxRank
    ) {
        Params params = Params.builder()
            .trackId(trackIds)
            .isrc(isrc)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .chartTrackMetricsDimension(metricsDimension)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .build();

        return getAppleMusicTracksChartsLifetime(params);
    }

    ResponseEntity<AppleMusicTrackChartsSearchResults> getAppleMusicTracksChartsLifetime(Params params);

    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_TRACK_ID_OR_ISRC,
            script = ApplicationConstants.SCRIPT_CHECK_TRACK_ID_OR_ISRC
        ),
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES,
            script = ApplicationConstants.SCRIPT_CHECK_DATES
        )
    })
    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts/analytics", produces = {"application/json"})
    default ResponseEntity<ChartTracksAnalyticsResults> getAppleMusicChartsAnalytics(
        @RequestParam(value = "chart_id") String chartId,
        @RequestParam(value = "track_id", required = false) List<String> trackIds,
        @RequestParam(value = "isrc", required = false) List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "start_date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
        @RequestParam(value = "metrics") Set<AnalyticsMetrics> metrics
    ) {
        Params params = Params.builder()
            .singleChartId(chartId)
            .trackId(CollectionUtils.isEmpty(trackIds) ? Collections.emptyList() : trackIds)
            .isrc(isrc)
            .startDate(startDate)
            .endDate(endDate)
            .analyticsMetrics(metrics)
            .build();

        return getAppleMusicChartsTracksAnalytics(params);
    }

    ResponseEntity<ChartTracksAnalyticsResults> getAppleMusicChartsTracksAnalytics(Params params);

    @OnlyDeclaredParams
    @GetMapping(value = "/apple-music/charts/data-health/status", produces = {"application/json"})
    default ResponseEntity<ChartsDataHealthStatusReport<AppleDataHealthStatusSummary>> getAppleMusicChartsDataHealthStatus(
        @RequestParam(value = "status") @NotEmpty(message = ApplicationConstants.ERROR_MESSAGE_STATUS_IS_EMPTY)
            Set<AppleMusicChartsDataHealthStatus> status,
        @RequestParam(value = "country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCodes
    ) {
        Params params = Params.builder()
            .dataHealthStatus(status.stream().map(AppleMusicChartsDataHealthStatus::getStatus).collect(Collectors.toSet()))
            .countryCode(CollectionUtils.isEmpty(countryCodes) ? Collections.emptySet() : new HashSet<>(countryCodes))
            .build();
        return getAppleMusicChartsDataHealthStatus(params);
    }

    ResponseEntity<ChartsDataHealthStatusReport<AppleDataHealthStatusSummary>> getAppleMusicChartsDataHealthStatus(Params params);
}
