package com.atlassian.bitbucket.internal.mirroring.mirror.client;

import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.httpclient.api.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static com.atlassian.bitbucket.hamcrest.OptionalMatcher.empty;
import static com.atlassian.bitbucket.hamcrest.OptionalMatcher.some;
import static com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType.BITBUCKET_CLOUD;
import static com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType.BITBUCKET_SERVER;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;

public class DefaultUpstreamValidatorTest {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Mock
    private HttpClient httpClient;

    @Mock
    private ResponsePromise responsePromise;

    @Mock
    private Response response;

    @Mock
    private Request.Builder requestBuilder;

    @Spy
    private SimpleI18nService service = new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS_WITH_ARGUMENTS);

    @InjectMocks
    private DefaultUpstreamValidator upstreamValidator;

    @Before
    public void setup() throws Exception {
        when(httpClient.newRequest(any(String.class))).thenReturn(requestBuilder);
        when(requestBuilder.get()).thenReturn(responsePromise);
        when(responsePromise.get(10, TimeUnit.SECONDS)).thenReturn(response);
    }

    @Test
    public void testServerValidateFailsWithBadUri() {
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "invalidUrl");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.url.invalid")));
    }

    @Test
    public void testServerValidateFailsWithNonHttpsUrl() {
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "http://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.url.not.https")));
    }

    @Test
    public void testServerValidateFailsOnTimeout() {
        failGetWithException(new TimeoutException());
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.timeout")));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnInterruped() {
        failGetWithException(new InterruptedException());
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.timeout")));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnExceptionWithCause() {
        String error = "Error";
        failGetWithException(new ExecutionException(new IOException(error)));
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.general")));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnConnectionError() {
        String error = "Error";
        failGetWithException(new ExecutionException(new ConnectException(error)));
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.bad.connection", error)));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnSSLError() {
        String error = "Error";
        failGetWithException(new ExecutionException(new SSLException(error)));
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.bad.connection.ssl", error)));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnUnknownHost() {
        String error = "Error";
        String url = "https://example.com";
        failGetWithException(new ExecutionException(new UnknownHostException(error)));
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, url);
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.unknown.host", error)));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnExceptionWithoutCause() {
        String error = "Error";
        failGetWithException(new ExecutionException(error, null));
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.general")));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidateFailsOnNonBadError() {
        when(response.getStatusCode()).thenReturn(HttpStatus.CREATED.code);
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com");
        assertThat(validate, some(service.keyedMessageMatcherFor("bitbucket.mirroring.upstream.validation.bad.response")));
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testServerValidatePassesOnGoodResponse() {
        when(response.getStatusCode()).thenReturn(HttpStatus.OK.code);
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_SERVER, "https://example.com/");
        assertThat(validate, empty());
        verify(httpClient).newRequest("https://example.com/status");
    }

    @Test
    public void testCloudValidateIsANoOp() {
        Optional<KeyedMessage> validate = upstreamValidator.validate(BITBUCKET_CLOUD, "https://example.com/");
        assertThat(validate, empty());

        verifyNoMoreInteractions(httpClient);
    }

    private void failGetWithException(Throwable e) {
        try {
            when(responsePromise.get(10, TimeUnit.SECONDS)).thenThrow(e);
        } catch (InterruptedException | ExecutionException | TimeoutException ignored) {
        }
    }
}