/**
 * Copyright © 2016 Jeremy Custenborder ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.jcustenborder.kafka.connect.splunk;

import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.json.Json;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import org.apache.kafka.connect.errors.RetriableException;
import org.apache.kafka.connect.sink.SinkRecord;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class SplunkHttpSinkTaskTest {

  SplunkHttpSinkTask task;

  @BeforeEach
  public void setup() {
    this.task = new SplunkHttpSinkTask();
    Map<String, String> settings = new LinkedHashMap<>();
    settings.put(SplunkHttpSinkConnectorConfig.REMOTE_HOST_CONF, "127.0.0.1");
    settings.put(SplunkHttpSinkConnectorConfig.AUTHORIZATION_TOKEN_CONF, "B5A79AAD-D822-46CC-80D1-819F80D7BFB0");
    settings.put(SplunkHttpSinkConnectorConfig.CURL_LOGGING_ENABLED_CONF, Boolean.TRUE.toString());
    this.task.start(settings);
  }

  LowLevelHttpResponse getResponse(int statusCode) throws IOException {
    String resourceFile = String.format("response.%s.json", statusCode);
    InputStream resourceStream = SplunkHttpSinkTaskTest.class.getResourceAsStream(resourceFile);
    Preconditions.checkNotNull(resourceStream, "Resource %s could not be found", resourceFile);

    LowLevelHttpResponse httpResponse = mock(LowLevelHttpResponse.class, CALLS_REAL_METHODS);
    when(httpResponse.getStatusCode()).thenReturn(statusCode);
    when(httpResponse.getContentType()).thenReturn(Json.MEDIA_TYPE);
    when(httpResponse.getContent()).thenReturn(resourceStream);
    when(httpResponse.getContentEncoding()).thenReturn("UTF-8");
    return httpResponse;
  }

  @Test
  public void normal() throws IOException {
    Collection<SinkRecord> sinkRecords = new ArrayList<>();
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp", "sourcetype", "txt", "index", "main"));

    final LowLevelHttpRequest httpRequest = mock(LowLevelHttpRequest.class, CALLS_REAL_METHODS);
    LowLevelHttpResponse httpResponse = getResponse(200);
    when(httpRequest.execute()).thenReturn(httpResponse);

    this.task.transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return httpRequest;
      }
    };

    this.task.httpRequestFactory = this.task.transport.createRequestFactory(this.task.httpRequestInitializer);
    this.task.put(sinkRecords);
  }

  @Test
  public void connectionRefused() throws IOException {
    Collection<SinkRecord> sinkRecords = new ArrayList<>();
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp", "sourcetype", "txt", "index", "main"));

    final LowLevelHttpRequest httpRequest = mock(LowLevelHttpRequest.class, CALLS_REAL_METHODS);
    when(httpRequest.execute()).thenThrow(ConnectException.class);
    this.task.transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return httpRequest;
      }
    };

    this.task.httpRequestFactory = this.task.transport.createRequestFactory(this.task.httpRequestInitializer);
    assertThrows(RetriableException.class, () -> this.task.put(sinkRecords));
  }

  @Test
  public void contentLengthTooLarge() throws IOException {
    Collection<SinkRecord> sinkRecords = new ArrayList<>();
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp", "sourcetype", "txt", "index", "main"));

    final LowLevelHttpRequest httpRequest = mock(LowLevelHttpRequest.class, CALLS_REAL_METHODS);
    LowLevelHttpResponse httpResponse = getResponse(417);
    when(httpResponse.getContentType()).thenReturn("text/html");
    when(httpRequest.execute()).thenReturn(httpResponse);

    this.task.transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return httpRequest;
      }
    };

    this.task.httpRequestFactory = this.task.transport.createRequestFactory(this.task.httpRequestInitializer);
    assertThrows(org.apache.kafka.connect.errors.ConnectException.class, () -> this.task.put(sinkRecords));
  }

  @Test
  public void invalidToken() throws IOException {
    Collection<SinkRecord> sinkRecords = new ArrayList<>();
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp", "sourcetype", "txt", "index", "main"));

    final LowLevelHttpRequest httpRequest = mock(LowLevelHttpRequest.class, CALLS_REAL_METHODS);
    LowLevelHttpResponse httpResponse = getResponse(403);
    when(httpRequest.execute()).thenReturn(httpResponse);

    this.task.transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return httpRequest;
      }
    };

    this.task.httpRequestFactory = this.task.transport.createRequestFactory(this.task.httpRequestInitializer);
    assertThrows(org.apache.kafka.connect.errors.ConnectException.class, () -> this.task.put(sinkRecords));
  }

  @Test
  public void invalidIndex() throws IOException {
    Collection<SinkRecord> sinkRecords = new ArrayList<>();
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp"));
    SinkRecordContentTest.addRecord(sinkRecords, ImmutableMap.of("host", "hostname.example.com", "time", new Date(1472256858924L), "source", "testapp", "sourcetype", "txt", "index", "main"));

    final LowLevelHttpRequest httpRequest = mock(LowLevelHttpRequest.class, CALLS_REAL_METHODS);
    LowLevelHttpResponse httpResponse = getResponse(400);
    when(httpRequest.execute()).thenReturn(httpResponse);

    this.task.transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
        return httpRequest;
      }
    };

    this.task.httpRequestFactory = this.task.transport.createRequestFactory(this.task.httpRequestInitializer);
    assertThrows(org.apache.kafka.connect.errors.ConnectException.class, () -> this.task.put(sinkRecords));
  }

}