package com.atlassian.bitbucket.mesh.grpc;

import com.atlassian.bitbucket.mesh.junit.LogListener;
import com.atlassian.bitbucket.mesh.network.MeshChannel;
import com.atlassian.bitbucket.mesh.network.MeshNode;
import com.atlassian.bitbucket.mesh.rpc.v1.*;
import io.grpc.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static com.atlassian.bitbucket.mesh.network.MeshChannel.OPT_CHANNEL;
import static com.atlassian.bitbucket.mesh.network.MeshChannel.OPT_TARGET_NODE;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ErrorHandlingClientInterceptorTest {

    @Rule
    public final LogListener logListener = new LogListener(ErrorHandlingClientInterceptor.class);

    @Mock
    private ClientCall<RpcGetCommitRequest, RpcGetCommitResponse> call;
    @Mock
    private CallOptions callOptions;
    @Mock
    private Channel channel;
    @InjectMocks
    private ErrorHandlingClientInterceptor interceptor;
    @Captor
    private ArgumentCaptor<ClientCall.Listener<RpcGetCommitResponse>> listenerCaptor;
    @Mock
    private ClientCall.Listener<RpcGetCommitResponse> listenerDelegate;
    @Mock
    private MeshChannel meshChannel;
    @Mock
    private MeshNode meshNode;

    @Before
    public void setUp() {
        when(channel.newCall(eq(CommitServiceGrpc.getGetCommitMethod()), any())).thenReturn(call);
    }

    @Test
    public void testWrongTarget() {
        when(callOptions.getOption(OPT_CHANNEL)).thenReturn(meshChannel);
        when(callOptions.getOption(OPT_TARGET_NODE)).thenReturn(meshNode);
        doReturn(true).when(meshChannel).close();

        ClientCall<RpcGetCommitRequest, RpcGetCommitResponse> intercepted =
                interceptor.interceptCall(CommitServiceGrpc.getGetCommitMethod(), callOptions, channel);

        assertNotNull(intercepted);
        assertNotSame(call, intercepted);

        //Simulate a RpcWrongTarget error
        RpcError wrongTarget = RpcError.newBuilder()
                .setWrongTarget(RpcWrongTarget.newBuilder()
                        .setActualId("actual-id")
                        .setTargetId("target-id"))
                .build();
        intercepted.start(listenerDelegate, new Metadata());
        verify(call).start(listenerCaptor.capture(), any());
        listenerCaptor.getValue().onClose(Status.FAILED_PRECONDITION, RpcErrorUtils.toMetadata(wrongTarget));

        verify(meshChannel).close();

        logListener.assertWarn("Closed mis-configured channel for meshNode. The Mesh node at the resolved address " +
                "has ID actual-id instead of target-id. Has the IP address changed?");
    }

    @Test
    public void testWrongTargetNoOptionsSet() {
        ClientCall<RpcGetCommitRequest, RpcGetCommitResponse> intercepted =
                interceptor.interceptCall(CommitServiceGrpc.getGetCommitMethod(), callOptions, channel);

        //Should not fail, but also opt out of intercepting the request
        assertNotNull(intercepted);
        assertSame(call, intercepted);
    }
}