package uk.gov.register.functional;

import com.fasterxml.jackson.databind.JsonNode;
import io.dropwizard.jackson.Jackson;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.JSONException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Before;
import org.junit.Test;

import javax.ws.rs.core.Response;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Map;
import java.util.Optional;

import static java.time.format.DateTimeFormatter.ISO_INSTANT;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;

public class EntryResourceFunctionalTest extends FunctionalTestBase {
    private final String item1 = "{\"address\":\"6789\",\"street\":\"elvis\"}";
    private final String item2 = "{\"address\":\"6790\",\"street\":\"presley\"}";
    private final String item1Hash = "sha-256:" + DigestUtils.sha256Hex(item1);
    private final String item2Hash = "sha-256:" + DigestUtils.sha256Hex(item2);
    
    @Before
    public void publishTestMessages() throws Throwable {
        mintItems(item1, item2);
    }

    @Test
    public void getEntriesView_itemHashesAreRenderedAsLinks() {
        Response response = getRequest("/entries");

        assertThat(response.getStatus(), equalTo(200));

        Document doc = Jsoup.parse(response.readEntity(String.class));
        String entry1Text = doc.getElementsByTag("table").select("a[href=/item/"+ item1Hash + "]").first().text();
        String entry2Text = doc.getElementsByTag("table").select("a[href=/item/"+ item2Hash + "]").first().text();

        assertThat(entry1Text, equalTo(item1Hash));
        assertThat(entry2Text, equalTo(item2Hash));
    }

    @Test
    public void getEntriesAsJson() throws JSONException, IOException {
        Response response = getRequest("/entries.json");

        assertThat(response.getStatus(), equalTo(200));

        JsonNode res = Jackson.newObjectMapper().readValue(response.readEntity(String.class), JsonNode.class);
        assertThat(res.isArray(), equalTo(true));
        assertEntryInJsonNode(res.get(0), 1, item1Hash);
        assertEntryInJsonNode(res.get(1), 2, item2Hash);
    }

    @Test
    public void getEntryByEntryNumber() throws JSONException, IOException {
        Response response = getRequest("/entry/1.json");

        assertThat(response.getStatus(), equalTo(200));
        JsonNode res = Jackson.newObjectMapper().readValue(response.readEntity(String.class), JsonNode.class);
        assertEntryInJsonNode(res, 1, item1Hash);
    }

    @Test
    public void entryView_itemHashIsRenderedAsALink() {
        Response response = getRequest("/entry/1");

        Document doc = Jsoup.parse(response.readEntity(String.class));
        String text = doc.getElementsByTag("table").select("a[href=/item/" + item1Hash + "]").first().text();
        assertThat(text, equalTo(item1Hash));
    }

    @Test
    public void return404ResponseWhenEntryNotExist() {
        assertThat(getRequest("/entry/5001").getStatus(), equalTo(404));
    }

    @Test
    public void return404ResponseWhenEntryNumberIsNotAnIntegerValue() {
        assertThat(getRequest("/entry/a2").getStatus(), equalTo(404));
    }

    @Test
    public void entryResource_retrieviesTimestampsInUTC() throws IOException {
        String expectedDateTime = formatInstant(Instant.now(), Optional.of(ChronoUnit.MINUTES));

        Response response = getRequest("/entry/1.json");

        Map<String, String> responseData = response.readEntity(Map.class);
        Instant entryTimestamp = Instant.parse(responseData.get("entry-timestamp"));
        String actualDateTime = formatInstant(entryTimestamp, Optional.of(ChronoUnit.MINUTES));

        assertThat(actualDateTime, equalTo(expectedDateTime));
    }

    private String formatInstant(Instant instant, Optional<TemporalUnit> temporalUnit){
        return ISO_INSTANT.format(instant.truncatedTo(temporalUnit.orElseGet(() -> ChronoUnit.SECONDS)));
    }

    private void assertEntryInJsonNode(JsonNode actualJsonNode, int expectedEntryNumber, String expectedHash){
        assertThat(actualJsonNode.get("entry-number").asInt(), equalTo(expectedEntryNumber));
        assertThat(actualJsonNode.get("item-hash").textValue(), equalTo(expectedHash));
        assertTrue(actualJsonNode.get("entry-timestamp").textValue().matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"));

    }
}

@@ -168,16 +168,45 @@
 tUtils;%0A
+import org.hamcrest.Matcher;%0A
 import o
@@ -438,41 +438,26 @@
 ava.
+u
 ti
-me.temporal.ChronoUnit;
+l.Map;%0A
 %0Aimport 
 java
@@ -444,32 +444,39 @@
 il.Map;%0A%0Aimport 
+static 
 java.time.tempor
@@ -482,132 +482,175 @@
 ral.
-TemporalUnit;%0Aimport java.util.Map;%0Aimport java.util.Optional;%0A%0Aimport static java.time.format.DateTimeFormatter.ISO_INSTANT
+ChronoUnit.SECONDS;%0Aimport static org.hamcrest.CoreMatchers.allOf;%0Aimport static org.hamcrest.CoreMatchers.equalTo;%0Aimport static org.hamcrest.MatcherAssert.assertThat
 ;%0Aim
@@ -666,36 +666,32 @@
 ic org.hamcrest.
-Core
 Matchers.equalTo
@@ -675,39 +675,43 @@
 mcrest.Matchers.
-equalTo
+greaterThan
 ;%0Aimport static 
@@ -730,33 +730,26 @@
 .Matcher
-Assert.assertThat
+s.lessThan
 ;%0Aimport
@@ -3440,17 +3440,16 @@
 _retriev
-i
 esTimest
@@ -3493,94 +3493,34 @@
     
-String expectedDateTime = formatInstant(Instant.now(), Optional.of(ChronoUnit.MINUTES)
+Instant now = Instant.now(
 );%0A%0A
@@ -3568,33 +3568,32 @@
 entry/1.json%22);%0A
-%0A
         Map%3CStri
@@ -3732,32 +3732,33 @@
 mestamp%22));%0A
+%0A
         
 String actua
@@ -3749,349 +3749,80 @@
     
-String actualDateTime = formatInstant(entryTimestamp, Optional.of(ChronoUnit.MINUTES));%0A%0A        assertThat(actualDateTime, equalTo(expectedDateTime));%0A    %7D%0A%0A    private String formatInstant(Instant instant, Optional%3CTemporalUnit%3E temporalUnit)%7B%0A        return ISO_INSTANT.format(instant.truncatedTo(temporalUnit.orElseGet(() -%3E ChronoUnit.
+assertThat(entryTimestamp, between(now.minus(30, SECONDS), now.plus(30, 
 SECO
@@ -4252,21 +4252,155 @@
 %5Cd%7B2%7DZ$%22));%0A
+    %7D%0A%0A    private Matcher%3CInstant%3E between(Instant lower, Instant upper) %7B%0A        return allOf(greaterThan(lower), lessThan(upper));
 %0A    %7D%0A%7D%0A

Fix test that fails if run at the top of a minute