// Path: src/main/java/org/blackdread/filtersortjooqapi/exception/FilteringApiException.java
// public class FilteringApiException extends BadUsageApiException {
// 
//     public FilteringApiException(final String message) {
//         super(message);
//     }
// }
// 
// Path: src/main/java/org/blackdread/filtersortjooqapi/filter/parser/FilterParser.java
// @FunctionalInterface
// public interface FilterParser<R> {
// 
//     /**
//      * @param value Value to be parsed
//      * @return Value parsed and transform to generic value
//      * @throws RuntimeException When parsing failed if implementation does not swallow exception
//      */
//     @NotNull
//     R parse(final String value);
// 
//     /**
//      * @param value  Value to be parsed
//      * @param parser Custom parser function
//      * @return Value parsed and transform to generic value
//      * @throws FilteringApiException If an error occurred at parsing
//      * @deprecated No sure it is useful, if need to define a new parser then better to create the class and etc
//      */
//     default R parseWithApiException(final String value, final Function<String, R> parser) throws FilteringApiException {
//         try {
//             return parser.apply(value);
//         } catch (Exception e) {
//             throw new FilteringApiException("Could not parse value (" + value + ") to " + this.getClass().getSimpleName());
//         }
//     }
// 
//     /**
//      * @param value Value to be parsed
//      * @return Value parsed and transform to generic value
//      * @throws FilteringApiException If an error occurred at parsing
//      */
//     default R parseWithApiException(final String value) throws FilteringApiException {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             throw new FilteringApiException("Could not parse value (" + value + ") to " + this.getClass().getSimpleName());
//         }
//     }
// 
//     /**
//      * @param value        Value to be parsed
//      * @param defaultValue Default value returned if parse throws an exception
//      * @return Value parsed and transform to generic value
//      */
//     default R parseOrDefault(final String value, final R defaultValue) {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             return defaultValue;
//         }
//     }
// 
//     /**
//      * @param value        Value to be parsed
//      * @param defaultValue Default value returned if parse throws an exception
//      * @return Value parsed and transform to generic value
//      */
//     default R parseOrDefault(final String value, final Supplier<R> defaultValue) {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             return defaultValue.get();
//         }
//     }
// 
//     /**
//      * @return Parser to change string to Integer
//      */
//     static FilterParser<Integer> ofInt() {
//         return FilterParserInteger.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalDate
//      */
//     static FilterParser<LocalDate> ofDate() {
//         return FilterParserDate.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalTime
//      */
//     static FilterParser<LocalTime> ofTime() {
//         return FilterParserTime.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalDateTime
//      */
//     static FilterParser<LocalDateTime> ofDateTime() {
//         return FilterParserDateTime.INSTANCE;
//     }
// 
//     /**
//      * A parser that does not parse. An identity string
//      *
//      * @return A parser that does not parse value
//      */
//     static FilterParser<String> ofIdentity() {
//         return FilterParserIdentity.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to Byte
//      */
//     static FilterParser<Byte> ofByte() {
//         return FilterParserByte.INSTANCE;
//     }
// 
//     // TODO To see if meaningful to have a "fake" parser that returns a value predefined
// //    static <T> FilterParser<T> of(final T value) {
// //        return FilterParse;
// //    }
// 
// }


