package com.atlassian.bitbucket.mesh.execution;

import com.atlassian.bitbucket.mesh.test.ExecutionContextRule;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.stub.ServerCallStreamObserver;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class DefaultGrpcAsyncExecutorTest {

    @Rule
    public final ExecutionContextRule executionContext = new ExecutionContextRule(new GrpcExecutionManager());

    private DefaultGrpcAsyncExecutor executor;
    private ExecutionManager executionManager;
    @Mock
    private ServerCallStreamObserver<String> observer;

    @Before
    public void setup() {
        executionManager = executionContext.getExecutionManager();
        executor = new DefaultGrpcAsyncExecutor(executionManager, 1, 1, Duration.ofMinutes(1L),
                new ThreadFactoryBuilder().setNameFormat("grpc-async-").build());
    }

    @After
    public void shutdownExecutor() {
        executor.shutdown();
    }

    @Test
    public void testExecutePropagatesExecutionContext() {
        executor.execute(observer, () -> assertTrue(executionManager.getContext().isPresent()));
    }

    @Test
    public void testPropagatesExceptionWhenNotCanceled() {
        IllegalStateException exception = new IllegalStateException("test exception");
        submitAndWait(() -> {
            throw exception;
        });
        verify(observer).onError(exception);
    }

    @Test
    public void testSubmitPropagatesExecutionContext() {
        submitAndWait(() -> assertTrue(executionManager.getContext().isPresent()));
    }

    private void submitAndWait(Runnable task) {
        //Submit the task to the executor
        try {
            executor.submit(observer, task).get(2, TimeUnit.SECONDS);
        } catch (InterruptedException interruptedException) {
            Thread.currentThread().interrupt();
            fail("Thread was interrupted");
        } catch (ExecutionException executionException) {
            fail(executionException.getMessage());
        } catch (TimeoutException e) {
            fail("Task did not complete in time");
        }
    }
}