package io.delphiplatform.api.v3.view.video;

import io.delphiplatform.api.v3.constant.RequestConstants;
import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
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.server.ResponseStatusException;

import java.time.LocalDate;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import javax.transaction.NotSupportedException;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;

import io.delphiplatform.api.util.CollectionUtils;
import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.model.video.ExpandTo;
import io.delphiplatform.api.v3.model.video.GroupByVideo;
import io.delphiplatform.api.v3.model.video.VideoDspSlug;
import io.delphiplatform.api.v3.model.video.VideoSummaryItems;
import io.delphiplatform.api.v3.model.video.YouTubeMetricsCategory;
import io.delphiplatform.api.v3.model.video.YoutubeContentType;
import io.delphiplatform.api.v3.view.util.ApiUtil;
import io.delphiplatform.api.v3.view.util.Params;
import io.delphiplatform.api.v3.view.interceptor.OnlyDeclaredParams;

import static io.delphiplatform.api.v3.constant.ApplicationConstants.COUNTRY_CODE_PATTERN;
import static io.delphiplatform.api.v3.constant.ApplicationConstants.VIDEO_ID_PATTERN;

@Validated
public interface VideoAnalyticsApi {

    /**
     * GET /video-analytics : Search for video analytics data Resource route for getting &#x60;VideoSummaryItems&#x60;
     * with filtering for common dimensions.  At least one identifying parameter must be provided (e.g.:
     * &#x60;isrc&#x60;, or &#x60;track_id&#x60;, etc.)
     *
     * @param startDate    The earliest date to include in the query range. Data is start_date inclusive. (required)
     * @param endDate      The latest date to include in the query. Data returned is end_date inclusive. (required)
     * @param contentTypes Content Type e.g.: &#x60;partner_uploaded&#x60;, &#x60;premium_ugc&#x60;, etc. For YouTube
     *                     these are related to active claims.  (optional)
     * @param isrc         International Standard Recording Code (ISRC) number (optional)
     * @param dsp          The slug name of a video DSP. This is a lowercase value that is the same as the
     *                     &#x60;dsp_id&#x60;. (optional)
     * @param videoId      Primary key identifier for a Video (optional)
     * @param expandTo     Providing &#x60;expand_to&#x3D;related_isrcs&#x60; will expand the query to include data from
     *                     related ISRCs by using Product Family.  (optional, default to new ArrayList&lt;&gt;())
     * @param groupBy      Provide one or more &#x60;group_by&#x60; values for aggregating results on certain
     *                     dimensions. A sensible default value of &#x60;video_id&#x60; is applied if the
     *                     &#x60;group_by&#x60; parameter is omitted from requests.  (optional, default to new
     *                     ArrayList&lt;&gt;())
     * @param limitRange   Pagination limit to specify the number of days to include in a single response page in the
     *                     format: &#x60;days:28&#x60;. Results spanning more than one page will return a
     *                     &#x60;next_cursor&#x60; value in the response. Provide this via the &#x60;cursor&#x60;
     *                     parameter in subsequent requests to page through results. If the number of days provided
     *                     exceeds the number of days between the &#x60;start_date&#x60; (or &#x60;cursor&#x60;) before
     *                     the &#x60;end_date&#x60;, the results will only include data through the
     *                     &#x60;end_date&#x60;. See more extensive documentation in [The Delphi API - Endpoints &amp;
     *                     Parameters - Paginating Results](https://data-analytics.atlassian.net/wiki/spaces/DDPS/pages/427720719/Endpoints+Parameters#Paginating-Results).
     *                     (optional)
     * @param cursor       This pagination parameter should be the value from a previous response&#39;s
     *                     &#x60;next_cursor&#x60;, which contains the starting point of the current page results&#39;
     *                     data. Provide this updated value to effectively page through results when using the
     *                     &#x60;limit_range&#x60; parameter. Passing a cursor outside of the provided
     *                     &#x60;start_date&#x60; and &#x60;end_date&#x60; range will result in the &#x60;cursor&#x60;
     *                     simply be ignored.  See more extensive documentation in [The Delphi API - Endpoints &amp;
     *                     Parameters - Paginating Results](https://data-analytics.atlassian.net/wiki/spaces/DDPS/pages/427720719/Endpoints+Parameters#Paginating-Results).
     *                     (optional)
     * @param limit        The maximum number of &#x60;items&#x60; to return in a single request (i.e.: a single page).
     *                     (optional)
     * @param offset       The number of &#x60;items&#x60; to offset (aka skip) for pagination.  (optional, default to
     *                     0)
     * @param sortBy       Field name existing in the top level of an object in &#x60;items&#x60; by which to sort the
     *                     results. (optional)
     * @param sortOrder    Direction to sort the data. Default: &#x60;desc&#x60; if &#x60;sort_by&#x60; provided.
     *                     (optional, default to desc)
     * @return &#x60;VideoSummaryItems&#x60; response object (status code 200) or A &#x60;400 Bad Request&#x60; response
     * status indicates a problem with the input provided by a client request. If you are receiving this error, check
     * your request parameters are valid.  (status code 400) or A &#x60;401 Unauthorized&#x60; response status indicates
     * that the request did not include a required Authorization header, or that there was a problem authenticating the
     * client. Possible problems include an expired or invalid token, or the Authorization header is not in the expected
     * format.  (status code 401) or A &#x60;500 Internal Error&#x60; response status is returned for any application
     * and server level errors that are not already associated with another error status. A 500 is used as a generic
     * fallback error.  (status code 500)
     */
    @OnlyDeclaredParams
    @ParameterScriptAssert.List({
        @ParameterScriptAssert(lang = "groovy",
            message = "Missing at least one identifying parameter from set: [video_id, isrc, artist_id]",
            script = "isrc || videoId || artistId"),
        @ParameterScriptAssert(lang = "groovy",
            message = "ISRC is required if expand_to=related_isrc provided",
            script = "!expandTo || expandTo && isrc")
    })
    @RequestMapping(value = "/video-analytics",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default CompletableFuture<ResponseEntity<VideoSummaryItems>> videoAnalyticsSearch(
        @RequestParam(value = "start_date") @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date") @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
        @RequestParam(value = "content_type", required = false) Set<YoutubeContentType> contentTypes,
        @Pattern(regexp = ApplicationConstants.ISRC_PATTERN) @RequestParam(value = "isrc", required = false) String isrc,
        @RequestParam(value = "dsp", required = false) VideoDspSlug dsp,
        @RequestParam(value = "country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCode,
        @RequestParam(value = "video_id", required = false)
            Set<@Pattern(regexp = VIDEO_ID_PATTERN) String> videoId,
        @Pattern(regexp = ApplicationConstants.GRAS_ID_PATTERN)
        @RequestParam(value = "artist_id", required = false) String artistId,
        @RequestParam(value = "expand_to", required = false) Set<ExpandTo> expandTo,
        @RequestParam(value = "group_by", required = false, defaultValue = "video_id") Set<GroupByVideo> groupBy,
        @RequestParam(value = "only", required = false) Set<YouTubeMetricsCategory> onlyMetricsCategories,
        @Pattern(regexp = ApplicationConstants.PAGE_LIMIT_RANGE_PATTERN) @RequestParam(value = "limit_range", required = false) String limitRange,
        @Pattern(regexp = ApplicationConstants.PAGE_CURSOR_PATTERN) @RequestParam(value = "cursor", required = false) String cursor,
        @Min(0) @Max(ApplicationConstants.MAX_BIGTABLE_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 = "desc") SortOrder sortOrder) {
        try {
            // ToDo: Remove restriction in DAE-1481 and use contentTypes param
            contentTypes = ApiUtil.getFilteredContentTypes(contentTypes);
            if (CollectionUtils.isNotEmpty(onlyMetricsCategories)) {
                onlyMetricsCategories.add(YouTubeMetricsCategory.VIEWS);
            }
            return getSearchData(Params.builder()
                .startDate(startDate)
                .endDate(endDate)
                .youTubeContentTypes(contentTypes)
                .minPremiumUgcViews(ApplicationConstants.MIN_PREMIUM_UGC_VIEWS)
                .countryCode(countryCode)
                .isrc(CollectionUtils.isEmpty(isrc) ? null : Collections.singletonList(isrc))
                .videoDsp(dsp)
                .videoIds(videoId)
                .artistId(artistId)
                .expandTo(expandTo)
                .groupByFieldsVideo(groupBy)
                .metricCategories(onlyMetricsCategories)
                .limitRange(limitRange)
                .cursor(cursor)
                .limit(limit)
                .offset(offset)
                .sortBy(sortBy)
                .sortOrder(sortOrder)
                .build());
        } catch (NotSupportedException e) {
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    CompletableFuture<ResponseEntity<VideoSummaryItems>> getSearchData(Params params) throws NotSupportedException;

    @OnlyDeclaredParams
    @GetMapping(value = "/video-analytics/product-family", produces = {MediaType.APPLICATION_JSON_VALUE})
    default CompletableFuture<ResponseEntity<VideoSummaryItems>> getVideoAnalyticsAggByProductFamily(
        @RequestParam(value = "start_date") @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date") @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
        @RequestParam(value = "content_type", required = false) Set<YoutubeContentType> contentTypes,
        @RequestParam(value = "dsp", required = false) VideoDspSlug dsp,
        @RequestParam(value = RequestConstants.COUNTRY_CODE, required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCode,
        @RequestParam(value = "product_family_id") Set<String> productFamilyId,
        @RequestParam(value = "group_by", required = false) Set<GroupByVideo> groupBy,
        @RequestParam(value = "only", required = false) Set<YouTubeMetricsCategory> onlyMetricsCategories,
        @Pattern(regexp = ApplicationConstants.PAGE_LIMIT_RANGE_PATTERN) @RequestParam(value = "limit_range", required = false) String limitRange,
        @Pattern(regexp = ApplicationConstants.PAGE_CURSOR_PATTERN) @RequestParam(value = "cursor", required = false) String cursor,
        @Min(0) @Max(ApplicationConstants.MAX_BIGTABLE_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 = "desc") SortOrder sortOrder) {
        try {
            return getSearchData(Params.builder()
                .startDate(startDate)
                .endDate(endDate)
                .youTubeContentTypes(
                    CollectionUtils.isEmpty(contentTypes) ? Set.of(YoutubeContentType.values()) : contentTypes)
                .countryCode(countryCode)
                .videoDsp(dsp)
                .productFamilyIds(productFamilyId)
                .groupByFieldsVideo(groupBy)
                .metricCategories(onlyMetricsCategories)
                .limitRange(limitRange)
                .cursor(cursor)
                .limit(limit)
                .offset(offset)
                .sortBy(sortBy)
                .sortOrder(sortOrder)
                .build());
        } catch (NotSupportedException e) {
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }

    @OnlyDeclaredParams
    @GetMapping(value = "/video-analytics/project", produces = {MediaType.APPLICATION_JSON_VALUE})
    default CompletableFuture<ResponseEntity<VideoSummaryItems>> getVideoAnalyticsAggByProject(
            @RequestParam(value = "start_date") @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
            @RequestParam(value = "end_date") @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
            @RequestParam(value = "content_type", required = false) Set<YoutubeContentType> contentTypes,
            @RequestParam(value = "dsp", required = false) VideoDspSlug dsp,
            @RequestParam(value = RequestConstants.COUNTRY_CODE, required = false)
                Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCode,
            @RequestParam(value = "project_number") Set<String> projectNumber,
            @RequestParam(value = "group_by", required = false) Set<GroupByVideo> groupBy,
            @RequestParam(value = "only", required = false) Set<YouTubeMetricsCategory> onlyMetricsCategories,
            @Pattern(regexp = ApplicationConstants.PAGE_LIMIT_RANGE_PATTERN) @RequestParam(value = "limit_range", required = false) String limitRange,
            @Pattern(regexp = ApplicationConstants.PAGE_CURSOR_PATTERN) @RequestParam(value = "cursor", required = false) String cursor,
            @Min(0) @Max(ApplicationConstants.MAX_BIGTABLE_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 = "desc") SortOrder sortOrder) {
        try {
            return getSearchData(Params.builder()
                    .startDate(startDate)
                    .endDate(endDate)
                    .youTubeContentTypes(
                        CollectionUtils.isEmpty(contentTypes) ? Set.of(YoutubeContentType.values()) : contentTypes)
                    .countryCode(countryCode)
                    .videoDsp(dsp)
                    .projectNumbers(projectNumber)
                    .groupByFieldsVideo(groupBy)
                    .metricCategories(onlyMetricsCategories)
                    .limitRange(limitRange)
                    .cursor(cursor)
                    .limit(limit)
                    .offset(offset)
                    .sortBy(sortBy)
                    .sortOrder(sortOrder)
                    .build());
        } catch (NotSupportedException e) {
            throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, e.getMessage());
        }
    }
}
