package io.delphiplatform.api.integration;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.time.LocalDateTime;
import java.util.List;

import io.delphiplatform.api.integration.annotation.BigTable;
import io.delphiplatform.api.integration.annotation.SqlScript;
import io.delphiplatform.api.integration.model.StreamModelModel;
import io.delphiplatform.api.integration.model.StreamsModelModel;
import io.delphiplatform.api.integration.util.MockCurrentDateService;
import io.delphiplatform.api.utils.TestUtils;
import io.delphiplatform.api.v3.model.AggBy;
import io.delphiplatform.api.v3.model.SubsetParam;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * <!-- @formatter:off -->
 * Please UPDATE this doc when logic changes
 *
 * Particular API params set, passed to /streams not only filters data, but it also defines which set of actions is taken during request processing
 * Depending on params combination:
 * - some logic may be applied before BT request is sent;
 * - type of rowkey to request BT with is defined;
 * - way to aggregate BT data is defined;
 *
 * Different params combinations are called "ROUTES" in this document
 *
 * So in below list, each route contains 3 stages:
 * logic    : ... - (optional) some actions performed before BT query
 * rowkey   : ... - BT rowkey used for this route
 * aggregate: ... - aggregation logic, i.e. how to group retrieved BT models (rows) for response (e.g. group by isrc but merge all dates into 1 record)
 *     Notes:
 *     1) Passing 'artist_id' param won't affect grouping - all models always end up with single artistId anyway
 *     2) API param 'group_by' is a straightforward grouping param that just allows to group response data further
 *       e.g. particular route is grouping by isrc and by default merges all dates for single isrc into 1 record. If we then specify group_by=DATE
 *          - single record for that isrc is expanded into multiple.
 *       This param IS NOT TESTED here to avoid complexity, tests inside StreamsIT are sufficient
 *
 * Each route has ist "main" params specified at its top (e.g. "params: playlist_id, isrc")
 * Presence of additional params may affect aggregation behaviour and is described for each route
 *
 * Routes are hierarchical - as they mentioned from top to bottom
 * So to know which route would be taken by API for particular params set - one should scan below routes list from top to bottom and pick
 * 1st route which "main" params are fully present in API request
 *
 * e.g.
 * req with "playlist_id, isrc" will match route "params: playlist_id, isrc"
 * req with "playlist_id, isrc, artist_id" will still match route "params: playlist_id, isrc"
 * req with "playlist_id, artist_id" will match route "params: playlist_id"
 *
 * req with "isrc" will match route "params: isrc"
 * req with "isrc, artist_id" will still match route "params: isrc"
 *
 * and only req with just "artist_id" will match route "params: artist_id"
 *
 *
 * === Routes:
 *
 * == playlist_id routes
 *
 * -----------------
 * Integration test name: getStreams_routes_playlist_playlistIdAndIsrc
 *
 * params: playlist_id, isrc
 * logic    : n/a
 * rowkey   : playlist_isrc_date_day~spotify_01hfS6ReScd18FE4AIykAT~18700801MSSU~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - no effect, just sets ANY passed id into response
 *     isrc        - main route param, always grouping by it
 *     playlist_id - main route param, always grouping by it
 *     subset      - no effect
 *     agg_by      - no effect
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "country_code": "jm",
 *     "isrc": "USSM10800781",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 5
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_playlist_playlistIdAndArtistIdAndAggByIsrc
 *
 * params: playlist_id, artist_id, [agg_by=isrc]
 * logic    : load all isrcs from tracks of this artist, use playlist+isrcs for lookup
 * rowkey   : playlist_isrc_date_day~spotify_01hfS6ReScd18FE4AIykAT~18700801MSSU~2022-05-01
 * (same as for "params: playlist_id, isrc")
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - main route param, always grouping by it
 *     subset      - no effect
 *     agg_by      - fixed at ISRC (if otherwise it would be different route), means that response is always grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "worldwide",
 *     "isrc": "USSM10800781",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 98
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_playlist_playlistIdAndArtistIdAndAggByNull
 *
 * params: playlist_id, artist_id, [agg_by=null]
 * logic    : load additional artistIds by param artistId, use playlist+all artists for lookup
 * rowkey   : artist_playlist_date_day~GRAS_1000013~spotify_01hfS6ReScd18FE4AIykAT~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - main route param, always grouping by it
 *     subset      - no effect
 *     agg_by      - fixed at null (if otherwise it would be different route), means that response is NOT grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "worldwide",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 98
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_playlist_playlistIdAndAggByIsrc
 *
 * params: playlist_id, [agg_by=isrc]
 * logic    : n/a
 * rowkey   : playlist_date_isrc_day~spotify_01hfS6ReScd18FE4AIykAT~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - n/a, if set it would be different route (see above)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - main route param, always grouping by it
 *     subset      - no effect
 *     agg_by      - fixed at ISRC (if otherwise it would be different route), means that response is always grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "country_code": "worldwide",
 *     "isrc": "USSM10800781",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 35112
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_playlist_playlistIdAndAggByNull
 *
 * params: playlist_id, [agg_by=null]
 * logic    : n/a
 * rowkey   : playlist_date_day~spotify_01hfS6ReScd18FE4AIykAT~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - n/a, if set it would be different route (see above)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - main route param, always grouping by it
 *     subset      - no effect
 *     agg_by      - fixed at null (if otherwise it would be different route), means that response is NOT grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "country_code": "worldwide",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 542498
 * }
 *
 *
 * = isrc routes
 *
 * -----------------
 * Integration test name: getStreams_routes_isrc_isrcAndSubsetPlaylists
 *
 * params: isrc, [subset=playlists]
 * logic    : n/a
 * rowkey   : isrc_date_playlist_day~18700801MSSU~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - no effect, just sets ANY passed id into response
 *     isrc        - main route param, always grouping by it
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - fixed at PLAYLISTS (if otherwise it would be different route), means that response is always grouped by playlist_id
 *     agg_by      - no effect
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "country_code": "worldwide",
 *     "isrc": "USSM10800781",
 *     "playlist_id": "spotify_01hfS6ReScd18FE4AIykAT",
 *     "streams": 117264
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_isrc_isrcAndSubsetNull
 *
 * params: isrc, [subset=null]
 * logic    : n/a
 * rowkey   : isrc_date_day~6714821P2ASU~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - no effect, just sets ANY passed id into response
 *     isrc        - main route param, always grouping by it
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - fixed at null (if otherwise it would be different route), means that response is NOT grouped by playlist_id
 *     agg_by      - no effect
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "country_code": "worldwide",
 *     "isrc": "USA2P1284176",
 *     "streams": 502093
 * }
 *
 *
 * = artist_id routes
 *
 * -----------------
 * Integration test name: getStreams_routes_arist_artistIdAndAggByArtist
 *
 * params: artist_id, [agg_by=artist]
 * logic    : n/a
 * rowkey   : artist_date_day~GRAS_1000013~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - not allowed
 *     agg_by      - fixed at ARTIST (if otherwise it would be different route), means that response is always grouped by artist_id
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "worldwide",
 *     "streams": 19630
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetNull
 *
 * params: artist_id, [agg_by=isrc], [subset=null]
 * logic    : load all isrcs from tracks of this artist, use isrcs for lookup
 * rowkey   : isrc_date_day~18700801MSSU~2022-05-01
 * (same as for "params: isrc, [subset=null]")
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - fixed at null (if otherwise it would be different route), means that response is NOT grouped by playlist_id
 *     agg_by      - fixed at ISRC (if otherwise it would be different route), means that response is always grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "worldwide",
 *     "isrc": "USSM10800781",
 *     "streams": 19153
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetPlaylists
 *
 * params: artist_id, [agg_by=isrc], [subset=playlists]
 * logic    : load all isrcs from tracks of this artist, use isrcs for lookup
 * rowkey   : isrc_date_playlist_day~18700801MSSU~2022-01-01
 * (same as for "params: isrc, [subset=playlists]")
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - fixed at PLAYLISTS (if otherwise it would be different route), means that response is always grouped by playlist_id
 *     agg_by      - fixed at ISRC (if otherwise it would be different route), means that response is always grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "us",
 *     "isrc": "USSM10800781",
 *     "playlist_id": "spotify_3SXTF3rOkrKW1bhyWGiJAy",
 *     "streams": 239
 * }
 *
 * -----------------
 * Integration test name: getStreams_routes_arist_artistIdAndAggByNull
 *
 * params: artist_id, [agg_by=null]
 * logic    : load additional artistIds by param artistId, use all artists for lookup
 * rowkey   : artist_date_day~GRAS_1000013~2022-05-01
 * aggregate: if additional params passed
 *     artist_id   - main route param, always grouping by it (though value is same for all entries)
 *     isrc        - n/a, if set it would be different route (see above)
 *     playlist_id - n/a, if set it would be different route (see above)
 *     subset      - no effect
 *     agg_by      - fixed at null (if otherwise it would be different route), means that response is NOT grouped by isrc
 *     group_by    - as expected
 *
 * Base response:
 * {
 *     "artist_id": "GRAS_1000013",
 *     "country_code": "worldwide",
 *     "streams": 19630
 * }
 * <!-- @formatter:on -->
 */
