package it.sw.pa.comune.artegna.service.criteria;

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

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

class StrutturaCriteriaTest {

    @Test
    void newStrutturaCriteriaHasAllFiltersNullTest() {
        var strutturaCriteria = new StrutturaCriteria();
        assertThat(strutturaCriteria).is(criteriaFiltersAre(Objects::isNull));
    }

    @Test
    void strutturaCriteriaFluentMethodsCreatesFiltersTest() {
        var strutturaCriteria = new StrutturaCriteria();

        setAllFilters(strutturaCriteria);

        assertThat(strutturaCriteria).is(criteriaFiltersAre(Objects::nonNull));
    }

    @Test
    void strutturaCriteriaCopyCreatesNullFilterTest() {
        var strutturaCriteria = new StrutturaCriteria();
        var copy = strutturaCriteria.copy();

        assertThat(strutturaCriteria).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(Objects::isNull)),
            criteria -> assertThat(criteria).isEqualTo(strutturaCriteria)
        );
    }

    @Test
    void strutturaCriteriaCopyDuplicatesEveryExistingFilterTest() {
        var strutturaCriteria = new StrutturaCriteria();
        setAllFilters(strutturaCriteria);

        var copy = strutturaCriteria.copy();

        assertThat(strutturaCriteria).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(Objects::nonNull)),
            criteria -> assertThat(criteria).isEqualTo(strutturaCriteria)
        );
    }

    @Test
    void toStringVerifier() {
        var strutturaCriteria = new StrutturaCriteria();

        assertThat(strutturaCriteria).hasToString("StrutturaCriteria{}");
    }

    private static void setAllFilters(StrutturaCriteria strutturaCriteria) {
        strutturaCriteria.id();
        strutturaCriteria.nome();
        strutturaCriteria.descrizione();
        strutturaCriteria.indirizzo();
        strutturaCriteria.capienzaMax();
        strutturaCriteria.attiva();
        strutturaCriteria.fotoUrl();
        strutturaCriteria.createdAt();
        strutturaCriteria.updatedAt();
        strutturaCriteria.disponibilitaId();
        strutturaCriteria.moduliLiberatorieId();
        strutturaCriteria.distinct();
    }

    private static Condition<StrutturaCriteria> criteriaFiltersAre(Function<Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId()) &&
                condition.apply(criteria.getNome()) &&
                condition.apply(criteria.getDescrizione()) &&
                condition.apply(criteria.getIndirizzo()) &&
                condition.apply(criteria.getCapienzaMax()) &&
                condition.apply(criteria.getAttiva()) &&
                condition.apply(criteria.getFotoUrl()) &&
                condition.apply(criteria.getCreatedAt()) &&
                condition.apply(criteria.getUpdatedAt()) &&
                condition.apply(criteria.getDisponibilitaId()) &&
                condition.apply(criteria.getModuliLiberatorieId()) &&
                condition.apply(criteria.getDistinct()),
            "every filter matches"
        );
    }

    private static Condition<StrutturaCriteria> copyFiltersAre(StrutturaCriteria copy, BiFunction<Object, Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId(), copy.getId()) &&
                condition.apply(criteria.getNome(), copy.getNome()) &&
                condition.apply(criteria.getDescrizione(), copy.getDescrizione()) &&
                condition.apply(criteria.getIndirizzo(), copy.getIndirizzo()) &&
                condition.apply(criteria.getCapienzaMax(), copy.getCapienzaMax()) &&
                condition.apply(criteria.getAttiva(), copy.getAttiva()) &&
                condition.apply(criteria.getFotoUrl(), copy.getFotoUrl()) &&
                condition.apply(criteria.getCreatedAt(), copy.getCreatedAt()) &&
                condition.apply(criteria.getUpdatedAt(), copy.getUpdatedAt()) &&
                condition.apply(criteria.getDisponibilitaId(), copy.getDisponibilitaId()) &&
                condition.apply(criteria.getModuliLiberatorieId(), copy.getModuliLiberatorieId()) &&
                condition.apply(criteria.getDistinct(), copy.getDistinct()),
            "every filter matches"
        );
    }
}
