package com.atlassian.stash.internal.crowd;

import com.atlassian.crowd.embedded.api.Query;
import com.atlassian.crowd.embedded.api.SearchRestriction;
import com.atlassian.crowd.search.query.entity.restriction.NullRestrictionImpl;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class FilterTest {

    @Test
    public void testFilterWithNoMatches() throws Exception {
        testFilter(0, 999, Collections.<String>emptyList(), Collections.<String>emptyList(), true);
    }

    @Test
    public void testFilterWithSeveralMatches() throws Exception {
        testFilter(0, 999, asList("test1", "test2", "test3"), asList("test1", "test2", "test3"), true);
    }

    @Test
    public void testFilterWithExactMatches() throws Exception {
        testFilter(0, 3, asList("test1", "test2", "test3"), asList("test1", "test2", "test3"), true);
    }

    @Test
    public void testFilterWithLessMatches() throws Exception {
        testFilter(0, 2, asList("test1", "test2", "test3"), asList("test1", "test2"), false);
    }

    @Test
    public void testFilterWithOffsets() throws Exception {
        testFilter(1, 999, asList("test1", "test2", "test3"), asList("test2", "test3"), true);
        testFilter(1, 1, asList("test1", "test2", "test3"), asList("test2"), false);
        testFilter(1, 2, asList("test1", "test2", "test3"), asList("test2", "test3"), true);
        testFilter(2, 2, asList("test1", "test2", "test3"), asList("test3"), true);
    }

    @Test
    public void testFilterVeryLargeResults() throws Exception {
        int fetchSize = 100;
        List<String> entities = repeat("test", 0, fetchSize + 5);

        testFilter(1, 3, entities, asList("test1", "test2", "test3"), false);
        testFilter(fetchSize + 1, 2, entities, repeat("test", fetchSize + 1, 2), false);
        testFilter(fetchSize + 1, 999, entities, repeat("test", fetchSize + 1, 4), true);
        testFilter(fetchSize + 1, 3, entities, repeat("test", fetchSize + 1, 3), false);
        testFilter(fetchSize + 1, 4, entities, repeat("test", fetchSize + 1, 4), true);
    }

    @SuppressWarnings("unchecked")
    private void testFilter(int start, int limit, final List<String> entities, List<String> expectedResult, boolean expectedLastPage) {
        PageRequestImpl pageRequest = new PageRequestImpl(start, limit);
        RiotPolice service = mock(RiotPolice.class);
        Filter<String> filter = idFilter(service);
        when(service.search(any(Query.class))).thenAnswer(new Answer<Iterable<?>>() {
            @Override
            public Iterable<?> answer(InvocationOnMock invocation) throws Throwable {
                Query query = (Query) invocation.getArguments()[0];
                int start = query.getStartIndex();
                int end = start + query.getMaxResults();
                return ImmutableList.copyOf(entities.subList(start, Math.min(end, entities.size())));
            }
        });

        Page<String> page = filter.scan(pageRequest, "group");

        assertEquals(pageRequest.getStart(), page.getStart());
        assertEquals(pageRequest.getLimit(), page.getLimit());
        assertEquals(expectedResult.size(), page.getSize());
        assertEquals(expectedLastPage, page.getIsLastPage());
        assertEquals(Iterables.toString(expectedResult), Iterables.toString(page.getValues()));
    }


    private List<String> repeat(String prefix, int offset, int count) {
        List<String> list = new LinkedList<String>();
        for (int i = offset; i < offset + count; i++) {
            list.add(prefix + i);
        }
        return list;
    }

    private Filter<String> idFilter(RiotPolice service) {
        return new Filter<String>(service) {
            @Override
            Query<String> getQuery(String group, int start, int limit) {
                return mockQuery(start, limit, String.class);
            }

            @Override
            boolean isMatch(String entity) {
                return true;
            }
        };
    }

    private <T> Query<T> mockQuery(final int start, final int limit, final Class<T> returnType) {
        return new Query<T>() {
            @Override
            public int getStartIndex() {
                return start;
            }

            @Override
            public int getMaxResults() {
                return limit;
            }

            @Override
            public Class<T> getReturnType() {
                return returnType;
            }

            @Override
            public SearchRestriction getSearchRestriction() {
                return NullRestrictionImpl.INSTANCE;
            }
        };
    }
}
