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.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.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.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.model.PublicPlaylist;
import io.delphiplatform.api.v3.model.PublicPlaylists;
import io.delphiplatform.api.v3.model.PublicPlaylistsFollowers;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.validation.ExactMatches;
import io.delphiplatform.api.v3.validation.ExactMatchesForSingleValue;
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 PlaylistsPublicApi {

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

    /**
     * GET /public/playlists/{playlist_id} : public - Query for a single Playlist Resource route for getting a single &#x60;Playlist&#x60; with
     * filtering based on provided parameters
     *
     * @param playlistId       (required)
     * @param appleCountryCode (optional)
     * @return &#x60;PublicPlaylist&#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)
     */
    @OnlyDeclaredParams
    @RequestMapping(value = "/public/playlists/{playlist_id}",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<PublicPlaylist> publicPlaylistsGet(
        @PathVariable("playlist_id") String playlistId,
        @Pattern(regexp = COUNTRY_CODE_PATTERN)
        @RequestParam(value = "apple_country_code", required = false) String appleCountryCode
    ) {
        return findOnePublic(playlistId, appleCountryCode);
    }

    ResponseEntity<PublicPlaylist> findOnePublic(String playlistId, String appleCountryCode);

    /**
     * GET /public/playlists/current/{playlist_id} : public - Query for a single Playlist Resource route for getting a single &#x60;Playlist&#x60;
     * for Apple's playlist or with the latest information about playlist metadata from Spotify
     *
     * @param playlistId (required)
     * @return &#x60;PublicPlaylist&#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)
     */
    @OnlyDeclaredParams
    @RequestMapping(value = "/public/playlists/current/{playlist_id}",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<PublicPlaylist> publicPlaylistsCurrentGet(
        @PathVariable("playlist_id") String playlistId,
        @Pattern(regexp = COUNTRY_CODE_PATTERN)
        @RequestParam(value = "apple_country_code", required = false) String appleCountryCode
    ) {
        return findOnePublicCurrent(playlistId, appleCountryCode);
    }

    ResponseEntity<PublicPlaylist> findOnePublicCurrent(String playlistId, String appleCountryCode);

    /**
     * GET /public/playlists : public - Search for Playlists Resource route for getting a list of &#x60;Playlists&#x60; with filtering based on
     * provided parameters
     *
     * @param dsp            Slug name of a DSP (optional, default to new ArrayList&lt;&gt;())
     * @param playlistIds    Array of playlist ids
     * @param countryCodes   filter response by playlist position country code (optional)
     * @param includeIgnored include ignored playlists into response (optional, default to false)
     * @param includeRemoved include removed playlists into response (optional, default to false)
     * @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;PublicPlaylists&#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)
     */
    @RequestMapping(value = "/public/playlists",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<PublicPlaylists> publicPlaylistsSearch(
        @RequestParam(value = "dsp", required = false) List<String> dsp,
        @RequestParam(value = "playlist_id", required = false) List<String> playlistIds,
        @RequestParam(value = "country_code", required = false)
            Set<@Pattern(regexp = COUNTRY_CODE_PATTERN) String> countryCodes,
        @RequestParam(value = "include_ignored", required = false, defaultValue = "false")
            Boolean includeIgnored,
        @RequestParam(value = "include_removed", required = false, defaultValue = "false")
            Boolean includeRemoved,
        @Min(0) @Max(ApplicationConstants.MAX_PAGE_SIZE)
        @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false) Integer offset,
        @ExactMatchesForSingleValue(message = ApplicationConstants.ERROR_MESSAGE_SORT_BY_PARAMETER, anyOf = {
            "country_code", "dsp_playlist_id", "name", "num_tracks", "playlist_id", "playlist_type", "rank", "uri"
        })
        @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 = "desc") String sortOrder
    ) {
        return getSearchDataPublic(dsp, playlistIds, countryCodes, includeIgnored, includeRemoved,
            limit, offset, sortBy, SortOrder.fromValue(sortOrder.toLowerCase()));
    }

    ResponseEntity<PublicPlaylists> getSearchDataPublic(List<String> dsp, List<String> playlistIds,
        Set<String> countryCodes, boolean includeIgnored, boolean includeRemoved,
        Integer limit, Integer offset, String sortBy, SortOrder sortOrder);

    /**
     * GET /public/playlists/followers : Search for playlists followers data
     *
     * @param playlistIds Array of playlist ids (required)
     * @param startDate   The earliest date to include in the query range. Data returned is start_date inclusive
     * @param endDate     The latest date to include in the query. Data returned is end_date inclusive
     * @return &#x60;PublicPlaylistsFollowers&#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.List({
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES_BOTH_PRESENT_OR_NULL,
            script = ApplicationConstants.SCRIPT_CHECK_DATES_BOTH_PRESENT_OR_NULL
        ),
        @ParameterScriptAssert(lang = "groovy",
            message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES,
            script = ApplicationConstants.SCRIPT_CHECK_DATES_NULLABLE
        )
    })
    @RequestMapping(value = "/public/playlists/followers",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default CompletableFuture<ResponseEntity<PublicPlaylistsFollowers>> publicPlaylistsFollowers(
        @RequestParam(value = "playlist_id") List<String> playlistIds,
        @RequestParam(value = "start_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date", required = false)
        @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate
    ) {
        Params params = Params.builder()
            .playlistId(playlistIds)
            .startDate(startDate)
            .endDate(endDate)
            .build();
        return publicPlaylistsFollowers(params);
    }

    CompletableFuture<ResponseEntity<PublicPlaylistsFollowers>> publicPlaylistsFollowers(Params params);
}
