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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.constant.DspConstants;
import io.delphiplatform.api.v3.constant.RequestConstants;
import io.delphiplatform.api.v3.model.Chart;
import io.delphiplatform.api.v3.model.Charts;
import io.delphiplatform.api.v3.model.IncludeChart;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.model.shazam.ShazamChartPositions;
import io.delphiplatform.api.v3.model.shazam.ShazamChartSummaries;
import io.delphiplatform.api.v3.model.shazam.ShazamCitySummaries;
import io.delphiplatform.api.v3.model.video.ShazamChartTrackSummaries;
import io.delphiplatform.api.v3.validation.ExactMatches;
import io.delphiplatform.api.v3.view.interceptor.OnlyDeclaredParams;
import io.delphiplatform.api.v3.view.util.Params;

import static io.delphiplatform.api.v3.constant.ApplicationConstants.DEFAULT_OFFSET;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.DEFAULT_SORT_ORDER;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.ISRC_PATTERN;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.MAX_PAGE_SIZE;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.PAGE_CURSOR_PATTERN;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.PAGE_LIMIT_RANGE_PATTERN;

@Validated
public interface ChartsApi {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();
    }

    /**
     * GET /charts/{chart_id} : Query for a single Chart Resource route for getting a single &#x60;Chart&#x60; with filtering based on provided
     * parameters
     *
     * @param chartId (required)
     * @return &#x60;Chart&#x60; response object (status code 200) or 400 Invalid Input Response (status code 400) or 401 Unauthorized (AuthError)
     * Response (status code 401) or 404 Not Found Error Response (status code 404) or 500 Internal Error Response (status code 500)
     */
    @RequestMapping(value = "/charts/{chart_id}",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Chart> chartsGet(@PathVariable("chart_id") String chartId) {
        return findOne(chartId);
    }

    ResponseEntity<Chart> findOne(String chartId);


    /**
     * GET /charts : Search for Charts Resource route for getting a list of &#x60;Charts&#x60; with filtering based on provided parameters
     *
     * @param chartGroup Identifier for a group of charts (optional)
     * @param dspUnused  UNUSED, preserved for params validation
     * @param limit      The maximum number of &#x60;items&#x60; to return in a single request. (optional)
     * @param offset     The number of &#x60;items&#x60; to offset for pagination. (optional)
     * @param sortBy     Field name by which to sort the data. (optional)
     * @param sortOrder  Direction to sort the data. Default: &#x60;desc&#x60; if &#x60;sort_by&#x60; provided. (optional, default to desc)
     * @return &#x60;Charts&#x60; response object (status code 200) or 400 Invalid Input Response (status code 400) or 401 Unauthorized
     * (AuthError) Response (status code 401) or 500 Internal Error Response (status code 500)
     */
    @OnlyDeclaredParams
    @RequestMapping(value = "/charts",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Charts> chartsSearch(
        @RequestParam(value = "chart_group", required = false) String chartGroup,
        @RequestParam(value = "dsp", required = false) List<String> dspUnused, //preserved for params validation
        @RequestParam(value = RequestConstants.COUNTRY_CODE, required = false) Set<String> countryCode,
        @Min(0) @Max(MAX_PAGE_SIZE)
        @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0)
        @RequestParam(value = "offset", required = false) Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = DEFAULT_SORT_ORDER) SortOrder sortOrder
    ) {
        return getSearchData(
            chartGroup,
            //overriding DSP to youtube-only. Other DSPs are going to be removed from DB. DAPD-1713
            Collections.singletonList(DspConstants.YOUTUBE),
            countryCode,
            limit,
            offset,
            sortBy,
            sortOrder
        );
    }

    ResponseEntity<Charts> getSearchData(String chartGroup, List<String> dsp, Set<String> countries, Integer limit,
        Integer offset, String sortBy, SortOrder sortOrder);

    @OnlyDeclaredParams
    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = "Missing at least one identifying parameter from set: [chart_id, isrc]",
            script = "chartId || isrc"),
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES,
            script = "startDate!=null && endDate!=null && startDate <= endDate")
    })
    @GetMapping(value = "/shazam/charts/track-positions",
        produces = {"application/json"})
    default ResponseEntity<ShazamChartPositions> getShazamChartsTrackPositions(
        @NotNull @Valid
        @RequestParam(value = "start_date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @NotNull @Valid
        @RequestParam(value = "end_date")
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
        @RequestParam(value = "chart_id", required = false) String chartId,
        @RequestParam(value = "city_id", required = false) Set<String> cityId,
        @RequestParam(value = "country_code", required = false) Set<String> countryCode,
        @RequestParam(value = "include", required = false) Set<IncludeChart> include,
        @RequestParam(value = "is_exit", required = false) Boolean isExit,
        @RequestParam(value = "is_sony", required = false) Boolean isSony,
        @RequestParam(value = "isrc", required = false)
        List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @RequestParam(value = "min_position", required = false) Integer minPosition,
        @RequestParam(value = "max_position", required = false) Integer maxPosition,

        @Pattern(regexp = PAGE_LIMIT_RANGE_PATTERN) @RequestParam(value = "limit_range", required = false) String limitRange,
        @Pattern(regexp = PAGE_CURSOR_PATTERN)
        @RequestParam(value = "cursor", required = false) String cursor,
        @Min(0) @Max(MAX_PAGE_SIZE)
        @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0)
        @RequestParam(value = "offset", required = false) Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = DEFAULT_SORT_ORDER) SortOrder sortOrder
    ) {
        Params params = buildParams(null, chartId, cityId, null, countryCode, isSony, isExit,
            isrc, limit, offset, sortBy, sortOrder, startDate, endDate, minPosition, maxPosition, limitRange, include,
            cursor);
        return getShazamChartsTrackPositions(params);
    }

    ResponseEntity<ShazamChartPositions> getShazamChartsTrackPositions(Params params);

    @OnlyDeclaredParams
    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = "Missing at least one identifying parameter from set: [chart_id, isrc]",
            script = "chartId || isrc ")
    })
    @GetMapping(value = "/shazam/charts/track-positions/summary", produces = {"application/json"})
    default ResponseEntity<ShazamChartTrackSummaries> shazamChartSummarySearch(
        @RequestParam(value = "chart_id", required = false) String chartId,
        @RequestParam(value = "city_id", required = false) Set<String> cityId,
        @RequestParam(value = "country_code", required = false) Set<String> countryCode,
        @RequestParam(value = "include", required = false) Set<IncludeChart> include,
        @RequestParam(value = "is_sony", required = false) Boolean isSony,
        @RequestParam(value = "isrc", required = false)
        List<@Pattern(regexp = ISRC_PATTERN) String> isrc,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = DEFAULT_OFFSET) Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = DEFAULT_SORT_ORDER) SortOrder sortOrder) {
        Params params = buildParams(null, chartId, cityId, null, countryCode, isSony, null, isrc, limit, offset, sortBy,
            sortOrder, null, null, null, null, null, include, null);
        return getShazamChartsSummarySearch(params);
    }

    ResponseEntity<ShazamChartTrackSummaries> getShazamChartsSummarySearch(Params params);

    private Params buildParams(Set<String> chartIds, String chartId, Set<String> cityIds,
        Set<String> cityNames, Set<String> countryCodes, Boolean isSony, Boolean isExit, List<String> isrc,
        Integer limit, Integer offset, String sortBy, SortOrder sortOrder, LocalDate startDate, LocalDate endDate,
        Integer minPosition, Integer maxPosition, String limitRange, Set<IncludeChart> include, String cursor) {

        List<String> charts = null;
        if (CollectionUtils.isNotEmpty(chartIds)) {
            charts = new ArrayList<>(chartIds);
        } else if (CollectionUtils.isNotEmpty(chartId)) {
            charts = Collections.singletonList(chartId);
        }

        return Params.builder()
            .chartId(charts)
            .countryCode(CollectionUtils.isEmpty(countryCodes) ? null : countryCodes)
            .cityCode(cityIds)
            .cityName(cityNames)
            .isSony(isSony)
            .isrc(isrc)
            .isExit(isExit)
            .limit(limit)
            .offset(offset)
            .sortBy(sortBy)
            .sortOrder(sortOrder)
            .startDate(startDate)
            .endDate(endDate)
            .minPosition(minPosition)
            .maxPosition(maxPosition)
            .limitRange(limitRange)
            .includeChart(include)
            .cursor(cursor)
            .build();
    }

    @OnlyDeclaredParams
    @GetMapping(value = "/shazam/charts/meta", produces = {"application/json"})
    default ResponseEntity<ShazamChartSummaries> getShazamCharts(
        @RequestParam(value = "chart_id", required = false) Set<String> chartIds,
        @RequestParam(value = RequestConstants.CITY_ID, required = false) Set<String> cityId,
        @RequestParam(value = RequestConstants.COUNTRY_CODE, required = false) Set<String> countryCode,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = DEFAULT_OFFSET) Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = DEFAULT_SORT_ORDER) SortOrder sortOrder) {
        Params params = buildParams(chartIds, null, cityId, null, countryCode, null, null,
            null, limit, offset, sortBy, sortOrder, null, null, null, null, null, null, null);
        return getShazamCharts(params);
    }

    ResponseEntity<ShazamChartSummaries> getShazamCharts(Params params);

    @OnlyDeclaredParams
    @GetMapping(value = "/shazam/cities", produces = {"application/json"})
    default ResponseEntity<ShazamCitySummaries> getShazamCities(
        @RequestParam(value = RequestConstants.CITY_ID, required = false) Set<String> cityId,
        @RequestParam(value = RequestConstants.CITY_NAME, required = false) Set<String> cityName,
        @RequestParam(value = RequestConstants.COUNTRY_CODE, required = false) Set<String> countryCode,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = DEFAULT_OFFSET) Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @ExactMatches(anyOf = {"asc", "desc", "ASC", "DESC"}, message = "Allowed values for sort_order: asc or desc")
        @RequestParam(value = "sort_order", required = false, defaultValue = DEFAULT_SORT_ORDER) String sortOrder) {
        Params params = buildParams(null, null, cityId, cityName, countryCode, null, null,
            null, limit, offset, sortBy, SortOrder.fromValue(sortOrder.toLowerCase()), null, null, null, null, null, null, null);
        return getShazamCities(params);
    }

    ResponseEntity<ShazamCitySummaries> getShazamCities(Params params);


}
