package com.atlassian.bitbucket.mesh;

import com.atlassian.bitbucket.mesh.grpc.MeshGrpcWiring;
import com.atlassian.bitbucket.mesh.test.context.DelegatingTestExecutionListener;
import com.google.common.base.MoreObjects;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.function.BooleanSupplier;

import static com.atlassian.bitbucket.mesh.MeshConstants.PROFILE_SIDECAR;

@ActiveProfiles({PROFILE_SIDECAR}) //Required for MeshCoreWiring
@ContextConfiguration(classes = {MeshTestWiring.class, MeshGrpcWiring.class})
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners(DelegatingTestExecutionListener.class)
@TestPropertySource(properties = {
        "authentication.enabled=false", //Simplify setup by disabling the need for auth tokens
        "node.config-required=false" //There's no control plane, so there's no configuration
})
public abstract class AbstractWiredTest extends Assert {

    // overrides the deprecated Assert.assertThat to get rid of deprecation warnings in subclasses
    public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
        MatcherAssert.assertThat("", actual, matcher);
    }

    /**
     * Waits up to {@code timeout} for {@code assertAction} to pass and triggers a JUnit failure
     * if the assertion does not pass after the timeout.
     *
     * @param message optional JUnit assertion failure message
     * @param timeout maximum time to wait for the assertAction to pass
     * @param assertAction the action that tests whether the assertion passes
     */
    protected static void awaitTrue(@Nullable String message, @Nonnull Duration timeout,
                                    @Nonnull BooleanSupplier assertAction) {
        Instant timeoutTimestamp = Instant.now().plus(timeout);
        while (true) {
            if (assertAction.getAsBoolean()) {
                return;
            }

            if (Instant.now().isAfter(timeoutTimestamp)) {
                fail(MoreObjects.firstNonNull(message,
                        "assertion did not pass within " + timeout.toMillis() + " millis"));
            }
            try {
                Thread.sleep(10L);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                fail("Interrupted while waiting for assertion to pass");
            }
        }
    }
}
