package io.delphiplatform.api.v3.view;

import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
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 java.time.LocalDate;
import java.util.List;

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

import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.model.gras.Project;
import io.delphiplatform.api.v3.model.gras.Projects;
import io.delphiplatform.api.v3.view.interceptor.OnlyDeclaredParams;
import io.delphiplatform.api.v3.view.util.Params;

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 ProjectsApi {

    /**
     * GET /projects/{project_id} : Get one Project Resource route for getting a single &#x60;Project&#x60;.
     *
     * @param projectId Primary key identifier for a (recording) &#x60;Project&#x60;. (required)
     * @return &#x60;Project&#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;404 Not Found&#x60; response status is returned when a single item queried
     * by primary key identifier was not found in the database, or an endpoint path was provided that does not exist.
     * (status code 404) 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)
     */
    @RequestMapping(value = "/projects/{project_id}",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Project> projectGet(
        @PathVariable("project_id") String projectId) {
        return findOne(projectId);
    }

    ResponseEntity<Project> findOne(String projectId);


    /**
     * GET /projects : Search for Projects Resource route for getting a list of &#x60;Project&#x60; with filtering based
     * on provided parameters. when querying this endpoint (e.g.: &#x60;artist_id&#x60;, &#x60;rep_owner_key,
     * &#x60;product_id&#x60;).
     *
     * @param startDate   The earliest date to include in the query range. Data is start_date inclusive. (optional)
     * @param endDate     The latest date to include in the query. Data returned is end_date inclusive. (optional)
     * @param artistId    An optional &#x60;artist_id&#x60; query parameter. (optional)
     * @param repOwnerKey Primary key identifier for a &#x60;RepertoireOwner&#x60; (&#x60;Company&#x60;). (optional,
     *                    default to new ArrayList&lt;&gt;())
     * @param productId   Primary key identifier for a &#x60;Product&#x60;. (optional, default to new
     *                    ArrayList&lt;&gt;())
     * @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)
     * @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)
     * @return &#x60;Projects&#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: [artist_id,"
                + " rep_owner_key, product_id, product_version_no]",
            script = "artistId || repOwnerKey || productId || productVersionNo"),
        @ParameterScriptAssert(lang = "groovy",
            message = "start_date and end_date are required if limit_range and/or cursor is provided.",
            script = "!limitRange && !cursor ||  startDate && endDate")
    })
    @RequestMapping(value = "/projects",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<Projects> projectsSearch(
        @DateTimeFormat(iso = ISO.DATE)
        @RequestParam(value = "start_date", required = false) LocalDate startDate,
        @DateTimeFormat(iso = ISO.DATE)
        @RequestParam(value = "end_date", required = false) LocalDate endDate,
        @RequestParam(value = "artist_id", required = false) String artistId,
        @RequestParam(value = "product_version_no", required = false) Integer productVersionNo,
        @RequestParam(value = "rep_owner_key", required = false) List<String> repOwnerKey,
        @RequestParam(value = "product_id", required = false) List<String> productId,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = "desc") SortOrder sortOrder,
        @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) {
        return getSearchData(Params.builder()
            .startDate(startDate)
            .endDate(endDate)
            .artistId(artistId)
            .productVersionNo(productVersionNo)
            .repOwnerKey(repOwnerKey)
            .productIds(productId)
            .limit(limit)
            .offset(offset)
            .sortBy(sortBy)
            .sortOrder(sortOrder)
            .limitRange(limitRange)
            .cursor(cursor)
            .build());
    }

    ResponseEntity<Projects> getSearchData(Params params);

}