public class StreamsSpecificParamSetsIT extends BaseIT {

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same but with artist_id added to response.
     *      Other params must take no effect
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_playlist_playlistIdAndIsrc.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_playlist_playlistIdAndIsrc() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";
        String isrc1 = "USSM10800781";

        long streams1 = 10L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&isrc=" + isrc1,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getArtistId());
        Assertions.assertEquals(streams1, item1.getStreams());

        //CASE2

        String artistId1 = "GRAS_testArtist";

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&isrc=" + isrc1
                //additional params
                + "&artist_id=" + artistId1
                + "&subset=" + SubsetParam.PLAYLISTS
                + "&agg_by=" + AggBy.ISRC,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(1, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2);

        StreamModelModel item1Copy = TestUtils.deepCopyObject(item1, StreamModelModel.class);
        item1Copy.setArtistId(artistId1);

        Assertions.assertEquals(item1Copy, item2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * pre-BT request logic searches for tracks (isrcs) by provided artistId and its related participantIds,
     * so we create mock mapping between artist1\track1 and participant1\track2 where participant is derived from that artist (see sql mock for details)
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/StreamsSpecificParamSetsIT/common_test_data_set_1.sql")
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_playlist_playlistIdAndArtistIdAndAggByIsrc.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_playlist_playlistIdAndArtistIdAndAggByIsrc() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";
        String artistId1 = "GRAS_1000013";

        String artistIsrc1 = "USSM10800781";
        String artistIsrc2 = "USSM10800782";

        long streamsIsrc1 = 10L;
        long streamsIsrc2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&artist_id=" + artistId1
                + "&agg_by=" + AggBy.ISRC,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(2, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1_1 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()) && artistIsrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_1);
        Assertions.assertNull(item1_1.getTrackId());
        Assertions.assertEquals(streamsIsrc1, item1_1.getStreams());

