package io.rtr.alchemy.service.filters;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import io.dropwizard.jackson.Jackson;
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
import org.junit.Before;
import org.junit.Test;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

public class SparseFieldSetFilterTest {
    private ContainerRequestContext request;
    private ContainerResponseContext response;
    private ObjectMapper mapper;
    private SparseFieldSetFilter filter;
    private JsonNode responseEntity;

    @Before
    public void setUp() {
        request = mock(ContainerRequestContext.class);
        response = mock(ContainerResponseContext.class);
        mapper = Jackson.newObjectMapper();
        filter = new SparseFieldSetFilter(mapper);
    }

    private void ensureMediaType() {
        doReturn(MediaType.APPLICATION_JSON_TYPE).when(response).getMediaType();
    }

    private void doFilter(String ... fields) {
        final MultivaluedMap<String, String> queryParams = new MultivaluedStringMap();
        queryParams.put("fields", Lists.newArrayList(fields));
        doReturn(queryParams).when(request).getHeaders();

        final ObjectNode entity = mapper.createObjectNode();
        entity.put("name", "foo");

        final ArrayNode arrayNode = mapper.createArrayNode();
        entity.put("array", arrayNode);

        final ObjectNode personNode = mapper.createObjectNode();
        arrayNode.add(personNode);

        personNode.put("age", 32);
        personNode.put("name", "Gene");
        entity.put("person", personNode);

        doReturn(entity).when(response).getEntity();
        doAnswer(invocation -> {
            responseEntity = (ObjectNode) invocation.getArguments()[0];
            return null;
        }).when(response).setEntity(any());

        filter.filter(request, response);
    }

    @Test
    public void testWrongMediaType() {
        doReturn(MediaType.TEXT_PLAIN_TYPE).when(request).getMediaType();

        doFilter();
        verify(response, never()).getEntity();
    }

    @Test
    public void testCorrectMediaType() {
        doReturn(MediaType.APPLICATION_JSON_TYPE).when(response).getMediaType();

        doFilter();
        verify(response).getEntity();
    }

    @Test
    public void testCompatibleMediaType() {
        final MediaType mediaType = MediaType.valueOf("application/json; charset=utf-8");
        doReturn(mediaType).when(response).getMediaType();

        doFilter();
        verify(response).getEntity();
    }

    @Test
    public void testFilterFieldsBasic() {
        ensureMediaType();
        doFilter("name");

        assertNotNull(responseEntity);
        assertEquals(1, responseEntity.size());
        assertTrue(responseEntity.has("name"));
    }

    @Test
    public void testFilterFieldsComplex() {
        ensureMediaType();
        doFilter("name", "person.name", "array.name");

        assertNotNull(responseEntity);
        assertEquals(3, responseEntity.size());
        assertTrue(responseEntity.has("name"));
        assertTrue(responseEntity.has("person"));
        assertTrue(responseEntity.has("array"));

        final ObjectNode personNode = (ObjectNode) responseEntity.get("person");
        final ArrayNode arrayNode = (ArrayNode) responseEntity.get("array");

        assertEquals(1, arrayNode.size());
        assertEquals(personNode, arrayNode.get(0));
        assertTrue(personNode.has("name"));
    }

}