package io.delphiplatform.api.v3.view;

import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

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.IncludeTracks;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.model.Track;
import io.delphiplatform.api.v3.model.TrackMediaType;
import io.delphiplatform.api.v3.model.Tracks;
import io.delphiplatform.api.v3.model.TracksBrandTagging;
import io.delphiplatform.api.v3.model.TracksGroupByField;
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;

@Validated
public interface TracksApi {

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

    /**
     * GET /tracks/{track_id} : Query for a single Track Resource route for getting a single &#x60;Track&#x60; with filtering based on provided
     * parameters
     *
     * @param trackId (required)
     * @return &#x60;Track&#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 = "/tracks/{track_id}",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Track> tracksGet(
        @Pattern(regexp = ApplicationConstants.GRAS_ID_PATTERN)
        @PathVariable("track_id") String trackId,
        @RequestParam(value = "include", required = false) Set<IncludeTracks> include
    ) {
        return findOne(trackId, include);
    }

    ResponseEntity<Track> findOne(String trackId, Set<IncludeTracks> include);


    /**
     * GET /tracks : Search for Tracks Resource route for getting a list of &#x60;Tracks&#x60; with filtering based on provided parameters
     *
     * @param isrc      Array of International Standard Recording Code (ISRC) numbers (optional, default to new ArrayList&lt;&gt;())
     * @param productId (optional)
     * @param artistId  (optional)
     * @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 groupBy   Field term to group the data by (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;Tracks&#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
    @ParameterScriptAssert(lang = "groovy",
        message = "Missing at least one identifying parameter from set: [product_id, artist_id, isrc, productFamilyNo]",
        script = "productId || artistId || isrc || productFamilyNo")
    @RequestMapping(value = "/tracks",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Tracks> tracksSearch(
        @RequestParam(value = "isrc", required = false)
            List<@Pattern(regexp = ApplicationConstants.ISRC_PATTERN) String> isrc,
        @Pattern(regexp = ApplicationConstants.GRAS_ID_PATTERN)
        @RequestParam(value = "product_id", required = false) String productId,
        @Pattern(regexp = ApplicationConstants.GRAS_ID_PATTERN)
        @RequestParam(value = "artist_id", required = false) String artistId,
        @RequestParam(value = "media_type", required = false) Set<TrackMediaType> mediaTypes,
        @RequestParam(value = "product_family_no", required = false) Integer productFamilyNo,
        @Pattern(regexp = ApplicationConstants.NOT_EMPTY_PATTERN)
        @RequestParam(value = "config_cat_key", required = false) String configCatKey,
        @Pattern(regexp = ApplicationConstants.NOT_EMPTY_PATTERN)
        @RequestParam(value = "config_key", required = false) String configKey,
        @RequestParam(value = "include", required = false) Set<IncludeTracks> include,
        @Min(0) @Max(ApplicationConstants.MAX_PAGE_SIZE)
        @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false) Integer offset,
        @RequestParam(value = "group_by", required = false) TracksGroupByField groupBy,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = "desc") SortOrder sortOrder
    ) {
        return getSearchData(Params.builder()
            .isrc(isrc)
            .productId(productId)
            .artistId(artistId)
            .mediaTypeKeys(CollectionUtils.isEmpty(mediaTypes) ? TrackMediaType.AUDIO_TYPES : mediaTypes)
            .productFamilyNo(productFamilyNo)
            .configCatKey(configCatKey)
            .configKey(configKey)
            .includeTracks(include)
            .limit(limit)
            .offset(offset)
            .tracksGroupByField(groupBy)
            .sortBy(sortBy)
            .sortOrder(sortOrder)
            .build());
    }

    ResponseEntity<Tracks> getSearchData(Params params);

    /**
     * GET /tracks/brands-tagging : Search for brand tagging info per provided track or upc code
     *
     * @param trackId     Array of dsp-prefixed track ids
     * @param upc         Array of track upc codes
     * @param countryCode country code to search for track's brand distribution
     * @return &#x60;TracksBrandTagging&#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
    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_EITHER_TRACK_ID_OR_UPC,
        script = ApplicationConstants.SCRIPT_CHECK_EITHER_TRACK_ID_OR_UPC)
    @RequestMapping(value = "/tracks/brands-tagging",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default CompletableFuture<ResponseEntity<TracksBrandTagging>> getTracksBrandsTagging(
        @RequestParam(value = "track_id", required = false) List<String> trackId,
        @RequestParam(value = "upc", required = false) List<String> upc,
        @Pattern(regexp = COUNTRY_CODE_PATTERN)
        @RequestParam(value = "country_code") String countryCode
    ) {
        return getTracksBrandsTagging(Params.builder()
            .trackId(trackId)
            .upc(upc)
            .countryCode(Collections.singleton(countryCode))
            .build());
    }

    CompletableFuture<ResponseEntity<TracksBrandTagging>> getTracksBrandsTagging(Params params);
}
