package com.google.sps.servlets;

import com.google.appengine.api.datastore.*;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.gson.Gson;
import com.google.sps.data.Marker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(JUnit4.class)
public final class MarkerServletTest {

    private static final LocalServiceTestHelper helper =
            new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
    private static Marker marker;
    private static Entity markerEntity;
    private static HttpServletRequest request;
    private static HttpServletResponse response;
    private static StringWriter stringWriter;
    private static PrintWriter writer;
    private static Gson gson;
    private static String markerJson;
    private static final int CREATE_CODE = Action.CREATE.ordinal();
    private static final int UPDATE_CODE = Action.UPDATE.ordinal();
    private static final int DELETE_CODE = Action.DELETE.ordinal();

    @Before
    public void setUp() throws IOException {
        helper.setUp();
        marker = new Marker.Builder()
                .setId(1111)
                .setLat(1)
                .setLng(1)
                .setAnimal("animal")
                .setReporter("reporter")
                .setDescription("description")
                .setUserId(Optional.empty())
                .setBlobKey("blobKey")
                .build();
        markerEntity = new Entity("Marker", 1111);
        markerEntity.setProperty("lat", 1.0);
        markerEntity.setProperty("lng", 1.0);
        markerEntity.setProperty("animal", "animal");
        markerEntity.setProperty("reporter", "reporter");
        markerEntity.setProperty("description", "description");
        markerEntity.setProperty("blobKey", "blobKey");
        request = mock(HttpServletRequest.class);
        response = mock(HttpServletResponse.class);
        stringWriter = new StringWriter();
        writer = new PrintWriter(stringWriter);
        when(response.getWriter()).thenReturn(writer);
        gson = new Gson();
        markerJson = gson.toJson(marker);
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    // Test the doGet method when datastore is empty
    public void doGetDatastoreEmpty() throws IOException {
        new MarkerServlet().doGet(request, response);

        assertEquals("[]\n", stringWriter.toString());
    }

    @Test
    // Test the doGet method when datastore has marker
    public void doGetDatastoreNotEmpty() throws IOException {
        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        ds.put(markerEntity);

        new MarkerServlet().doGet(request, response);

        assertTrue(stringWriter.toString().contains(markerJson));
    }

    @Test
    // Test the UPDATE case when the marker is not in datastore
    public void doPostCaseUpdateNoMarker() throws IOException {
        when(request.getParameter("marker")).thenReturn(markerJson);
        when(request.getParameter("action")).thenReturn(Integer.toString(UPDATE_CODE));
        when(request.getParameter("userToken")).thenReturn("undefined");
        MockServletContext mockServletContext = new MockServletContext();

        MarkerServlet servlet = new MarkerServlet();
        MarkerServlet spiedServlet = Mockito.spy(servlet);
        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();
        spiedServlet.doPost(request, response);


        assertTrue(mockServletContext.throwable.toString().contains(new EntityNotFoundException(markerEntity.getKey()).toString()));
    }

    @Test
    // Test the DELETE case when the marker is not in datastore
    public void doPostCaseDeleteNoMarker() throws IOException {
        when(request.getParameter("marker")).thenReturn(markerJson);
        when(request.getParameter("action")).thenReturn(Integer.toString(DELETE_CODE));
        when(request.getParameter("userToken")).thenReturn("undefined");
        when(request.getParameter("id")).thenReturn(Long.toString(marker.getId()));
        MockServletContext mockServletContext = new MockServletContext();

        MarkerServlet servlet = new MarkerServlet();
        MarkerServlet spiedServlet = Mockito.spy(servlet);
        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();
        spiedServlet.doPost(request, response);


        assertTrue(mockServletContext.throwable.toString().contains(new EntityNotFoundException(markerEntity.getKey()).toString()));
    }

    @Test
    // Test the CREATE case when the user is undefined
    public void doPostCaseCreateNoUser() throws IOException, EntityNotFoundException {
        when(request.getParameter("marker")).thenReturn(markerJson);
        when(request.getParameter("action")).thenReturn(Integer.toString(CREATE_CODE));
        when(request.getParameter("userToken")).thenReturn("undefined");
        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        MockServletContext mockServletContext = new MockServletContext();
        // Mock ServletContext or else we get an error
        MarkerServlet servlet = new MarkerServlet();
        MarkerServlet spiedServlet = Mockito.spy(servlet);
        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();

        // Preform the doPost and get the marker Id back so we can search for it
        spiedServlet.doPost(request, response);
        Map<String,String> responseParameter = gson.fromJson(String.valueOf(stringWriter), Map.class);
        Key markerEntityKey = KeyFactory.createKey("Marker", Long.parseLong(responseParameter.get("id")));

        // Create the entity identical to the one put in the datastore
        markerEntity = new Entity("Marker", Long.parseLong(responseParameter.get("id")));
        markerEntity.setProperty("lat", 1.0);
        markerEntity.setProperty("lng", 1.0);
        markerEntity.setProperty("animal", "animal");
        markerEntity.setProperty("reporter", "reporter");
        markerEntity.setProperty("description", "description");
        markerEntity.setProperty("blobKey", "blobKey");

        assertEquals(ds.get(markerEntityKey), markerEntity);
    }
}
@@ -1415,24 +1415,183 @@
 markerJson;%0A
+    private static DatastoreService datastoreService;%0A    private static MockServletContext mockServletContext;%0A    private static MarkerServlet spiedServlet;%0A
     private 
@@ -1638,32 +1638,32 @@
 EATE.ordinal();%0A
-
     private stat
@@ -2908,24 +2908,350 @@
 on(marker);%0A
+        datastoreService = DatastoreServiceFactory.getDatastoreService();%0A        // Mock ServletContext or else we get an error%0A        mockServletContext = new MockServletContext();%0A        spiedServlet = Mockito.spy(MarkerServlet.class);%0A        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();%0A
     %7D%0A%0A    @
@@ -4293,280 +4293,8 @@
 %22);%0A
-        MockServletContext mockServletContext = new MockServletContext();%0A%0A        MarkerServlet servlet = new MarkerServlet();%0A        MarkerServlet spiedServlet = Mockito.spy(servlet);%0A        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();%0A
     
@@ -4330,33 +4330,32 @@
 st, response);%0A%0A
-%0A
         assertTr
@@ -5251,17 +5251,16 @@
 onse);%0A%0A
-%0A
         
@@ -5776,411 +5776,8 @@
 d%22);
-%0A        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();%0A        MockServletContext mockServletContext = new MockServletContext();%0A        // Mock ServletContext or else we get an error%0A        MarkerServlet servlet = new MarkerServlet();%0A        MarkerServlet spiedServlet = Mockito.spy(servlet);%0A        Mockito.doReturn(mockServletContext).when(spiedServlet).getServletContext();
 %0A%0A  
@@ -6622,17 +6622,31 @@
 Equals(d
-s
+atastoreService
 .get(mar
@@ -6681,9 +6681,10 @@
 ;%0A    %7D%0A
+%0A
 %7D

Move mockServletContext and datastoreService to Before