/**
 * Copyright 2014 Flipkart Internet Pvt. Ltd.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.flipkart.foxtrot.server.resources;

import com.fasterxml.jackson.core.type.TypeReference;
import com.flipkart.foxtrot.common.Document;
import com.flipkart.foxtrot.core.TestUtils;
import com.flipkart.foxtrot.core.exception.FoxtrotExceptions;
import com.flipkart.foxtrot.core.querystore.impl.ElasticsearchUtils;
import com.flipkart.foxtrot.server.providers.exception.FoxtrotExceptionMapper;
import com.foxtrot.flipkart.translator.TableTranslator;
import com.foxtrot.flipkart.translator.config.SegregationConfiguration;
import io.dropwizard.testing.junit.ResourceTestRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Matchers;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.*;

import static org.junit.Assert.*;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;

/**
 * Created by rishabh.goyal on 04/05/14.
 */
public class DocumentResourceTest extends FoxtrotResourceTest {

    @Rule
    public ResourceTestRule resources = ResourceTestRule.builder()
            .addResource(new DocumentResource(getQueryStore(), new TableTranslator(new SegregationConfiguration())))
            .addProvider(new FoxtrotExceptionMapper(getMapper()))
            .setMapper(objectMapper)
            .build();

    @Test
    public void testSaveDocument() throws Exception {
        String id = UUID.randomUUID()
                .toString();
        Document document = new Document(id, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("hello", "world"));
        Entity<Document> documentEntity = Entity.json(document);
        resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(documentEntity);
        getElasticsearchConnection().refresh(ElasticsearchUtils.getIndices(TestUtils.TEST_TABLE_NAME));
        Document response = getQueryStore().get(TestUtils.TEST_TABLE_NAME, id);
        compare(document, response);
    }

