package spark;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static spark.Spark.after;
import static spark.Spark.before;

import java.io.FileNotFoundException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import spark.examples.books.Books;
import spark.utils.IOUtils;

public class BooksIntegrationTest {

    private static int PORT = 4567;

    private static String AUTHOR = "FOO";
    private static String TITLE = "BAR";
    private static String NEW_TITLE = "SPARK";

    private String bookId;

    @After
    public void tearDown() {
        Books.books.clear();
        Spark.stop();
    }

    @Before
    public void setup() {
        before((request, response) -> {
            response.header("FOZ", "BAZ");
        });

        Books.main(null);

        after((request, response) -> {
            response.header("FOO", "BAR");
        });

        try {
            Thread.sleep(500);
        } catch (Exception e) {
        }
    }

    @Test
    public void canCreateBook() {
        try {
            UrlResponse response = createBookViaPOST();

            assertNotNull(response);
            assertNotNull(response.body);
            assertTrue(Integer.valueOf(response.body) > 0);
            assertEquals(201, response.status);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void canListBooks() {
        try {
            bookId = createBookViaPOST().body.trim();

            UrlResponse response = doMethod("GET", "/books", null);

            assertNotNull(response);
            String body = response.body.trim();
            assertNotNull(body);
            assertTrue(Integer.valueOf(body) > 0);
            assertEquals(200, response.status);
            assertTrue(response.body.contains(bookId));
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Test
    public void canGetBook() {
        try {
            bookId = createBookViaPOST().body.trim();

            UrlResponse response = doMethod("GET", "/books/" + bookId, null);

            String result = response.body;
            assertNotNull(response);
            assertNotNull(response.body);
            assertEquals(200, response.status);
            assertTrue(result.contains(AUTHOR));
            assertTrue(result.contains(TITLE));
            assertTrue(beforeFilterIsSet(response));
            assertTrue(afterFilterIsSet(response));
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void canUpdateBook() {
        try {
            bookId = createBookViaPOST().body.trim();

            UrlResponse response = updateBook();

            String result = response.body;
            assertNotNull(response);
            assertNotNull(response.body);
            assertEquals(200, response.status);
            assertTrue(result.contains(bookId));
            assertTrue(result.contains("updated"));
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void canGetUpdatedBook() {
        try {
            bookId = createBookViaPOST().body.trim();
            updateBook();

            UrlResponse response = doMethod("GET", "/books/" + bookId, null);

            String result = response.body;
            assertNotNull(response);
            assertNotNull(response.body);
            assertEquals(200, response.status);
            assertTrue(result.contains(AUTHOR));
            assertTrue(result.contains(NEW_TITLE));
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void canDeleteBook() {
        try {
            bookId = createBookViaPOST().body.trim();

            UrlResponse response = doMethod("DELETE", "/books/" + bookId, null);

            String result = response.body;
            assertNotNull(response);
            assertNotNull(response.body);
            assertEquals(200, response.status);
            assertTrue(result.contains(bookId));
            assertTrue(result.contains("deleted"));
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void wontFindBook() {
        try {
            doMethod("GET", "/books/" + bookId, null);
        } catch (Exception e) {
            if (e instanceof FileNotFoundException) {
                assertTrue(true);
            } else {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }

    private static UrlResponse doMethod(String requestMethod, String path, String body) throws Exception {
        URL url = new URL("http://localhost:" + PORT + path);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod(requestMethod);

        connection.connect();

        String res = IOUtils.toString(connection.getInputStream());
        UrlResponse response = new UrlResponse();
        response.body = res;
        response.status = connection.getResponseCode();
        response.headers = connection.getHeaderFields();
        return response;
    }

    private static class UrlResponse {
        public Map<String, List<String>> headers;
        private String body;
        private int status;
    }

    private UrlResponse createBookViaPOST() throws Exception {
        return doMethod("POST", "/books?author=" + AUTHOR + "&title=" + TITLE, null);
    }

    private UrlResponse updateBook() throws Exception {
        return doMethod("PUT", "/books/" + bookId + "?title=" + NEW_TITLE, null);
    }

    private boolean afterFilterIsSet(UrlResponse response) {
        return response.headers.get("FOO").get(0).equals("BAR");
    }

    private boolean beforeFilterIsSet(UrlResponse response) {
        return response.headers.get("FOZ").get(0).equals("BAZ");
    }
}

@@ -245,24 +245,52 @@
 dException;%0A
+import java.io.IOException;%0A
 import java.
@@ -4948,35 +4948,87 @@
 g body) 
-throws Exception %7B%0A
+%7B%0A        UrlResponse response = new UrlResponse();%0A%0A        try %7B%0A    
         
@@ -5089,16 +5089,20 @@
         
+ 
+   
 HttpURLC
@@ -5162,24 +5162,28 @@
 nnection();%0A
+    
         conn
@@ -5223,32 +5223,36 @@
 thod);%0A%0A        
+    
 connection.conne
@@ -5258,32 +5258,36 @@
 ect();%0A%0A        
+  
+  
 String res = IOU
@@ -5334,58 +5334,13 @@
 ));%0A
+%0A
     
-    UrlResponse response = new UrlResponse();%0A
     
@@ -5364,16 +5364,20 @@
  = res;%0A
+    
         
@@ -5424,32 +5424,36 @@
 Code();%0A        
+ 
+   
 response.headers
@@ -5481,24 +5481,102 @@
 erFields();%0A
+        %7D catch (IOException e) %7B%0A            e.printStackTrace();%0A        %7D%0A%0A
         retu

deal with exceptions in doMethod