package de.renew.navigator.models;


import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

/**
 * Test class for {@link de.renew.navigator.models.SearchFilter}.
 *
 * @author 0zander
 */
public class SearchFilterTest {
    @ParameterizedTest
    @MethodSource("termArrayProvider")
    public void testConstructor(String[] testingTerms, String[] expectedTerms) {
        // Given
        String name = "name";
        SearchFilter.Type type = SearchFilter.Type.CONTAINS;

        // When
        SearchFilter searchFilter = new SearchFilter(name, type, true, testingTerms);

        // Then
        assertThat(searchFilter.getName()).isEqualTo(name);
        assertThat(searchFilter.getType()).isEqualTo(type);
        assertThat(searchFilter.isCaseSensitive()).isTrue();
        assertThat(searchFilter.getTerms()).containsExactly(expectedTerms);
    }

    private static Stream<Arguments> termArrayProvider() {
        return Stream.of(
            Arguments.of(null, new String[] { }), Arguments.of(new String[] { }, new String[] { }),
            Arguments.of(new String[] { "       " }, new String[] { }),
            Arguments.of(new String[] { "a " }, new String[] { "a" }),
            Arguments.of(new String[] { null, " b " }, new String[] { "b" }),
            Arguments.of(new String[] { " c", "", "a" }, new String[] { "c", "a" }));
    }

    @Test
    public void testIsValid() {
        // Given
        String name = "name";
        SearchFilter.Type type = SearchFilter.Type.ENDS_WITH;
        String[] termsTrue = new String[] { "a" };
        String[] termsFalse = new String[] { };

        // When
        SearchFilter searchFilterTrue = new SearchFilter(name, type, false, termsTrue);
        SearchFilter searchFilterFalse = new SearchFilter(name, type, false, termsFalse);

        // Then
        assertThat(searchFilterTrue.isValid()).isTrue();
        assertThat(searchFilterFalse.isValid()).isFalse();
    }

    /**
     * Parameterized Test method for {@link SearchFilter#match(String)} using a {@link SearchFilter.Type#CONTAINS} filter.
     * This tests the edge cases and case-sensitiveness of the search filter.
     *
     * @param terms which terms the search filter contains
     * @param toCheck which string should be tried to match
     * @param caseSensitive whether matching is case-sensitive
     * @param expectedResult the expected result
     */
    @ParameterizedTest
    @MethodSource("matchEdgeArgumentProvider")
    public void testMatchEdges(
        String[] terms, String toCheck, boolean caseSensitive, boolean expectedResult)
    {
        // When
        SearchFilter searchFilter =
            new SearchFilter("name", SearchFilter.Type.CONTAINS, caseSensitive, terms);

        // Then
        assertThat(searchFilter.match(toCheck)).isEqualTo(expectedResult);
    }

    private static Stream<Arguments> matchEdgeArgumentProvider() {
        return Stream.of(
            Arguments.of(new String[] { }, " ", true, true),
            Arguments.of(new String[] { "a" }, " ", true, true),
            Arguments.of(new String[] { "a" }, null, true, true),
            Arguments.of(new String[] { "a" }, "a", true, true),
            Arguments.of(new String[] { "A" }, "a", true, false),
            Arguments.of(new String[] { "A" }, "a", false, true));
    }

    /**
     * Parameterized Test method for {@link SearchFilter#match(String)} for different types of search filters.
     *
     * @param toCheck which word is used for the testcase
     * @param startsWith whether toCheck starts with "a"
     * @param contains whether toCheck contains "a"
     * @param endsWith whether toCheck ends with "a"
     */
    @ParameterizedTest
    @MethodSource("matchTypesArgumentProvider")
    public void testMatchTypes(
        String toCheck, boolean startsWith, boolean contains, boolean endsWith)
    {
        // Given
        String[] terms = new String[] { "a" };

        // When
        SearchFilter startFilter =
            new SearchFilter("name", SearchFilter.Type.STARTS_WITH, true, terms);
        SearchFilter containsFilter =
            new SearchFilter("name", SearchFilter.Type.CONTAINS, true, terms);
        SearchFilter endFilter = new SearchFilter("name", SearchFilter.Type.ENDS_WITH, true, terms);

        // Then
        assertThat(startFilter.match(toCheck)).isEqualTo(startsWith);
        assertThat(containsFilter.match(toCheck)).isEqualTo(contains);
        assertThat(endFilter.match(toCheck)).isEqualTo(endsWith);
    }

    private static Stream<Arguments> matchTypesArgumentProvider() {
        return Stream.of(
            Arguments.of("ab", true, true, false), Arguments.of("ba", false, true, true),
            Arguments.of("bab", false, true, false), Arguments.of("a", true, true, true),
            Arguments.of("c", false, false, false));
    }
}