package com.atlassian.bitbucket.mesh.gprc;

import io.grpc.Context;
import org.junit.Test;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;

public class GrpcSupportTest {

    @Test
    public void testGetDeadline() {
        Instant start = Instant.now();
        Instant deadline = GrpcSupport.getDeadline(Duration.ofMinutes(1L));

        assertNotNull(deadline);

        assertTrue(Instant.now().isBefore(deadline));
        assertThat(Duration.between(start, deadline).toMillis(),
                both(greaterThanOrEqualTo(60000L)).and(lessThanOrEqualTo(60500L)));
    }

    @Test
    public void testGetDeadlineWithGrpcDeadline() {
        Context.current().withDeadlineAfter(1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            Instant start = Instant.now();
            Instant deadline = GrpcSupport.getDeadline(Duration.ofMinutes(2L));

            assertNotNull(deadline);

            assertTrue(start.isBefore(deadline));
            assertThat(Duration.between(start, deadline).toMillis(),
                    both(greaterThanOrEqualTo(59500L)).and(lessThanOrEqualTo(60000L)));
        });
    }

    @Test
    public void testGetDeadlineZeroTimeout() {
        Instant deadline = GrpcSupport.getDeadline(Duration.ZERO);

        assertNotNull(deadline);

        assertTrue(Instant.now().isBefore(deadline));
        assertTrue(Duration.between(Instant.now(), deadline).toDays() > 30);
    }

    @Test
    public void testGetDeadlineZeroTimeoutWithGrpcDeadline() {
        Context.current().withDeadlineAfter(1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            Instant start = Instant.now();
            Instant deadline = GrpcSupport.getDeadline(Duration.ZERO);

            assertNotNull(deadline);

            assertTrue(start.isBefore(deadline));
            assertThat(Duration.between(start, deadline).toMillis(),
                    both(greaterThanOrEqualTo(59500L)).and(lessThanOrEqualTo(60000L)));
        });
    }

    @Test
    public void testGetTimeout() {
        assertEquals(Duration.ZERO, GrpcSupport.getTimeout());
    }

    @Test
    public void testGetTimeoutWithExpiredGrpcDeadline() {
        Context.current().withDeadlineAfter(-1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            assertEquals(Duration.ofMillis(1L), GrpcSupport.getTimeout());
        });
    }

    @Test
    public void testGetTimeoutWithGrpcDeadline() {
        Context.current().withDeadlineAfter(1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            Duration timeout = GrpcSupport.getTimeout();

            assertNotNull(timeout);
            assertThat(timeout.toMillis(),
                    both(greaterThanOrEqualTo(59500L)).and(lessThanOrEqualTo(60000L)));
        });
    }

    @Test
    public void testMaybeReduceToDeadlineWhenNoDeadlineSet() {
        Duration timeout = Duration.ofMillis(68364213L);
        assertEquals(timeout, GrpcSupport.maybeReduceToDeadline(timeout));
    }

    @Test
    public void testMaybeReduceDeadlineWithExpiredGrpcDeadline() {
        Context.current().withDeadlineAfter(-1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            Duration timeout = Duration.ofMillis(68426L);
            assertEquals(Duration.ofMillis(1L), GrpcSupport.maybeReduceToDeadline(timeout));
        });
    }

    @Test
    public void testMaybeReduceDeadlineWithGrpcDeadline() {
        Context.current().withDeadlineAfter(1, TimeUnit.MINUTES, mock(ScheduledExecutorService.class)).run(() -> {
            Duration timeoutAfterDeadline = Duration.ofMillis(72394L);
            Duration timeoutBeforeDeadline = Duration.ofMillis(16239L);

            assertEquals(timeoutBeforeDeadline, GrpcSupport.maybeReduceToDeadline(timeoutBeforeDeadline));
            Duration timeout = GrpcSupport.maybeReduceToDeadline(timeoutAfterDeadline);
            assertNotNull(timeout);
            assertThat(timeout.toMillis(),
                    both(greaterThanOrEqualTo(59500L)).and(lessThanOrEqualTo(60000L)));
        });
    }
}