import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.blackdread.filtersortjooqapi.exception.FilteringApiException;
import org.blackdread.filtersortjooqapi.filter.parser.FilterParser;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
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 java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
    private static List<FilterValue> createFilterValueTrueConditionByKeys(final String... keys) {
        if (keys.length < 1)
            throw new IllegalArgumentException("Cannot have keys length < 1");
        return Arrays.stream(keys)
            .map(key -> Filter.of(key, DSL::trueCondition))
            .collect(Collectors.toList());
    }

    @BeforeEach
    void setUp() {
        filteringJooqImpl1 = new FilteringJooqImpl1();
    }

    @AfterEach
    void teardDown() {

    }

    @ParameterizedTest
    @MethodSource("goodMapConditionAndResult")
    void buildConditions(final Map<String, String> params, final List<FilterValue> filterValues, final Condition expectedCondition) {
        filteringJooqImpl1.getFilterValues().addAll(filterValues);
        final Condition condition = filteringJooqImpl1.buildConditions(params);
        Assertions.assertEquals(expectedCondition, condition);
    }

    @ParameterizedTest
    @MethodSource("incompleteMapOfFilterMultipleKeys")
    void buildConditionsThrowsByDefaultForMissingKeyMultiple(final Map<String, String> params, final List<FilterValue> filterValues) {
        filteringJooqImpl1.getFilterValues().addAll(filterValues);
// Path: src/main/java/org/blackdread/filtersortjooqapi/exception/FilteringApiException.java
// public class FilteringApiException extends BadUsageApiException {
// 
//     public FilteringApiException(final String message) {
//         super(message);
//     }
// }
// 
// Path: src/main/java/org/blackdread/filtersortjooqapi/filter/parser/FilterParser.java
// @FunctionalInterface
// public interface FilterParser<R> {
// 
//     /**
//      * @param value Value to be parsed
//      * @return Value parsed and transform to generic value
//      * @throws RuntimeException When parsing failed if implementation does not swallow exception
//      */
//     @NotNull
//     R parse(final String value);
// 
//     /**
//      * @param value  Value to be parsed
//      * @param parser Custom parser function
//      * @return Value parsed and transform to generic value
//      * @throws FilteringApiException If an error occurred at parsing
//      * @deprecated No sure it is useful, if need to define a new parser then better to create the class and etc
//      */
//     default R parseWithApiException(final String value, final Function<String, R> parser) throws FilteringApiException {
//         try {
//             return parser.apply(value);
//         } catch (Exception e) {
//             throw new FilteringApiException("Could not parse value (" + value + ") to " + this.getClass().getSimpleName());
//         }
//     }
// 
//     /**
//      * @param value Value to be parsed
//      * @return Value parsed and transform to generic value
//      * @throws FilteringApiException If an error occurred at parsing
//      */
//     default R parseWithApiException(final String value) throws FilteringApiException {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             throw new FilteringApiException("Could not parse value (" + value + ") to " + this.getClass().getSimpleName());
//         }
//     }
// 
//     /**
//      * @param value        Value to be parsed
//      * @param defaultValue Default value returned if parse throws an exception
//      * @return Value parsed and transform to generic value
//      */
//     default R parseOrDefault(final String value, final R defaultValue) {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             return defaultValue;
//         }
//     }
// 
//     /**
//      * @param value        Value to be parsed
//      * @param defaultValue Default value returned if parse throws an exception
//      * @return Value parsed and transform to generic value
//      */
//     default R parseOrDefault(final String value, final Supplier<R> defaultValue) {
//         try {
//             return parse(value);
//         } catch (Exception e) {
//             return defaultValue.get();
//         }
//     }
// 
//     /**
//      * @return Parser to change string to Integer
//      */
//     static FilterParser<Integer> ofInt() {
//         return FilterParserInteger.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalDate
//      */
//     static FilterParser<LocalDate> ofDate() {
//         return FilterParserDate.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalTime
//      */
//     static FilterParser<LocalTime> ofTime() {
//         return FilterParserTime.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to LocalDateTime
//      */
//     static FilterParser<LocalDateTime> ofDateTime() {
//         return FilterParserDateTime.INSTANCE;
//     }
// 
//     /**
//      * A parser that does not parse. An identity string
//      *
//      * @return A parser that does not parse value
//      */
//     static FilterParser<String> ofIdentity() {
//         return FilterParserIdentity.INSTANCE;
//     }
// 
//     /**
//      * @return Parser to change string to Byte
//      */
//     static FilterParser<Byte> ofByte() {
//         return FilterParserByte.INSTANCE;
//     }
// 
//     // TODO To see if meaningful to have a "fake" parser that returns a value predefined
// //    static <T> FilterParser<T> of(final T value) {
// //        return FilterParse;
// //    }
// 
// }


// Path: src/test/java/org/blackdread/filtersortjooqapi/filter/FilteringJooqTest.java
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.blackdread.filtersortjooqapi.exception.FilteringApiException;
import org.blackdread.filtersortjooqapi.filter.parser.FilterParser;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
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 java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

    private static List<FilterValue> createFilterValueTrueConditionByKeys(final String... keys) {
        if (keys.length < 1)
            throw new IllegalArgumentException("Cannot have keys length < 1");
        return Arrays.stream(keys)
            .map(key -> Filter.of(key, DSL::trueCondition))
            .collect(Collectors.toList());
    }

    @BeforeEach
    void setUp() {
        filteringJooqImpl1 = new FilteringJooqImpl1();
    }

    @AfterEach
    void teardDown() {

    }

    @ParameterizedTest
    @MethodSource("goodMapConditionAndResult")
    void buildConditions(final Map<String, String> params, final List<FilterValue> filterValues, final Condition expectedCondition) {
        filteringJooqImpl1.getFilterValues().addAll(filterValues);
        final Condition condition = filteringJooqImpl1.buildConditions(params);
        Assertions.assertEquals(expectedCondition, condition);
    }

    @ParameterizedTest
    @MethodSource("incompleteMapOfFilterMultipleKeys")
    void buildConditionsThrowsByDefaultForMissingKeyMultiple(final Map<String, String> params, final List<FilterValue> filterValues) {
        filteringJooqImpl1.getFilterValues().addAll(filterValues);
        Assertions.assertThrows(FilteringApiException.class, () -> filteringJooqImpl1.buildConditions(params));