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.PublicPlaylistTrackPreviousPositionModel;
import io.delphiplatform.api.integration.model.PublicPlaylistTrackPreviousPositionsModel;
import io.delphiplatform.api.integration.util.MockCurrentDateService;

/**
 * Details:
 * <p>
 * Track lifetime record isrc1-playlist1 is considered 'current' if this record has been present in the latest data update
 * (fact_public_spotify_playlist_track_lifetime.latest_date_time + 1min for isrc1-playlist1 is equal or greater than
 * fact_public_playlist_lifetime.playlist_updated_at_date_time for playlist1)
 */
public class TrackPositionsPlaylistsPreviousPublicIT extends BaseIT {

    /**
     * <!-- @formatter:off -->
     * Check that methods returns only 'previous' playlists positions and ignores 'current' ones
     * (see class comment for logic details)
     *
     * e.g. we request isrcs USQX91901111 and USQX91901112.
     * We have lifetime records with these isrcs within 2 playlists:
     * (isrc, playlistID, latest_date_time)
     * USQX91901111, spotify_1, 13:30:00  --USQX91901111 is current in spotify_1
     * USQX91901113, spotify_2, 13:30:00  --USQX91901113 is current in spotify_2
     *
     * USQX91901111, spotify_2, 13:10:00  --USQX91901111 is previous in spotify_2
     * USQX91901112, spotify_2, 13:10:00  --USQX91901112 is previous in spotify_2
     *
     * USQX91901111 is current for 'spotify_1', but it is NOT current (thus 'previous') for 'spotify_2',
     *  so record USQX91901111-spotify_2 is returned
     * USQX91901112 is also previous for 'spotify_2' so record USQX91901112-spotify_2 is returned
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/TrackPositionsPlaylistsPreviousPublicIT/getPublicTrackPositionsPlaylistsPrevious_mainFlow_spotify.sql")
    public void getPublicTrackPositionsPlaylistsPrevious_mainFlow_spotify() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-03-20T00:00:00"));

        String isrc1 = "USQX91901111";
        String isrc2 = "USQX91901112";

        String playlistId2 = "spotify_2";

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + String.format("%s,%s", isrc1, isrc2),
            PublicPlaylistTrackPreviousPositionsModel.class
        );

        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());

        //2 items expected - "USQX91901111 / spotify_2", "USQX91901112 / spotify_2"

        Assertions.assertEquals(2, response.getBody().getCount());

        List<PublicPlaylistTrackPreviousPositionModel> previousPositions = response.getBody().getItems();

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist2 = previousPositions.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist2);

        Assertions.assertEquals(4, itemIsrc1Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc1Playlist2.getEarliestPositionDateTime());

        PublicPlaylistTrackPreviousPositionModel itemIsrc2Playlist2 = previousPositions.stream()
            .filter(m -> isrc2.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc2Playlist2);

        Assertions.assertEquals(4, itemIsrc2Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc2Playlist2.getEarliestPositionDateTime());
    }

    /**
     * this test is similar to getPublicTrackPositionsPlaylistsPrevious_mainFlow_spotify but for apple
     */
    @Test
    @SqlScript(scripts = "/scripts/TrackPositionsPlaylistsPreviousPublicIT/getPublicTrackPositionsPlaylistsPrevious_mainFlow_apple.sql")
    public void getPublicTrackPositionsPlaylistsPrevious_mainFlow_apple() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-03-20T00:00:00"));

        String isrc1 = "USQX91901111";
        String isrc2 = "USQX91901112";

