package com.atlassian.bitbucket.internal.mirroring.mirror.client;

import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.internal.mirroring.mirror.rest.cloud.RestCloudPage;
import com.atlassian.bitbucket.mirroring.mirror.NoSuchUpstreamException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;

public class CloudEntityIteratorTest {
    private static final int PAGE_SIZE = 2;

    @SuppressWarnings("unchecked")
    @Test
    public void testEmptySinglePage() throws Exception {
        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = iteratorForPagesOf(PAGE_SIZE);

        assertHasNext(entityIterator, false);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSinglePage() throws Exception {
        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = iteratorForPagesOf(
                PAGE_SIZE, Collections.emptyMap(), Collections.emptyMap());

        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, false);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testMultiplePages() throws Exception {
        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = iteratorForPagesOf(
                PAGE_SIZE, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());

        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, true);
        assertHasNext(entityIterator, false);
    }

    @Test(expected = NoSuchUpstreamException.class)
    public void testInitialPageFailure() throws Exception {
        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = new CloudPageIterable.CloudEntityIterator<>(
                () -> {
                    throw new NoSuchUpstreamException(new KeyedMessage("", "", ""));
                },
                (nextUrl) -> {
                    throw new NullPointerException();
                });
    }

    @SuppressWarnings("unchecked")
    @Test(expected = NoSuchUpstreamException.class)
    public void testSecondPageFailure() throws Exception {
        List<List<Map<String, ?>>> valuePages =
                partition(PAGE_SIZE, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());

        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = new CloudPageIterable.CloudEntityIterator<>(
                () -> newPage(0, valuePages),
                (nextUrl) -> {
                    throw new NoSuchUpstreamException(new KeyedMessage("", "", ""));
                });

        // pages are retrieved one ahead so the second hasNext() will trigger it
        entityIterator.hasNext();
    }

    @SuppressWarnings("unchecked")
    @Test(expected = NoSuchUpstreamException.class)
    public void testLastPageFailure() throws Exception {
        List<List<Map<String, ?>>> valuePages =
                partition(PAGE_SIZE,
                        Collections.emptyMap(), Collections.emptyMap(),
                        Collections.emptyMap(), Collections.emptyMap(),
                        Collections.emptyMap(), Collections.emptyMap());

        CloudPageIterable.CloudEntityIterator<Map<String, ?>> entityIterator = new CloudPageIterable.CloudEntityIterator<>(
                () -> newPage(0, valuePages),
                (nextUrl) -> {
                    int index = Integer.parseInt(nextUrl);
                    if (index > 1) {
                        throw new NoSuchUpstreamException(new KeyedMessage("", "", ""));
                    } else {
                        return newPage(index, valuePages);
                    }
                });

        try {
            assertTrue(entityIterator.hasNext());
            assertNotNull(entityIterator.next());
            assertTrue(entityIterator.hasNext());
            assertNotNull(entityIterator.next());
        } catch (Exception e) {
            fail("Initial page retrieval should have succeeded");
        }

        // pages are retrieved one ahead so the third hasNext() will trigger it
        entityIterator.hasNext();
    }

    private void assertHasNext(CloudPageIterable.CloudEntityIterator<Map<String, ?>> iterator, boolean hasNext) {
        if (hasNext) {
            assertTrue(iterator.hasNext());
            Map<String, ?> entity = iterator.next();
            assertNotNull(entity);
        } else {
            assertFalse(iterator.hasNext());
        }
    }

    @SuppressWarnings("unchecked")
    private CloudPageIterable.CloudEntityIterator<Map<String, ?>> iteratorForPagesOf(int pageSize, Map<String, ?>... values) {
        List<List<Map<String, ?>>> valuePages = partition(pageSize, values);

        return new CloudPageIterable.CloudEntityIterator<>(
                () -> newPage(0, valuePages),
                (nextUrl) -> newPage(Integer.valueOf(nextUrl), valuePages));
    }

    private List<List<Map<String, ?>>> partition(int pageSize, Map<String, ?>... values) {
        return values.length == 0
                    ? Collections.singletonList(Arrays.asList(values))
                    : Lists.partition(Arrays.asList(values), pageSize);
    }

    private RestCloudPage<Map<String, ?>> newPage(int index, List<List<Map<String, ?>>> valuePages) {
        ImmutableMap.Builder<String, Object> valueBuilder = ImmutableMap.<String, Object>builder().put("values", valuePages.get(index));
        String next = (index + 1) < valuePages.size() ? String.valueOf(index + 1) : null;
        if (next != null) {
            valueBuilder.put("next", next);
        }
        return new RestCloudPage<>(valueBuilder.build(), (item) -> (Map) item);
    }
}
