/**
 * Copyright (C) 2014 Stratio (http://stratio.com)
 *
 * 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.stratio.ingestion.source.rest;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.MapAssert.entry;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.event.EventBuilder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerContext;
import org.quartz.SchedulerException;

import com.google.common.base.Charsets;
import com.stratio.ingestion.source.rest.handler.DefaultRestSourceHandler;
import com.stratio.ingestion.source.rest.handler.JsonRestSourceHandler;
import com.stratio.ingestion.source.rest.handler.RestSourceHandler;
import com.stratio.ingestion.source.rest.url.DefaultUrlHandler;
import com.stratio.ingestion.source.rest.url.UrlHandler;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;

@RunWith(JUnit4.class)
public class RestJobTest {
    RequestJob job = spy(new RequestJob());
    Client client = mock(Client.class);
    WebResource webResource = mock(WebResource.class);
    ClientResponse response = mock(ClientResponse.class);
    JobExecutionContext context = mock(JobExecutionContext.class);
    Scheduler scheduler = mock(Scheduler.class);
    SchedulerContext schedulerContext = mock(SchedulerContext.class);
    LinkedBlockingQueue<Event> queue;
    WebResource.Builder builder = mock(WebResource.Builder.class);
    RestSourceHandler restSourceHandler;
    UrlHandler urlHandler;

    @Before
    public void setUp() throws SchedulerException {
        when(builder.header(anyString(), anyObject())).thenReturn(builder);
        when(builder.header(anyString(), anyObject())).thenReturn(builder);
        when(builder.get(ClientResponse.class)).thenReturn(response);
        when(webResource.accept(any(MediaType.class))).thenReturn(builder);
        when(context.getScheduler()).thenReturn(scheduler);
        when(scheduler.getContext()).thenReturn(schedulerContext);
        queue = new LinkedBlockingQueue<Event>(10);
        when(schedulerContext.get("queue")).thenReturn(queue);
        when(schedulerContext.get("client")).thenReturn(client);

    }

    @Test
    public void simpleGetUsingDefaultHandler() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        when(client.resource(goodUrl)).thenReturn(webResource);
        when(response.getEntity(String.class)).thenReturn("XXXXX");
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        restSourceHandler = initDefaultHandler();
        urlHandler = new DefaultUrlHandler();
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);

        RequestJob job = new RequestJob();
        job.execute(context);

        assertThat(queue.size()).isEqualTo(1);
        assertThat(new String(queue.poll().getBody()).equals("XXXXX"));
    }

    private RestSourceHandler initDefaultHandler() {
        return new DefaultRestSourceHandler();
    }

    @Test
    public void simpleGetWithJustOneEventUsingJsonHandler() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        Map<String, Object> properties = new HashMap<String, Object>();
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        String jsonResponse = "{\"field1\": \"value1\"}";

        restSourceHandler = initJsonHandler("");
        urlHandler = new DefaultUrlHandler();
        when(client.resource(goodUrl)).thenReturn(webResource);
        when(response.getEntity(String.class)).thenReturn(jsonResponse);
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);


        job.execute(context);

        assertThat(queue.size()).isEqualTo(1);
        assertThat(new String(queue.poll().getBody())).isEqualTo("{\"field1\":\"value1\"}");
    }

    private RestSourceHandler initJsonHandler(String pathValue) {
        RestSourceHandler jsonHandler = new JsonRestSourceHandler();
        Context flumeContext = new Context();
        flumeContext.put("jsonPath", pathValue);
        jsonHandler.configure(flumeContext);
        return jsonHandler;
    }

    @Test
    public void simpleGetWithMultipleEventsUsingJsonHandler() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        Map<String, Object> properties = new HashMap<String, Object>();
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        String jsonResponse = "[{\"field1\":\"value1\"},{\"field2\":\"value2\"}]";

        restSourceHandler = initJsonHandler("");
        urlHandler = new DefaultUrlHandler();
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);
        when(client.resource(goodUrl)).thenReturn(webResource);
        when(response.getEntity(String.class)).thenReturn(jsonResponse);
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);

        job.execute(context);

        assertThat(queue.size()).isEqualTo(2);
        assertThat(new String(queue.poll().getBody())).isEqualTo("{\"field1\":\"value1\"}");
        assertThat(new String(queue.poll().getBody())).isEqualTo("{\"field2\":\"value2\"}");
    }

    @Test
    public void simpleGetWithMultipleEventsAndHeadersUsingJsonHandler() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        Map<String, Object> properties = new HashMap<String, Object>();
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        responseHeaders.put("header1", Arrays.asList("value1", "value2"));
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        String jsonResponse = "[{\"field1\":\"value1\"},{\"field2\":\"value2\"}]";

        restSourceHandler = initJsonHandler("");
        urlHandler = new DefaultUrlHandler();
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);
        when(client.resource(goodUrl)).thenReturn(webResource);
        when(response.getEntity(String.class)).thenReturn(jsonResponse);
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);

        job.execute(context);

        assertThat(queue.size()).isEqualTo(2);
        Event poll = queue.poll();
        assertThat(new String(poll.getBody())).isEqualTo("{\"field1\":\"value1\"}");
        assertThat(poll.getHeaders()).includes(entry("header1", "value1, value2"));
        poll = queue.poll();
        assertThat(new String(poll.getBody())).isEqualTo("{\"field2\":\"value2\"}");
        assertThat(poll.getHeaders()).includes(entry("header1", "value1, value2"));
    }

    @Test
    public void simpleGetWithMultipleEventsAndHeadersUsingJsonHandlerAndPath() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        Map<String, Object> properties = new HashMap<String, Object>();
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        responseHeaders.put("header1", Arrays.asList("value1", "value2"));
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        String jsonResponse = "[{\"field1\":\"value1\",\"field2\":\"value2\",\"data\":{\"dataField1\":1,"
                + "\"dataField2\":\"value1\"}},{\"field1\":\"value1\",\"field2\":\"value2\",\"data\":{\"dataField1\":1,\"dataField2\":\"value2\"}}]";

        urlHandler = new DefaultUrlHandler();
        restSourceHandler = initJsonHandler("data");
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);
        when(client.resource(goodUrl)).thenReturn(webResource);
        when(response.getEntity(String.class)).thenReturn(jsonResponse);
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);
        
        job.execute(context);

        assertThat(queue.size()).isEqualTo(2);
        Event poll = queue.poll();
        assertThat(new String(poll.getBody())).isEqualTo("{\"dataField1\":1,\"dataField2\":\"value1\"}");
        assertThat(poll.getHeaders()).includes(entry("header1", "value1, value2"));
        poll = queue.poll();
        assertThat(new String(poll.getBody())).isEqualTo("{\"dataField1\":1,\"dataField2\":\"value2\"}");
        assertThat(poll.getHeaders()).includes(entry("header1", "value1, value2"));
    }

    @Test
    public void testMultivaluedHeader() throws JobExecutionException {
        String goodUrl = "GOOD URL";
        when(client.resource(goodUrl)).thenReturn(webResource);

        //Create Properties
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(RestSource.CONF_URL, goodUrl);
        properties.put(RestSource.CONF_HEADERS, "{}");
        properties.put(RestSource.CONF_METHOD, "GET");
        when(response.getEntity(String.class)).thenReturn("XXXXX");

        //Return headers
        MultivaluedMap<String, String> responseHeaders = new MultivaluedMapImpl();
        responseHeaders.put("GOODHEADER", Arrays.asList("aa", "bb"));
        restSourceHandler = initDefaultHandler();
        urlHandler = new DefaultUrlHandler();
        when(schedulerContext.get("urlHandler")).thenReturn(urlHandler);
        when(response.getHeaders()).thenReturn(responseHeaders);
        when(schedulerContext.get("properties")).thenReturn(properties);
        when(schedulerContext.get("restSourceHandler")).thenReturn(restSourceHandler);

        job.execute(context);

        assertEquals(queue.size(), 1);
        assertEquals(queue.poll().getHeaders().get("GOODHEADER"), "aa, bb");
    }

    @Test
    public void getLastEvent() throws Exception {
        List<Event> events = new ArrayList<Event>();
        events.add(buildEvent("{\"dataField1\":1,\"dataField1\":\"value1\"}"));
        events.add(buildEvent("{\"dataField2\":2,\"dataField2\":\"value2\"}"));
        events.add(buildEvent("{\"dataField3\":3,\"dataField3\":\"value3\"}"));
      
        final String lastEvent = job.getLastEvent(events);
       
        assertThat(lastEvent).isNotEmpty().isEqualToIgnoringCase("{\"dataField3\":3,\"dataField3\":\"value3\"}");

    }

    private Event buildEvent(String json) {
        Event event = EventBuilder.withBody(json, Charsets.UTF_8, new HashMap<String, String>());
        return event;
    }
}