        String playlistId2 = "apple_2";

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + String.format("%s,%s", isrc1, isrc2),
            PublicPlaylistTrackPreviousPositionsModel.class
        );

        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());

        //2 items expected - "USQX91901111 / apple_2", "USQX91901112 / apple_2"

        Assertions.assertEquals(2, response.getBody().getCount());

        List<PublicPlaylistTrackPreviousPositionModel> previousPositions = response.getBody().getItems();

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist2 = previousPositions.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist2);

        Assertions.assertEquals(4, itemIsrc1Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc1Playlist2.getEarliestPositionDateTime());

        PublicPlaylistTrackPreviousPositionModel itemIsrc2Playlist2 = previousPositions.stream()
            .filter(m -> isrc2.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc2Playlist2);

        Assertions.assertEquals(4, itemIsrc2Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc2Playlist2.getEarliestPositionDateTime());
    }

    /**
     * <!-- @formatter:off -->
     * Check that methods returns only 'previous' playlists positions and ignores 'current' ones
     * (see class comment for logic details)
     *
     * Test that track lifetime record is considered 'current' (and thus NOT returned) even if its latest_date_time
     * is significantly later than playlist's playlist_updated_at_date_time
     * This would mean that this particular track lifetime record itself had been updated once more after 'global' playlist update happened
     * (which updated timestamp for both tables)
     *
     * e.g. we have lifetime records with 3 isrcs:
     * (isrc, playlistID, latest_date_time)
     * USQX91901113, spotify_1, 13:30:00
     * USQX91901112, spotify_1, 13:20:00
     * USQX91901111, spotify_1, 13:10:00
     *
     * and fact_public_playlist_lifetime record for this playlist:
     * (playlist_id, playlist_updated_at_date_time)
     * spotify_1, 13:20:00
     *
     * USQX91901112 has equal timestamps values between 2 tables, so it is 'current'.
     * USQX91901113 has latest_date_time even later than playlist_updated_at_date_time, so is also 'current'
     * USQX91901111 has latest_date_time before playlist_updated_at_date_time so it is 'previous' and is returned
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/TrackPositionsPlaylistsPreviousPublicIT/getPublicTrackPositionsPlaylistsPrevious_mainFlow_playlistLifetimeHasOlderTimestamp.sql")
    public void getPublicTrackPositionsPlaylistsPrevious_mainFlow_playlistLifetimeHasOlderTimestamp() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-03-20T00:00:00"));

        String isrc1 = "USQX91901111";
        String isrc2 = "USQX91901112";
        String isrc3 = "USQX91901113";

        String playlistId1 = "spotify_1";

        //CASE 1: request USQX91901113

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response1 = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + isrc3,
            PublicPlaylistTrackPreviousPositionsModel.class
        );

        //0 items expected

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

        //CASE 2: request USQX91901112

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response2 = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + isrc2,
            PublicPlaylistTrackPreviousPositionsModel.class
        );

        //0 items expected

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

        //CASE 3: request USQX91901111

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response3 = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + isrc1,
            PublicPlaylistTrackPreviousPositionsModel.class
        );

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

        //1 item expected - "USQX91901111 / spotify_1"

        Assertions.assertEquals(1, response3.getBody().getCount());

        List<PublicPlaylistTrackPreviousPositionModel> previousPositions = response3.getBody().getItems();

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist1 = previousPositions.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist1);

        Assertions.assertEquals(4, itemIsrc1Playlist1.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc1Playlist1.getEarliestPositionDateTime());
    }

    /**
     * <!-- @formatter:off -->
     * Check that method does not return position that is 'previous' but is considered related to other isrc which is 'current' in same playlist
     * (see class comment for logic details)
     *
     * e.g. we have following lifetime records:
     * (isrc, playlistID, latest_date_time)
     * USQX91901111, spotify_1, 13:30:00  --USQX91901111 is current in spotify_1
     * USQX91901112, spotify_1, 13:10:00  --USQX91901112 is previous in spotify_1
     *
     * USQX91901111 is current for 'spotify_1' so if we request this isrc - it won't be returned
     * USQX91901112 is previous for 'spotify_1' so if we request just this isrc - record USQX91901112-spotify_1 is returned
     *
     * but if we request both isrcs USQX91901111,USQX91901112 - they are processed as 'related' and since one of them is current
     *  in playlist spotify_1 - this means any other isrc within this playlist is also considered 'current' and won't be returned.
     * So response is empty
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/TrackPositionsPlaylistsPreviousPublicIT/getPublicTrackPositionsPlaylistsPrevious_relatedIsrcs.sql")
    public void getPublicTrackPositionsPlaylistsPrevious_relatedIsrcs() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-03-20T00:00:00"));

        String isrc1 = "USQX91901111";
        String isrc2 = "USQX91901112";

        String playlistId1 = "spotify_1";

        //CASE 1: request only USQX91901112

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response1 = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + String.format("%s", isrc2),
            PublicPlaylistTrackPreviousPositionsModel.class
        );

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

        Assertions.assertEquals(1, response1.getBody().getCount());

        List<PublicPlaylistTrackPreviousPositionModel> previousPositions = response1.getBody().getItems();

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist2 = previousPositions.stream()
            .filter(m -> isrc2.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist2);

        Assertions.assertEquals(4, itemIsrc1Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc1Playlist2.getEarliestPositionDateTime());

        //CASE 2: request both USQX91901111,USQX91901112

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response2 = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + String.format("%s,%s", isrc1, isrc2),
            PublicPlaylistTrackPreviousPositionsModel.class
        );

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

        Assertions.assertEquals(0, response2.getBody().getCount());
    }

    /**
     * <!-- @formatter:off -->
     * Given ISRC USQX91901111 with two previous playlists (spotify_1, spotify_2)
     * When send request for previous playlists and include streams_for_period
     * Then get two previous playlists with their streaming information
     * (see class comment for logic details)
     *
     * <!-- @formatter:on -->
     */
    @Test
    @SqlScript(scripts = "/scripts/TrackPositionsPlaylistsPreviousPublicIT/getPublicTrackPositionsPlaylistsPrevious_mainFlowWithStreams_spotify.sql")
    @BigTable(table = "spotify", json =
        "/scripts/TrackPositionsPlaylistsPreviousPublicIT/BT_getPublicTrackPositionsPlaylistsPrevious_mainFlowWithStreams_spotify.json")
    public void getPublicTrackPositionsPlaylistsPrevious_mainFlowWithStreams_spotify() {
        MockCurrentDateService.setMockDateTime(LocalDateTime.parse("2023-03-20T00:00:00"));

        String isrc1 = "USQX91901111";

        String playlistId1 = "spotify_1";
        String playlistId2 = "spotify_2";

        ResponseEntity<PublicPlaylistTrackPreviousPositionsModel> response = apiClient.sendAPIRequest(
            "/v3/public/track-positions/previous/playlists?"
                + "isrc=" + String.format("%s", isrc1)
                + "&include=streams_for_period"
                + "&streams_country_code=worldwide,local,ca",
            PublicPlaylistTrackPreviousPositionsModel.class
        );

        Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());

        //2 items expected - "USQX91901111 / spotify_1", "USQX91901111 / spotify_2"

        Assertions.assertEquals(2, response.getBody().getCount());

        List<PublicPlaylistTrackPreviousPositionModel> previousPositions = response.getBody().getItems();

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist1 = previousPositions.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId1.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist1);

        Assertions.assertEquals(4, itemIsrc1Playlist1.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:40:00"), itemIsrc1Playlist1.getEarliestPositionDateTime());

        Assertions.assertEquals(3, itemIsrc1Playlist1.getStreamingInfo().size());

        System.out.println("Items are " + itemIsrc1Playlist1.getStreamingInfo().keySet());
        System.out.println("Items are " + itemIsrc1Playlist1.getStreamingInfo());

        Assertions.assertNotNull(itemIsrc1Playlist1.getStreamingInfo().get("ca"));
        Assertions.assertNotNull(itemIsrc1Playlist1.getStreamingInfo().get("local"));
        Assertions.assertNotNull(itemIsrc1Playlist1.getStreamingInfo().get("worldwide"));
        Assertions.assertEquals(100L, itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor1Day().getPlaylist());
        Assertions.assertEquals(200L, itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor7Days().getPlaylist());
        Assertions.assertEquals(300L, itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor14Days().getPlaylist());

        Assertions.assertNull(itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor1Day().getIsrcInPlaylist());
        Assertions.assertNull(itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor7Days().getIsrcInPlaylist());
        Assertions.assertNull(itemIsrc1Playlist1.getStreamingInfo().get("worldwide").getPeriodFor14Days().getIsrcInPlaylist());

        Assertions.assertEquals(10L, itemIsrc1Playlist1.getStreamingInfo().get("local").getPeriodFor1Day().getPlaylist());
        Assertions.assertEquals(20L, itemIsrc1Playlist1.getStreamingInfo().get("local").getPeriodFor7Days().getPlaylist());
        Assertions.assertEquals(30L, itemIsrc1Playlist1.getStreamingInfo().get("local").getPeriodFor14Days().getPlaylist());

        Assertions.assertEquals(20L, itemIsrc1Playlist1.getStreamingInfo().get("ca").getPeriodFor1Day().getPlaylist());
        Assertions.assertEquals(40L, itemIsrc1Playlist1.getStreamingInfo().get("ca").getPeriodFor7Days().getPlaylist());
        Assertions.assertEquals(60L, itemIsrc1Playlist1.getStreamingInfo().get("ca").getPeriodFor14Days().getPlaylist());

        PublicPlaylistTrackPreviousPositionModel itemIsrc1Playlist2 = previousPositions.stream()
            .filter(m -> isrc1.equals(m.getIsrc()) && playlistId2.equals(m.getPlaylistId()))
            .findFirst().orElse(null);

        Assertions.assertNotNull(itemIsrc1Playlist2);

        Assertions.assertEquals(4, itemIsrc1Playlist2.getNumDaysOn());
        Assertions.assertEquals(LocalDateTime.parse("2023-03-01T12:00:00"), itemIsrc1Playlist2.getEarliestPositionDateTime());

        Assertions.assertEquals(3, itemIsrc1Playlist2.getStreamingInfo().size());

        Assertions.assertNull(itemIsrc1Playlist2.getStreamingInfo().get("ca"));
        Assertions.assertNull(itemIsrc1Playlist2.getStreamingInfo().get("local"));
        Assertions.assertNotNull(itemIsrc1Playlist2.getStreamingInfo().get("worldwide"));

        Assertions.assertEquals(100L, itemIsrc1Playlist2.getStreamingInfo().get("worldwide").getPeriodFor1Day().getPlaylist());
        Assertions.assertEquals(200L, itemIsrc1Playlist2.getStreamingInfo().get("worldwide").getPeriodFor7Days().getPlaylist());
        Assertions.assertEquals(300L, itemIsrc1Playlist2.getStreamingInfo().get("worldwide").getPeriodFor14Days().getPlaylist());
    }

}
