package com.emonster.taroaichat.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 DonationCriteriaTest {

    @Test
    void newDonationCriteriaHasAllFiltersNullTest() {
        var donationCriteria = new DonationCriteria();
        assertThat(donationCriteria).is(criteriaFiltersAre(Objects::isNull));
    }

    @Test
    void donationCriteriaFluentMethodsCreatesFiltersTest() {
        var donationCriteria = new DonationCriteria();

        setAllFilters(donationCriteria);

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

    @Test
    void donationCriteriaCopyCreatesNullFilterTest() {
        var donationCriteria = new DonationCriteria();
        var copy = donationCriteria.copy();

        assertThat(donationCriteria).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(donationCriteria)
        );
    }

    @Test
    void donationCriteriaCopyDuplicatesEveryExistingFilterTest() {
        var donationCriteria = new DonationCriteria();
        setAllFilters(donationCriteria);

        var copy = donationCriteria.copy();

        assertThat(donationCriteria).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(donationCriteria)
        );
    }

    @Test
    void toStringVerifier() {
        var donationCriteria = new DonationCriteria();

        assertThat(donationCriteria).hasToString("DonationCriteria{}");
    }

    private static void setAllFilters(DonationCriteria donationCriteria) {
        donationCriteria.id();
        donationCriteria.amount();
        donationCriteria.currency();
        donationCriteria.stripePaymentIntentId();
        donationCriteria.status();
        donationCriteria.createdBy();
        donationCriteria.createdDate();
        donationCriteria.lastModifiedBy();
        donationCriteria.lastModifiedDate();
        donationCriteria.sessionId();
        donationCriteria.userProfileId();
        donationCriteria.distinct();
    }

    private static Condition<DonationCriteria> criteriaFiltersAre(Function<Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId()) &&
                condition.apply(criteria.getAmount()) &&
                condition.apply(criteria.getCurrency()) &&
                condition.apply(criteria.getStripePaymentIntentId()) &&
                condition.apply(criteria.getStatus()) &&
                condition.apply(criteria.getCreatedBy()) &&
                condition.apply(criteria.getCreatedDate()) &&
                condition.apply(criteria.getLastModifiedBy()) &&
                condition.apply(criteria.getLastModifiedDate()) &&
                condition.apply(criteria.getSessionId()) &&
                condition.apply(criteria.getUserProfileId()) &&
                condition.apply(criteria.getDistinct()),
            "every filter matches"
        );
    }

    private static Condition<DonationCriteria> copyFiltersAre(DonationCriteria copy, BiFunction<Object, Object, Boolean> condition) {
        return new Condition<>(
            criteria ->
                condition.apply(criteria.getId(), copy.getId()) &&
                condition.apply(criteria.getAmount(), copy.getAmount()) &&
                condition.apply(criteria.getCurrency(), copy.getCurrency()) &&
                condition.apply(criteria.getStripePaymentIntentId(), copy.getStripePaymentIntentId()) &&
                condition.apply(criteria.getStatus(), copy.getStatus()) &&
                condition.apply(criteria.getCreatedBy(), copy.getCreatedBy()) &&
                condition.apply(criteria.getCreatedDate(), copy.getCreatedDate()) &&
                condition.apply(criteria.getLastModifiedBy(), copy.getLastModifiedBy()) &&
                condition.apply(criteria.getLastModifiedDate(), copy.getLastModifiedDate()) &&
                condition.apply(criteria.getSessionId(), copy.getSessionId()) &&
                condition.apply(criteria.getUserProfileId(), copy.getUserProfileId()) &&
                condition.apply(criteria.getDistinct(), copy.getDistinct()),
            "every filter matches"
        );
    }
}
