package com.oguerreiro.resilient.service.criteria;

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

import java.util.function.BiFunction;
import java.util.function.Function;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.Test;

class VariableCriteriaTest {

    @Test
    void newVariableCriteriaHasAllFiltersNullTest() {
        var variableCriteria = new VariableCriteria();
        assertThat(variableCriteria).is(criteriaFiltersAre(filter -> filter == null));
    }

    @Test
    void variableCriteriaFluentMethodsCreatesFiltersTest() {
        var variableCriteria = new VariableCriteria();

        setAllFilters(variableCriteria);

        assertThat(variableCriteria).is(criteriaFiltersAre(filter -> filter != null));
    }

    @Test
    void variableCriteriaCopyCreatesNullFilterTest() {
        var variableCriteria = new VariableCriteria();
        var copy = variableCriteria.copy();

        assertThat(variableCriteria).satisfies(
            criteria ->
                assertThat(criteria).is(
                    copyFiltersAre(copy, (a, b) -> (a == null || a instanceof Boolean) ? a == b : (a != b && a.equals(b)))
                ),
            criteria -> assertThat(criteria).isEqualTo(copy),
            criteria -> assertThat(criteria).hasSameHashCodeAs(copy)
        );

        assertThat(copy).satisfies(
            criteria -> assertThat(criteria).is(criteriaFiltersAre(filter -> filter == null)),
            criteria -> assertThat(criteria).isEqualTo(variableCriteria)
        );
    }

    @Test
    void variableCriteriaCopyDuplicatesEveryExistingFilterTest() {
        var variableCriteria = new VariableCriteria();
        setAllFilters(variableCriteria);

        var copy = variableCriteria.copy();

        assertThat(variableCriteria).satisfies(
            criteria ->
                assertThat(criteria).is(
                    copyFiltersAre(copy, (a, b) -> (a == null || a instanceof Boolean) ? a == b : (a != b && a.equals(b)))
                ),
            criteria -> assertThat(criteria).isEqualTo(copy),
            criteria -> assertThat(criteria).hasSameHashCodeAs(copy)
        );

        assertThat(copy).satisfies(
            criteria -> assertThat(criteria).is(criteriaFiltersAre(filter -> filter != null)),
            criteria -> assertThat(criteria).isEqualTo(variableCriteria)
        );
    }

    @Test
    void toStringVerifier() {
        var variableCriteria = new VariableCriteria();

        assertThat(variableCriteria).hasToString("VariableCriteria{}");
    }

    private static void setAllFilters(VariableCriteria variableCriteria) {
        variableCriteria.id();
        variableCriteria.code();
        variableCriteria.variableIndex();
        variableCriteria.name();
        variableCriteria.description();
        variableCriteria.input();
        variableCriteria.inputMode();
        variableCriteria.output();
        variableCriteria.outputFormula();
        variableCriteria.version();
        variableCriteria.variableUnitsId();
        variableCriteria.baseUnitId();
        variableCriteria.variableScopeId();
        variableCriteria.variableCategoryId();
        variableCriteria.distinct();
    }

    private static Condition<VariableCriteria> criteriaFiltersAre(Function<Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId()) &&
                condition.apply(criteria.getCode()) &&
                condition.apply(criteria.getVariableIndex()) &&
                condition.apply(criteria.getName()) &&
                condition.apply(criteria.getDescription()) &&
                condition.apply(criteria.getInput()) &&
                condition.apply(criteria.getInputMode()) &&
                condition.apply(criteria.getOutput()) &&
                condition.apply(criteria.getOutputFormula()) &&
                condition.apply(criteria.getVersion()) &&
                condition.apply(criteria.getVariableUnitsId()) &&
                condition.apply(criteria.getBaseUnitId()) &&
                condition.apply(criteria.getVariableScopeId()) &&
                condition.apply(criteria.getVariableCategoryId()) &&
                condition.apply(criteria.getDistinct()),
            "every filter matches"
        );
    }

    private static Condition<VariableCriteria> copyFiltersAre(VariableCriteria copy, BiFunction<Object, Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId(), copy.getId()) &&
                condition.apply(criteria.getCode(), copy.getCode()) &&
                condition.apply(criteria.getVariableIndex(), copy.getVariableIndex()) &&
                condition.apply(criteria.getName(), copy.getName()) &&
                condition.apply(criteria.getDescription(), copy.getDescription()) &&
                condition.apply(criteria.getInput(), copy.getInput()) &&
                condition.apply(criteria.getInputMode(), copy.getInputMode()) &&
                condition.apply(criteria.getOutput(), copy.getOutput()) &&
                condition.apply(criteria.getOutputFormula(), copy.getOutputFormula()) &&
                condition.apply(criteria.getVersion(), copy.getVersion()) &&
                condition.apply(criteria.getVariableUnitsId(), copy.getVariableUnitsId()) &&
                condition.apply(criteria.getBaseUnitId(), copy.getBaseUnitId()) &&
                condition.apply(criteria.getVariableScopeId(), copy.getVariableScopeId()) &&
                condition.apply(criteria.getVariableCategoryId(), copy.getVariableCategoryId()) &&
                condition.apply(criteria.getDistinct(), copy.getDistinct()),
            "every filter matches"
        );
    }
}
