package com.atlassian.bitbucket.mesh.test;

import com.google.common.collect.ImmutableList;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

import static com.atlassian.bitbucket.mesh.test.EventualMatcher.eventually;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

public class EventualMatcherTest {

    @Test
    public void testEventuallyAttemptsSingleMatchForNonPositiveTimeout() {
        Matcher<Integer> matcher = spy(equalTo(1));
        long start = System.currentTimeMillis();
        assertThat(1, eventually(matcher, Duration.ZERO));
        verify(matcher).matches(1);
        assertThat(1, eventually(matcher, Duration.ofSeconds(-1L)));
        verify(matcher, times(2)).matches(1);

        assertThat(2, not(eventually(matcher, Duration.ZERO)));
        verify(matcher).matches(2);
        assertThat(3, not(eventually(matcher, Duration.ofSeconds(-1L))));
        verify(matcher).matches(3);

        assertThat(System.currentTimeMillis() - start, lessThan(100L));
    }

    @Test
    public void testEventuallyDoesNotBlockIfMatchesImmediately() {
        long start = System.currentTimeMillis();
        assertThat(1, eventually(equalTo(1), Duration.ofSeconds(1L)));
        long durationMs = System.currentTimeMillis() - start;
        assertThat(durationMs, lessThan(100L));
    }

    @Test
    public void testEventuallyFailsAfterRetrying() {
        long start = System.currentTimeMillis();
        assertThat(1, not(eventually(equalTo(2), Duration.ofMillis(100L))));
        long durationMs = System.currentTimeMillis() - start;
        assertThat(durationMs, both(greaterThanOrEqualTo(100L)).and(lessThan(200L)));
    }

    @Test
    public void testEventuallyPassesAfterRetrying() {
        Matcher<Instant> isInThePast = new BaseMatcher<Instant>() {

            @Override
            public void describeTo(Description description) {
            }

            @Override
            public boolean matches(Object actual) {
                if (actual instanceof Instant) {
                    return Instant.now().isAfter((Instant) actual);
                }
                return false;
            }
        };
        long start = System.currentTimeMillis();
        assertThat(Instant.now().plus(50L, ChronoUnit.MILLIS), eventually(isInThePast, Duration.ofMillis(150L)));
        long durationMs = System.currentTimeMillis() - start;
        assertThat(durationMs, both(greaterThanOrEqualTo(50L)).and(lessThan(150L)));
    }

    @Test
    public void testEventuallyWithCollection() {
        //Verifies that eventually deals with generic signatures properly
        assertThat(ImmutableList.of("aa", "test", "bb"),
                eventually(containsInAnyOrder("bb", "test", "aa"), Duration.ZERO));
    }
}