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.Objects;
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.charts.ChartTracksAnalyticsResults;
import io.delphiplatform.api.v3.model.datahealth.DataHealthStatusSummary;
import io.delphiplatform.api.v3.model.datahealth.SpotifyChartsDataHealthStatus;
import io.delphiplatform.api.v3.model.datahealth.SpotifyChartsDataHealthStatusReport;
import io.delphiplatform.api.v3.model.spotify.ChartTrackMetricsDimension;
import io.delphiplatform.api.v3.model.spotify.SpotifyChartBreakdown;
import io.delphiplatform.api.v3.model.spotify.SpotifyChartTrackStates;
import io.delphiplatform.api.v3.model.spotify.SpotifyChartType;
import io.delphiplatform.api.v3.model.spotify.SpotifyCharts;
import io.delphiplatform.api.v3.model.spotify.SpotifyTrackChartsSearchResults;
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 SpotifyChartsApi {

    @OnlyDeclaredParams
    @GetMapping(value = "/spotify/charts", produces = {"application/json"})
    default ResponseEntity<SpotifyCharts> getSpotifyCharts(
        @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 = "type", required = false) Set<SpotifyChartType> types,
        @RequestParam(value = "breakdown", required = false) Set<SpotifyChartBreakdown> breakdowns,
        @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)
            .types(types)
            .breakdowns(breakdowns)
            .minRank(minRank)
            .maxRank(maxRank)
            .build();
        return getSpotifyCharts(params);
    }

    ResponseEntity<SpotifyCharts> getSpotifyCharts(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_TRACK_ID_OR_ISRC,
        script = ApplicationConstants.SCRIPT_CHECK_TRACK_ID_OR_ISRC
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/spotify/charts/tracks", produces = {"application/json"})
    default ResponseEntity<SpotifyChartTrackStates> getSpotifyChartTracks(
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .trackId(trackIds)
            .isrc(isrc)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getSpotifyChartTracks(params);
    }

    ResponseEntity<SpotifyChartTrackStates> getSpotifyChartTracks(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
        script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/spotify/charts/tracks/additions", produces = {"application/json"})
    default ResponseEntity<SpotifyChartTrackStates> getSpotifyChartsTracksAdditions(
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .previousDate(previousDate)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getSpotifyChartsTracksAdditions(params);
    }

    ResponseEntity<SpotifyChartTrackStates> getSpotifyChartsTracksAdditions(Params params);

    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_PREVIOUS_DATE_IS_AFTER_DATE,
        script = ApplicationConstants.PREVIOUS_DATE_IS_AFTER_DATE
    )
    @OnlyDeclaredParams
    @GetMapping(value = "/spotify/charts/tracks/removals", produces = {"application/json"})
    default ResponseEntity<SpotifyChartTrackStates> getSpotifyChartsTracksRemovals(
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .chartTrackStateIncludes(CollectionUtils.isEmpty(chartTrackStateIncludes)
                ? Set.of(ChartTrackStateIncludes.values())
                : chartTrackStateIncludes)
            .previousDate(previousDate)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .build();
        return getSpotifyChartsTracksRemovals(params);
    }

    ResponseEntity<SpotifyChartTrackStates> getSpotifyChartsTracksRemovals(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 = "/spotify/tracks/charts", produces = {"application/json"})
    default ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksCharts(
        @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_type", required = false) SpotifyChartType chartType,
        @RequestParam(value = "chart_breakdown", required = false) SpotifyChartBreakdown chartBreakdown,
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .types(Objects.nonNull(chartType) ? Set.of(chartType) : null)
            .breakdowns(Objects.nonNull(chartBreakdown) ? Set.of(chartBreakdown) : null)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getSpotifyTracksCharts(params);
    }

    ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksCharts(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 = "/spotify/tracks/charts/additions", produces = {"application/json"})
    default ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsAdditions(
        @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_type", required = false) Set<SpotifyChartType> chartTypes,
        @RequestParam(value = "chart_breakdown", required = false) Set<SpotifyChartBreakdown> chartBreakdowns,
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .types(chartTypes)
            .breakdowns(chartBreakdowns)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getSpotifyTracksChartsAdditions(params);
    }

    ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsAdditions(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 = "/spotify/tracks/charts/removals", produces = {"application/json"})
    default ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsRemovals(
        @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_type", required = false) Set<SpotifyChartType> chartTypes,
        @RequestParam(value = "chart_breakdown", required = false) Set<SpotifyChartBreakdown> chartBreakdowns,
        @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)
            .chartTrackMetricsDimension(metricsDimension)
            .trackStateIncludes(CollectionUtils.isEmpty(trackStateIncludes)
                ? Set.of(TrackStateIncludes.values())
                : trackStateIncludes)
            .types(chartTypes)
            .breakdowns(chartBreakdowns)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .minPosition(minTrackPosition)
            .maxPosition(maxTrackPosition)
            .build();
        return getSpotifyTracksChartsRemovals(params);
    }

    ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsRemovals(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 = "/spotify/tracks/charts/lifetime", produces = {"application/json"})
    default ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsLifetime(
        @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_type", required = false) Set<SpotifyChartType> chartTypes,
        @RequestParam(value = "chart_breakdown", required = false) Set<SpotifyChartBreakdown> chartBreakdowns,
        @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)
            .types(chartTypes)
            .breakdowns(chartBreakdowns)
            .countryCode(chartCountryCode)
            .minRank(chartMinRank)
            .maxRank(chartMaxRank)
            .build();

        return getSpotifyTracksChartsLifetime(params);
    }

    ResponseEntity<SpotifyTrackChartsSearchResults> getSpotifyTracksChartsLifetime(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 = "/spotify/charts/analytics", produces = {"application/json"})
    default ResponseEntity<ChartTracksAnalyticsResults> getSpotifyChartsAnalytics(
        @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 getSpotifyChartsTracksAnalytics(params);
    }

    ResponseEntity<ChartTracksAnalyticsResults> getSpotifyChartsTracksAnalytics(Params params);

    @OnlyDeclaredParams
    @GetMapping(value = "/spotify/charts/data-health/status", produces = {"application/json"})
    default ResponseEntity<SpotifyChartsDataHealthStatusReport<DataHealthStatusSummary>> getSpotifyChartsDataHealthStatus(
        @RequestParam(value = "status") @NotEmpty(message = ApplicationConstants.ERROR_MESSAGE_STATUS_IS_EMPTY)
            Set<SpotifyChartsDataHealthStatus> status,
        @RequestParam(value = "chart_breakdown", required = false) SpotifyChartBreakdown breakdown,
        @RequestParam(value = "chart_type", required = false) Set<SpotifyChartType> types,
        @RequestParam(value = "country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCodes
    ) {
        Params params = Params.builder()
            .dataHealthStatus(status.stream().map(SpotifyChartsDataHealthStatus::getStatus).collect(Collectors.toSet()))
            .breakdowns(breakdown == null
                ? Set.of(SpotifyChartBreakdown.values())
                : Collections.singleton(breakdown))
            .types(CollectionUtils.isEmpty(types) ? Collections.emptySet() : new HashSet<>(types))
            .countryCode(CollectionUtils.isEmpty(countryCodes) ? Collections.emptySet() : new HashSet<>(countryCodes))
            .build();
        return getSpotifyChartsDataHealthStatus(params);
    }

    ResponseEntity<SpotifyChartsDataHealthStatusReport<DataHealthStatusSummary>> getSpotifyChartsDataHealthStatus(Params params);
}
