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

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
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.ads.AdPerformanceSummaryItems;
import io.delphiplatform.api.v3.model.ads.AdsCampaignsBudgetItems;
import io.delphiplatform.api.v3.model.ads.AdsDspSlug;
import io.delphiplatform.api.v3.model.ads.AdsSetBudgetItems;
import io.delphiplatform.api.v3.model.ads.GroupByAdsPerformance;
import io.delphiplatform.api.v3.model.datahealth.AdsDataHealthRangeReport;
import io.delphiplatform.api.v3.model.datahealth.AdsDataHealthStatus;
import io.delphiplatform.api.v3.model.datahealth.AdsDataHealthStatusReport;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdown;
import io.delphiplatform.api.v3.model.datahealth.DataHealthBreakdownGroup;
import io.delphiplatform.api.v3.model.datahealth.DataHealthStatusSummary;
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 AdsApi {


    /**
     * GET /ads/performance : Get ad performance data aggregations with flexible filters Resource route for getting ad
     * performance data.
     *
     * @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 artistId       An optional &#x60;artist_id&#x60; query parameter. (optional)
     * @param campaignId     (optional, default to new ArrayList&lt;&gt;())
     * @param decibelLabelId (optional, default to new ArrayList&lt;&gt;())
     * @param dsp            The slug name of a DSP. This is a lowercase value that is the same as the
     *                       &#x60;dsp_id&#x60;. (optional)
     * @param groupBy        (optional, default to new ArrayList&lt;&gt;())
     * @param isAssigned     (optional)
     * @param isPending      (optional)
     * @param platform       (optional, default to new ArrayList&lt;&gt;())
     * @param projectId      Primary key identifier for a (recording) &#x60;Project&#x60;. (optional)
     * @param providerId     (optional, default to new ArrayList&lt;&gt;())
     * @param subCategoryId  (optional, default to new ArrayList&lt;&gt;())
     * @param userId         (optional)
     * @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;AdsPerformance&#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(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES,
        script = ApplicationConstants.SCRIPT_CHECK_DATES)
    @RequestMapping(value = "/ads/performance",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<AdPerformanceSummaryItems> adsPerformanceSearch(
        @RequestParam(value = "start_date") @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date") @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
        @RequestParam(value = "artist_id", required = false) String artistId,
        @RequestParam(value = "campaign_id", required = false) Set<String> campaignId,
        @RequestParam(value = "decibel_label_id", required = false) Set<Integer> decibelLabelId,
        @RequestParam(value = "dsp", required = false) Set<AdsDspSlug> dsp,
        @RequestParam(value = "group_by", required = false) Set<GroupByAdsPerformance> groupBy,
        @RequestParam(value = "is_assigned", required = false) Boolean isAssigned,
        @RequestParam(value = "is_pending", required = false) Boolean isPending,
        @RequestParam(value = "referrer_domain", required = false) Set<String> referrerDomains,
        @RequestParam(value = "platform", required = false) Set<String> platform,
        @RequestParam(value = "project_id", required = false) Set<String> projectId,
        @RequestParam(value = "provider_id", required = false) Set<Integer> providerId,
        @RequestParam(value = "country_code", required = false) Set<String> countryCodes,
        @RequestParam(value = "sub_category_id", required = false) Set<Integer> subCategoryId,
        @RequestParam(value = "user_id") Integer userId,
        @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, defaultValue = "0") Integer offset,
        @RequestParam(value = "sort_by", required = false) String sortBy,
        @RequestParam(value = "sort_order", required = false, defaultValue = "desc") SortOrder sortOrder,
        @Pattern(regexp = "^[A-Z]{3}$") @RequestParam(value = "currency", required = false) String currency) {
        Params params = Params.builder()
            .startDate(startDate)
            .endDate(endDate)
            .artistId(artistId)
            .campaignIds(campaignId)
            .decibelLabelIds(decibelLabelId)
            .adsDsps(dsp)
            .adsPerformancesGroupBy(groupBy == null ? Collections.emptySet() : groupBy)
            .isAssigned(isAssigned)
            .isPending(isPending)
            .referrerDomains(referrerDomains)
            .platforms(platform)
            .projectIds(projectId)
            .providerIds(providerId)
            .subCategoryIds(subCategoryId)
            .countryCode(countryCodes)
            .userId(userId)
            .limitRange(limitRange)
            .cursor(cursor)
            .limit(limit)
            .offset(offset)
            .sortBy(sortBy)
            .sortOrder(sortOrder)
            .currency(currency)
            .build();

        return getSearchData(params);
    }

    ResponseEntity<AdPerformanceSummaryItems> getSearchData(Params params);

    /**
     * GET /ads/data-health/dsp/status Returns DSP data health status data for the dsp lifetime.
     *
     * @param dsp    Optionally include one or more DSP slug names. Omitting this field entirely will result in all
     *               available DSPs being queried. The results from each DSP will be combined and aggregated. To query
     *               for data by individual DSPs only, use separate requests providing one DSP per request. (optional,
     *               default to new ArrayList&lt;&gt;())
     * @param status Data health status describing the state of data. (optional)
     * @param parentRepOwner Label ids to filter the result (optional)
     * @param adsAccounts Ads account ids to filter the result (optional)
     * @return AdsDataHealthStatus 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
    @RequestMapping(value = "/ads/data-health/dsp/status",
        produces = {"application/json"},
        method = RequestMethod.GET)
    default ResponseEntity<AdsDataHealthStatusReport<DataHealthStatusSummary>> adsDataHealthStatusGet(
        @RequestParam(value = "dsp", required = false) Set<String> dsp,
        @RequestParam(value = "status") @NotEmpty(message = ApplicationConstants.ERROR_MESSAGE_STATUS_IS_EMPTY) Set<AdsDataHealthStatus> status,
        @RequestParam(value = "parent_rep_owner", required = false) Set<String> parentRepOwner,
        @RequestParam(value = "ads_account", required = false) Set<String> adsAccounts) {
        return getAdsDataHealthStatus(Params.builder()
            .dsp(CollectionUtils.isEmpty(dsp) ? Collections.emptyList() : new ArrayList<>(dsp))
            .adsAccounts(CollectionUtils.isEmpty(adsAccounts) ? Collections.emptySet() : adsAccounts)
            .parentRepOwnerKeys(
                CollectionUtils.isEmpty(parentRepOwner) ? Collections.emptyList() : new ArrayList<>(parentRepOwner))
            .dataHealthStatus(status.stream().map(AdsDataHealthStatus::getStatus).collect(Collectors.toSet())).build());
    }

    ResponseEntity<AdsDataHealthStatusReport<DataHealthStatusSummary>> getAdsDataHealthStatus(Params params);

    /**
     * GET /ads/data-health/dsp/status/analytics Returns DSP data health status data for the specified date range and
     * breakdowns. Consolidates multiple data providers into uniform API. Provides a high-level overview over the daily
     * data processing statuses.
     *
     * @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 breakdownGroups List of the breakdown groups to include in the response. By default only dsp_segment
     *                        breakdown group is included. (optional, default to new ArrayList&lt;&gt;())
     * @param breakdowns      List of the breakdowns to include in the response. By default only days breakdown is
     *                        included. (optional, default to new ArrayList&lt;&gt;())
     * @param dsp             Optionally include one or more DSP slug names. Omitting this field entirely will result in
     *                        all available DSPs being queried. The results from each DSP will be combined and
     *                        aggregated. To query for data by individual DSPs only, use separate requests providing one
     *                        DSP per request. (optional, default to new ArrayList&lt;&gt;())
     * @param parentRepOwner Label id to filter the result (optional)
     * @param adsAccounts Ads account ids to filter the result (optional)
     * @return DataHealthStatus 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)
     */
    @RequestMapping(value = "/ads/data-health/dsp/status/analytics",
        produces = {"application/json"},
        method = RequestMethod.GET)
    @ParameterScriptAssert(lang = "groovy",
        message = ApplicationConstants.ERROR_MESSAGE_START_END_DATES,
        script = ApplicationConstants.SCRIPT_CHECK_DATES)
    @OnlyDeclaredParams
    default ResponseEntity<AdsDataHealthRangeReport> dataHealthStatusAnalyticsGet(
        @RequestParam(value = "start_date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @RequestParam(value = "end_date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
        @RequestParam(value = "breakdown_group", required = false, defaultValue = "dsp_segment")
            Set<DataHealthBreakdownGroup> breakdownGroups,
        @RequestParam(value = "breakdown", required = false, defaultValue = "days") Set<DataHealthBreakdown> breakdowns,
        @RequestParam(value = "dsp", required = false) Set<String> dsp,
        @RequestParam(value = "parent_rep_owner", required = false) Set<String> parentRepOwner,
        @RequestParam(value = "ads_account", required = false) Set<String> adsAccounts) {
        return getAdsDataHealthStatusAnalytics(Params.builder()
            .startDate(startDate)
            .endDate(endDate)
            .dataHealthBreakdownGroups(breakdownGroups)
            .dataHealthBreakdowns(breakdowns)
            .dsp(CollectionUtils.isEmpty(dsp) ? Collections.emptyList() : new ArrayList<>(dsp))
            .parentRepOwnerKeys(
                CollectionUtils.isEmpty(parentRepOwner) ? Collections.emptyList() : new ArrayList<>(parentRepOwner))
            .adsAccounts(CollectionUtils.isEmpty(adsAccounts) ? Collections.emptySet() : adsAccounts)
            .build());
    }

    ResponseEntity<AdsDataHealthRangeReport> getAdsDataHealthStatusAnalytics(Params params);

    /**
     * GET /ads/campaigns/budgets : Resource route for getting ad campaign budget data.
     *
     * @param campaignId     (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)
     * @return &#x60;AdsCampaignsBudgetItems&#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)
     */
    @RequestMapping(value = "/ads/campaigns/budgets", produces = {"application/json"}, method = RequestMethod.GET)
    @OnlyDeclaredParams
    default ResponseEntity<AdsCampaignsBudgetItems> adsCampaignsBudgets(
        @RequestParam(value = "campaign_id", required = false) Set<String> campaignId,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset) {
        return adsCampaignsBudgetsSearch(Params.builder()
            .campaignIds(campaignId)
            .limit(limit)
            .offset(offset)
            .build());
    }
    ResponseEntity<AdsCampaignsBudgetItems> adsCampaignsBudgetsSearch(Params params);

    /**
     * GET /ads/adsets/budgets : Resource route for getting ad set budget data.
     *
     * @param adSetId        (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)
     * @return &#x60;AdsCampaignsBudgetItems&#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)
     */
    @RequestMapping(value = "/ads/adsets/budgets", produces = {"application/json"}, method = RequestMethod.GET)
    @OnlyDeclaredParams
    default ResponseEntity<AdsSetBudgetItems> adsSetBudgets(
        @RequestParam(value = "ad_set_id", required = false) Set<String> adSetId,
        @Min(0) @Max(MAX_PAGE_SIZE) @RequestParam(value = "limit", required = false) Integer limit,
        @Min(0) @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset) {
        return adsSetBudgetsSearch(Params.builder()
            .adSetIds(adSetId)
            .limit(limit)
            .offset(offset)
            .build());
    }
    ResponseEntity<AdsSetBudgetItems> adsSetBudgetsSearch(Params params);
}
