package io.delphiplatform.api.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openapitools.jackson.nullable.JsonNullable;

import java.math.BigDecimal;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.delphiplatform.api.util.model.ModelUtils;
import io.delphiplatform.api.v3.model.video.YouTubeSummaryItem;

class ModelUtilsTest {

    private static final double DELTA = 0.001;

    @Test
    void createUri_invalidUri() {
        URI createdUri = ModelUtils.createUri("::corrupted_uri");
        Assertions.assertNull(createdUri);
    }

    @Test
    void createUri_validUri() {
        URI createdUri = ModelUtils.createUri("http://localhost:8080");
        Assertions.assertNotNull(createdUri);
    }

    @Test
    void weightedAverage_generalExample() {
        List<Pair<Long, Float>> items = List.of(
            Pair.of(1000L, 0.25F),
            Pair.of(1200L, 0.30F),
            Pair.of(1400L, 0.10F),
            Pair.of(1600L, 0.05F),
            Pair.of(1800L, 0.12F),
            Pair.of(2000L, 0.03F),
            Pair.of(2200L, 0.02F),
            Pair.of(2400L, 0.15F),
            Pair.of(2600L, 0.20F)
        );
        float weightedAverage = ModelUtils.weightedAverage(items, Pair::getLeft, Pair::getRight);
        Assertions.assertEquals(0.1253, weightedAverage, DELTA);
    }

    @Test
    void weightedAverage_zeroTotalIgnored() {
        List<Pair<Long, Float>> items = List.of(
            Pair.of(1000L, 0.25F),
            Pair.of(1200L, 0.30F),
            Pair.of(1400L, 0.10F),
            Pair.of(1600L, 0.05F),
            Pair.of(1800L, 0.12F),
            Pair.of(2000L, 0.03F),
            Pair.of(2200L, 0.02F),
            Pair.of(2400L, 0.15F),
            Pair.of(2600L, 0.20F),

            Pair.of(0L, 0.20F),
            Pair.of(0L, 0.40F),
            Pair.of(0L, 0.50F),
            Pair.of(0L, 0.10F)
        );
        float weightedAverage = ModelUtils.weightedAverage(items, Pair::getLeft, Pair::getRight);
        Assertions.assertEquals(0.1253, weightedAverage, DELTA);
    }

    @Test
    void weightedAverage_allDailyTotalZero() {
        List<Pair<Long, Float>> items = List.of(
            Pair.of(0L, 0.20F),
            Pair.of(0L, 0.40F),
            Pair.of(0L, 0.50F),
            Pair.of(0L, 0.10F)
        );
        float weightedAverage = ModelUtils.weightedAverage(items, Pair::getLeft, Pair::getRight);
        Assertions.assertEquals(0, weightedAverage, DELTA);
    }

    @Test
    void weightedAverage_durationSeconds() {
        List<YouTubeSummaryItem> items = List.of(
            createItem(100L, 1f),
            createItem(1L, 100f)
        );
        float weightedAverage = ModelUtils.weightedAverage(items,
            item -> item.getMetrics().getViews().orElse(0L),
            item -> item.getMetrics().getAverageViewDurationSeconds().orElse(0F));
        Assertions.assertEquals(1.98, weightedAverage, DELTA);
    }

    @Test
    void toCurrencyHolder_simpleInput() throws JsonProcessingException {
        String currencyInfo = new ObjectMapper().writeValueAsString(Map.of("USD", BigDecimal.valueOf(100.489)));

        Map<String, BigDecimal> stringBigDecimalMap = ModelUtils.toCurrencies(currencyInfo);

        Assertions.assertEquals(1, stringBigDecimalMap.size());
        Assertions.assertEquals(BigDecimal.class, stringBigDecimalMap.get("USD").getClass());
        Assertions.assertEquals(BigDecimal.valueOf(100.489), stringBigDecimalMap.get("USD"));
    }