    @Test
    public void testSaveDocumentInternalError() throws Exception {
        String id = UUID.randomUUID()
                .toString();
        Document document = new Document(id, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("hello", "world"));
        Entity<Document> documentEntity = Entity.json(document);
        doThrow(FoxtrotExceptions.createExecutionException("dummy", new IOException())).when(getQueryStore())
                .save(anyString(), Matchers.<Document>any());
        Response response = resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(documentEntity);
        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentNullId() throws Exception {
        Document document = new Document(null, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("hello", "world"));
        Entity<Document> documentEntity = Entity.json(document);
        Response response = resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(documentEntity);
        assertEquals(201, response.getStatus());
    }

    @Test
    public void testSaveDocumentNullData() throws Exception {
        Document document = new Document(UUID.randomUUID()
                                                 .toString(), System.currentTimeMillis(), null);
        Entity<Document> documentEntity = Entity.json(document);
        Response response = resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(documentEntity);
        assertEquals(422, response.getStatus());
    }

    @Test
    public void testSaveDocumentUnknownObject() throws Exception {
        Response response = resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(Entity.text("hello"));
        assertEquals(Response.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentEmptyJson() throws Exception {
        Response response = resources.client()
                .target("/v1/document/" + TestUtils.TEST_TABLE_NAME)
                .request()
                .post(Entity.json("{}"));
        assertEquals(422, response.getStatus());
    }


    @Test
    public void testSaveDocuments() throws Exception {
        List<Document> documents = new ArrayList<Document>();
        String id1 = UUID.randomUUID()
                .toString();
        Document document1 = new Document(id1, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        String id2 = UUID.randomUUID()
                .toString();
        Document document2 = new Document(id2, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        documents.add(document1);
        documents.add(document2);
        Entity<List<Document>> listEntity = Entity.json(documents);
        resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(listEntity);
        getElasticsearchConnection().refresh(ElasticsearchUtils.getIndices(TestUtils.TEST_TABLE_NAME));
        compare(document1, getQueryStore().get(TestUtils.TEST_TABLE_NAME, id1));
        compare(document2, getQueryStore().get(TestUtils.TEST_TABLE_NAME, id2));
    }

    @Test
    public void testSaveDocumentsInternalError() throws Exception {
        List<Document> documents = new ArrayList<Document>();
        String id1 = UUID.randomUUID()
                .toString();
        Document document1 = new Document(id1, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        String id2 = UUID.randomUUID()
                .toString();
        Document document2 = new Document(id2, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        documents.add(document1);
        documents.add(document2);
        doThrow(FoxtrotExceptions.createExecutionException("dummy", new IOException())).when(getQueryStore())
                .save(anyString(), anyListOf(Document.class));
        Entity<List<Document>> listEntity = Entity.json(documents);
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(listEntity);
        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentsNullDocuments() throws Exception {
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(null);
        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentsNullDocument() throws Exception {
        List<Document> documents = new Vector<Document>();
        documents.add(null);
        documents.add(new Document(UUID.randomUUID()
                                           .toString(), System.currentTimeMillis(), getMapper().getNodeFactory()
                                           .objectNode()
                                           .put("d", "d")));
        Entity<List<Document>> listEntity = Entity.json(documents);
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(listEntity);
        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentsNullId() throws Exception {
        List<Document> documents = new Vector<Document>();
        documents.add(new Document(null, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("d", "d")));
        Entity<List<Document>> listEntity = Entity.json(documents);
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(listEntity);
        assertEquals(201, response.getStatus());
    }

    @Test
    public void testSaveDocumentsNullData() throws Exception {
        List<Document> documents = new Vector<Document>();
        documents.add(new Document(UUID.randomUUID()
                                           .toString(), System.currentTimeMillis(), null));
        Entity<List<Document>> listEntity = Entity.json(documents);
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(listEntity);
        assertEquals(422, response.getStatus());
    }

    @Test
    public void testSaveDocumentsInvalidRequestObject() throws Exception {
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(Entity.text("Hello"));
        assertEquals(Response.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), response.getStatus());
    }

    @Test
    public void testSaveDocumentsEmptyList() throws Exception {
        Entity<List<Document>> list = Entity.json(Collections.emptyList());
        Response response = resources.client()
                .target(String.format("/v1/document/%s/bulk", TestUtils.TEST_TABLE_NAME))
                .request()
                .post(list);
        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
    }

    @Test
    public void testGetDocument() throws Exception {
        String id = UUID.randomUUID()
                .toString();
        Document document = new Document(id, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        getQueryStore().save(TestUtils.TEST_TABLE_NAME, document);
        getElasticsearchConnection().refresh(ElasticsearchUtils.getIndices(TestUtils.TEST_TABLE_NAME));
        Document response = resources.client()
                .target(String.format("/v1/document/%s/%s", TestUtils.TEST_TABLE_NAME, id))
                .request()
                .get(Document.class);
        compare(document, response);
    }

    @Test
    public void testGetDocumentMissingId() throws Exception {
        String id = UUID.randomUUID()
                .toString();
        try {
            resources.client()
                    .target(String.format("/v1/document/%s/%s", TestUtils.TEST_TABLE_NAME, id))
                    .request()
                    .get(Document.class);
            fail();
        } catch (WebApplicationException ex) {
            assertEquals(Response.Status.NOT_FOUND.getStatusCode(), ex.getResponse()
                    .getStatus());
        }
    }

    @Test
    public void testGetDocumentInternalError() throws Exception {
        String id = UUID.randomUUID()
                .toString();
        try {
            doThrow(FoxtrotExceptions.createExecutionException("dummy", new IOException())).when(getQueryStore())
                    .get(anyString(), anyString());
            resources.client()
                    .target(String.format("/v1/document/%s/%s", TestUtils.TEST_TABLE_NAME, id))
                    .request()
                    .get(Document.class);
            fail();
        } catch (WebApplicationException ex) {
            assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ex.getResponse()
                    .getStatus());
        }
    }


    @Test
    public void testGetDocuments() throws Exception {
        List<Document> documents = new ArrayList<Document>();
        String id1 = UUID.randomUUID()
                .toString();
        Document document1 = new Document(id1, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        String id2 = UUID.randomUUID()
                .toString();
        Document document2 = new Document(id2, System.currentTimeMillis(), getMapper().getNodeFactory()
                .objectNode()
                .put("D", "data"));
        documents.add(document1);
        documents.add(document2);
        getQueryStore().save(TestUtils.TEST_TABLE_NAME, documents);
        getElasticsearchConnection().refresh(ElasticsearchUtils.getIndices(TestUtils.TEST_TABLE_NAME));
        String response = resources.client()
                .target(String.format("/v1/document/%s", TestUtils.TEST_TABLE_NAME))
                .queryParam("id", id1)
                .queryParam("id", id2)
                .request()
                .get(String.class);
        String expectedResponse = getMapper().writeValueAsString(documents);
        assertEquals(expectedResponse, response);
    }

    @Test(expected = BadRequestException.class)
    public void testGetDocumentsNoIds() throws Exception {
        String response = resources.client()
                .target(String.format("/v1/document/%s", TestUtils.TEST_TABLE_NAME))
                .request()
                .get(String.class);
    }

    @Test
    public void testGetDocumentsMissingIds() throws Exception {
        try {
            resources.client()
                    .target(String.format("/v1/document/%s", TestUtils.TEST_TABLE_NAME))
                    .queryParam("id", UUID.randomUUID()
                            .toString())
                    .request()
                    .get(String.class);
            fail();
        } catch (WebApplicationException ex) {
            assertEquals(Response.Status.NOT_FOUND.getStatusCode(), ex.getResponse()
                    .getStatus());
        }
    }

    @Test
    public void testGetDocumentsInternalError() throws Exception {
        try {
            doThrow(FoxtrotExceptions.createExecutionException("dummy", new IOException())).when(getQueryStore())
                    .getAll(anyString(), anyListOf(String.class));
            resources.client()
                    .target(String.format("/v1/document/%s", TestUtils.TEST_TABLE_NAME))
                    .queryParam("id", UUID.randomUUID()
                            .toString())
                    .request()
                    .get(String.class);
            fail();
        } catch (WebApplicationException ex) {
            assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ex.getResponse()
                    .getStatus());
        }
    }

    public void compare(Document expected, Document actual) throws Exception {
        assertNotNull(expected);
        assertNotNull(actual);
        assertNotNull("Actual document Id should not be null", actual.getId());
        assertNotNull("Actual document data should not be null", actual.getData());
        assertEquals("Actual Doc Id should match expected Doc Id", expected.getId(), actual.getId());
        assertEquals("Actual Doc Timestamp should match expected Doc Timestamp", expected.getTimestamp(), actual.getTimestamp());
        Map<String, Object> expectedMap = getMapper().convertValue(expected.getData(), new TypeReference<HashMap<String, Object>>() {
        });
        Map<String, Object> actualMap = getMapper().convertValue(actual.getData(), new TypeReference<HashMap<String, Object>>() {
        });
        assertEquals("Actual data should match expected data", expectedMap, actualMap);
    }
}