        StreamModelModel item1_2 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()) && artistIsrc2.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_2);
        Assertions.assertNull(item1_2.getTrackId());
        Assertions.assertEquals(streamsIsrc2, item1_2.getStreams());

        //CASE2

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&artist_id=" + artistId1
                + "&agg_by=" + AggBy.ISRC
                //additional params
                //+ "&isrc="     - n/a, would be a different route
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(2, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2_1 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()) && artistIsrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_1);
        Assertions.assertEquals(item1_1, item2_1);

        StreamModelModel item2_2 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()) && artistIsrc2.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_2);
        Assertions.assertEquals(item1_2, item2_2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * pre-BT request logic searches for additional artistIds by provided artist_id. This allows resolving artistIds by 'participant' id if it
     * has been passed as artist_id param. So we create mock participant records to check this in CASE3
     * (see sql mock for details)
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * CASE3: request by main route params, pass 'participant' id as artist_id param.
     *     So both passed participantId and resolved artistId are searched in BT for streams
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/StreamsSpecificParamSetsIT/common_test_data_set_2.sql")
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_playlist_playlistIdAndArtistIdAndAggByNull.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_playlist_playlistIdAndArtistIdAndAggByNull() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";
        String artistId1 = "GRAS_1000013";

        long streamsArtist1 = 10L;
        long streamsArtist2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&artist_id=" + artistId1
            //+ "&agg_by=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getIsrc());
        Assertions.assertEquals(streamsArtist1, item1.getStreams());

        //CASE2

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&artist_id=" + artistId1
                //+ "&agg_by=" - is not passed, so it is null
                //additional params
                //+ "&isrc="     - n/a, would be a different route
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(1, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId1.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2);
        Assertions.assertEquals(item1, item2);

        //CASE3

        String artistId2 = "GRAS_1000000001";

        ResponseEntity<StreamsModelModel> response3 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&artist_id=" + artistId2
            //+ "&agg_by=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response3.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response3.getStatusCode());
        Assertions.assertEquals(1, response3.getBody().getCount());

        List<StreamModelModel> items3 = response3.getBody().getItems();

        //streams count is merged for both "artists". Response object will contain artist_id equal to what has been passed as artist_id param

        StreamModelModel item3 = items3.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && artistId2.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item3);
        Assertions.assertNull(item3.getTrackId());
        Assertions.assertNull(item3.getIsrc());
        Assertions.assertEquals(streamsArtist1 + streamsArtist2, item3.getStreams());
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_playlist_playlistIdAndAggByIsrc.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_playlist_playlistIdAndAggByIsrc() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";

        String isrc1 = "USSM10800781";
        String isrc2 = "USSM10800782";

        long streamsIsrc1 = 10L;
        long streamsIsrc2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&agg_by=" + AggBy.ISRC,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(2, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1_1 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_1);
        Assertions.assertNull(item1_1.getTrackId());
        Assertions.assertNull(item1_1.getArtistId());
        Assertions.assertEquals(streamsIsrc1, item1_1.getStreams());

        StreamModelModel item1_2 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc2.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_2);
        Assertions.assertNull(item1_2.getTrackId());
        Assertions.assertNull(item1_2.getArtistId());
        Assertions.assertEquals(streamsIsrc2, item1_2.getStreams());

        //CASE2

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                + "&agg_by=" + AggBy.ISRC
                //additional params
                //+ "&isrc="      - n/a, would be a different route
                //+ "&artist_id=" - n/a, would be a different route
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(2, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2_1 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_1);
        Assertions.assertEquals(item1_1, item2_1);

        StreamModelModel item2_2 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()) && isrc2.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_2);
        Assertions.assertEquals(item1_2, item2_2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_playlist_playlistIdAndAggByNull.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_playlist_playlistIdAndAggByNull() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";

        long streams1 = 10L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
            //+ "&agg_by=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getIsrc());
        Assertions.assertNull(item1.getArtistId());
        Assertions.assertEquals(streams1, item1.getStreams());

        //CASE2

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&playlist_id=" + playlistId1
                //+ "&agg_by=" - is not passed, so it is null
                //additional params
                //+ "&isrc="      - n/a, would be a different route
                //+ "&artist_id=" - n/a, would be a different route
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(1, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2 = items2.stream()
            .filter(m -> playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2);
        Assertions.assertEquals(item1, item2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_isrc_isrcAndSubsetPlaylists.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_isrc_isrcAndSubsetPlaylists() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";
        String playlistId2 = "spotify_01hfS6ReScd18FE4AIykAW";

        String isrc1 = "USSM10800781";

        long streamsPlaylist1 = 10L;
        long streamsPlaylist2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&isrc=" + isrc1
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(2, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1_1 = items1.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_1);
        Assertions.assertNull(item1_1.getTrackId());
        Assertions.assertNull(item1_1.getArtistId());
        Assertions.assertEquals(streamsPlaylist1, item1_1.getStreams());

        StreamModelModel item1_2 = items1.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_2);
        Assertions.assertNull(item1_2.getTrackId());
        Assertions.assertNull(item1_2.getArtistId());
        Assertions.assertEquals(streamsPlaylist2, item1_2.getStreams());

        //CASE2

        String artistId1 = "GRAS_testArtist";

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&isrc=" + isrc1
                + "&subset=" + SubsetParam.PLAYLISTS
                //additional params
                + "&artist_id=" + artistId1
                //+ "&playlist_id=" - n/a, would be a different route
                + "&agg_by=" + AggBy.ISRC
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(2, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2_1 = items2.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_1);

        StreamModelModel item1_1Copy = TestUtils.deepCopyObject(item1_1, StreamModelModel.class);
        item1_1Copy.setArtistId(artistId1);

        Assertions.assertEquals(item1_1Copy, item2_1);

        StreamModelModel item2_2 = items2.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2_2);

        StreamModelModel item1_2Copy = TestUtils.deepCopyObject(item1_2, StreamModelModel.class);
        item1_2Copy.setArtistId(artistId1);

        Assertions.assertEquals(item1_2Copy, item2_2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_isrc_isrcAndSubsetNull.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_isrc_isrcAndSubsetNull() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String isrc1 = "USSM10800781";

        long streams1 = 10L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&isrc=" + isrc1
            //+ "&subset=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getPlaylistId());
        Assertions.assertNull(item1.getArtistId());
        Assertions.assertEquals(streams1, item1.getStreams());

        //CASE2

        String artistId1 = "GRAS_testArtist";

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&isrc=" + isrc1
                //+ "&subset=" - is not passed, so it is null
                //additional params
                + "&artist_id=" + artistId1
                //+ "&playlist_id=" - n/a, would be a different route
                + "&agg_by=" + AggBy.ISRC
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(1, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2 = items2.stream()
            .filter(m -> isrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2);

        StreamModelModel item1Copy = TestUtils.deepCopyObject(item1, StreamModelModel.class);
        item1Copy.setArtistId(artistId1);

        Assertions.assertEquals(item1Copy, item2);
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * CASE1: request by main route params
     * <!-- @formatter:on -->
     */
    @Test
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_arist_artistIdAndAggByArtist.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_arist_artistIdAndAggByArtist() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String artistId1 = "GRAS_1000013";

        long streams1 = 10L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId1
                + "&agg_by=" + AggBy.ARTIST
            //additional params - none available
            //+ "&isrc="        - n/a, would be a different route
            //+ "&playlist_id=" - n/a, would be a different route
            //+ "&subset=" - not allowed for agg_by=ARTIST
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getIsrc());
        Assertions.assertNull(item1.getPlaylistId());
        Assertions.assertEquals(streams1, item1.getStreams());
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * pre-BT request logic searches for tracks (isrcs) by provided artistId and its related participantIds,
     * so we create mock mapping between artist1\track1 and participant1\track2 where participant is derived from that artist (see sql mock for details)
     *
     * CASE1: request by main route params
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/StreamsSpecificParamSetsIT/common_test_data_set_1.sql")
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetNull.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetNull() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String artistId1 = "GRAS_1000013";

        String artistIsrc1 = "USSM10800781";
        String artistIsrc2 = "USSM10800782";

        long streamsIsrc1 = 10L;
        long streamsIsrc2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId1
                + "&agg_by=" + AggBy.ISRC,
            //+ "&subset=" - is not passed, so it is null
            //additional params - none available
            //+ "&isrc="        - n/a, would be a different route
            //+ "&playlist_id=" - n/a, would be a different route
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(2, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1_1 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc1.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_1);
        Assertions.assertNull(item1_1.getTrackId());
        Assertions.assertNull(item1_1.getPlaylistId());
        Assertions.assertEquals(streamsIsrc1, item1_1.getStreams());

        StreamModelModel item1_2 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc2.equals(m.getIsrc()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1_2);
        Assertions.assertNull(item1_2.getTrackId());
        Assertions.assertNull(item1_2.getPlaylistId());
        Assertions.assertEquals(streamsIsrc2, item1_2.getStreams());
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * pre-BT request logic searches for tracks (isrcs) by provided artistId and its related participantIds,
     * so we create mock mapping between artist1\track1 and participant1\track2 where participant is derived from that artist (see sql mock for details)
     *
     * CASE1: request by main route params
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/StreamsSpecificParamSetsIT/common_test_data_set_1.sql")
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetPlaylists.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_arist_artistIdAndAggByIsrcAndSubsetPlaylists() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String artistId1 = "GRAS_1000013";

        String artistIsrc1 = "USSM10800781";
        String artistIsrc2 = "USSM10800782";

        String playlistId1 = "spotify_01hfS6ReScd18FE4AIykAT";
        String playlistId2 = "spotify_01hfS6ReScd18FE4AIykAW";

        long streamsIsrc1Playlsit1 = 10L;
        long streamsIsrc1Playlsit2 = 20L;
        long streamsIsrc2Playlsit1 = 30L;
        long streamsIsrc2Playlsit2 = 40L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId1
                + "&agg_by=" + AggBy.ISRC
                + "&subset=" + SubsetParam.PLAYLISTS,
            //additional params - none available
            //+ "&isrc="        - n/a, would be a different route
            //+ "&playlist_id=" - n/a, would be a different route
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(4, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel itemIsrc_1_playlist_1 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc1.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc_1_playlist_1);
        Assertions.assertNull(itemIsrc_1_playlist_1.getTrackId());
        Assertions.assertEquals(streamsIsrc1Playlsit1, itemIsrc_1_playlist_1.getStreams());

        StreamModelModel itemIsrc_1_playlist_2 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc_1_playlist_2);
        Assertions.assertNull(itemIsrc_1_playlist_2.getTrackId());
        Assertions.assertEquals(streamsIsrc1Playlsit2, itemIsrc_1_playlist_2.getStreams());

        StreamModelModel itemIsrc_2_playlist_1 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc2.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc_2_playlist_1);
        Assertions.assertNull(itemIsrc_2_playlist_1.getTrackId());
        Assertions.assertEquals(streamsIsrc2Playlsit1, itemIsrc_2_playlist_1.getStreams());

        StreamModelModel itemIsrc_2_playlist_2 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()) && artistIsrc2.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc_2_playlist_2);
        Assertions.assertNull(itemIsrc_2_playlist_2.getTrackId());
        Assertions.assertEquals(streamsIsrc2Playlsit2, itemIsrc_2_playlist_2.getStreams());
    }

    /**
     * <!-- @formatter:off -->
     * Test particular route - look for route details in class comment, search by test name
     *
     * pre-BT request logic searches for additional artistIds by provided artist_id. This allows resolving artistIds by 'participant' id if it
     * has been passed as artist_id param. So we create mock participant records to check this in CASE3
     * (see sql mock for details)
     *
     * CASE1: request by main route params
     * CASE2: request with additional params. Result must be the same.
     * CASE3: request by main route params, pass 'participant' id as artist_id param.
     *     So both passed participantId and resolved artistId are searched in BT for streams
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/StreamsSpecificParamSetsIT/common_test_data_set_2.sql")
    @BigTable(table = "spotify", json = "/scripts/StreamsSpecificParamSetsIT/BT_spotify_getStreams_routes_arist_artistIdAndAggByNull.json")
    @BigTable(table = "apple_music", json = "/scripts/BT_empty.json")
    @BigTable(table = "amazon_music", json = "/scripts/BT_empty.json")
    public void getStreams_routes_arist_artistIdAndAggByNull() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-01-31T00:00:00"));

        String artistId1 = "GRAS_1000013";

        long streamsArtist1 = 10L;
        long streamsArtist2 = 20L;

        //CASE1

        ResponseEntity<StreamsModelModel> response1 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId1
            //+ "&agg_by=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response1.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response1.getStatusCode());
        Assertions.assertEquals(1, response1.getBody().getCount());

        List<StreamModelModel> items1 = response1.getBody().getItems();

        StreamModelModel item1 = items1.stream()
            .filter(m -> artistId1.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item1);
        Assertions.assertNull(item1.getTrackId());
        Assertions.assertNull(item1.getIsrc());
        Assertions.assertNull(item1.getPlaylistId());
        Assertions.assertEquals(streamsArtist1, item1.getStreams());

        //CASE2

        ResponseEntity<StreamsModelModel> response2 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId1
                //+ "&agg_by=" - is not passed, so it is null
                //additional params
                //+ "&isrc="        - n/a, would be a different route
                //+ "&playlist_id=" - n/a, would be a different route
                + "&subset=" + SubsetParam.PLAYLISTS,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response2.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response2.getStatusCode());
        Assertions.assertEquals(1, response2.getBody().getCount());

        List<StreamModelModel> items2 = response2.getBody().getItems();

        StreamModelModel item2 = items2.stream()
            .filter(m -> artistId1.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item2);
        Assertions.assertEquals(item1, item2);

        //CASE3

        String artistId2 = "GRAS_1000000001";

        ResponseEntity<StreamsModelModel> response3 = apiClient.sendAPIRequest(
            "/v3/streams?"
                + "start_date=" + "2022-12-01"
                + "&end_date=" + "2022-12-31"
                + "&artist_id=" + artistId2
            //+ "&agg_by=" - is not passed, so it is null
            ,
            StreamsModelModel.class
        );

        assertEquals(HttpStatus.OK, response3.getStatusCode());

        Assertions.assertEquals(HttpStatus.OK, response3.getStatusCode());
        Assertions.assertEquals(1, response3.getBody().getCount());

        List<StreamModelModel> items3 = response3.getBody().getItems();

        //streams count is merged for both "artists". Response object will contain artist_id equal to what has been passed as artist_id param

        StreamModelModel item3 = items3.stream()
            .filter(m -> artistId2.equals(m.getArtistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(item3);
        Assertions.assertNull(item3.getTrackId());
        Assertions.assertNull(item3.getIsrc());
        Assertions.assertNull(item3.getPlaylistId());
        Assertions.assertEquals(streamsArtist1 + streamsArtist2, item3.getStreams());
    }
}