    @Test
    void toCurrencyHolder_nullCalculated() throws JsonProcessingException {
        Map<String, Object> value = new HashMap<>();
        value.put("USD", null);
        String currencyInfo = new ObjectMapper().writeValueAsString(value);

        Map<String, BigDecimal> stringBigDecimalMap = ModelUtils.toCurrencies(currencyInfo);

        Assertions.assertEquals(0, stringBigDecimalMap.size());
    }

    @Test
    void cutFullAmazonPlaylistIdsToBasePart() {
        //test full amazon playlistIds are cut to base playlistIds
        List<String> fullAmazonPlaylistIds = List.of("amazon_123_us", "amazon_4_ca");
        List<String> cutFullAmazonPlaylistIds = ModelUtils.cutFullAmazonPlaylistIdsToBasePart(fullAmazonPlaylistIds)
            .orElse(null);

        Assertions.assertNotNull(cutFullAmazonPlaylistIds);
        Assertions.assertEquals(2, cutFullAmazonPlaylistIds.size());

        Assertions.assertTrue(cutFullAmazonPlaylistIds.contains("amazon_123"));
        Assertions.assertTrue(cutFullAmazonPlaylistIds.contains("amazon_4"));

        //test base amazon playlistIds are not processed and returned as is
        List<String> baseAmazonPlaylistIds = List.of("amazon_123", "amazon_4");
        List<String> basePlaylistIdsProcessingResult = ModelUtils.cutFullAmazonPlaylistIdsToBasePart(baseAmazonPlaylistIds)
            .orElse(null);

        Assertions.assertNotNull(basePlaylistIdsProcessingResult);
        Assertions.assertEquals(2, basePlaylistIdsProcessingResult.size());

        Assertions.assertTrue(basePlaylistIdsProcessingResult.contains("amazon_123"));
        Assertions.assertTrue(basePlaylistIdsProcessingResult.contains("amazon_4"));

        //test NON amazon playlistIds are not processed even for similar format and returned as is
        List<String> nonAmazonPlaylistIds = List.of("spotify_123_us");
        List<String> nonAmazonPlaylistIdsProcessingResult = ModelUtils.cutFullAmazonPlaylistIdsToBasePart(nonAmazonPlaylistIds)
            .orElse(null);

        Assertions.assertNotNull(nonAmazonPlaylistIdsProcessingResult);
        Assertions.assertEquals(1, nonAmazonPlaylistIdsProcessingResult.size());

        Assertions.assertTrue(nonAmazonPlaylistIdsProcessingResult.contains("spotify_123_us"));
    }

    @Test
    void filteredPlaylistByDsps() {
        List<String> playlistIds = List.of("spotify_id1", "apple_pl.id2", "amazon_123_us");
        List<String> restrictedPlaylistIds = ModelUtils.filterPlaylistsByDsp(playlistIds, null);
        Assertions.assertEquals(restrictedPlaylistIds.size(), playlistIds.size());

        restrictedPlaylistIds = ModelUtils.filterPlaylistsByDsp(playlistIds, Collections.emptyList());
        Assertions.assertEquals(restrictedPlaylistIds.size(), playlistIds.size());

        restrictedPlaylistIds = ModelUtils.filterPlaylistsByDsp(playlistIds, Set.of("apple", "amazon"));
        Assertions.assertEquals(restrictedPlaylistIds.size(), 2);
        Assertions.assertTrue(restrictedPlaylistIds.containsAll(List.of("apple_pl.id2", "amazon_123_us")));

        restrictedPlaylistIds = ModelUtils.filterPlaylistsByDsp(playlistIds, Set.of("spotify"));
        Assertions.assertEquals(restrictedPlaylistIds.size(), 1);
        Assertions.assertTrue(restrictedPlaylistIds.contains("spotify_id1"));
    }

    private YouTubeSummaryItem createItem(long views, float averageDurationSeconds) {
        YouTubeSummaryItem youTubeSummaryItem = new YouTubeSummaryItem();
        youTubeSummaryItem.getMetrics().setViews(JsonNullable.of(views));
        youTubeSummaryItem.getMetrics().setAverageViewDurationSeconds(JsonNullable.of(averageDurationSeconds));
        return youTubeSummaryItem;
    }

}
