package org.jboss.resteasy.test.cache;

import static org.jboss.resteasy.test.TestPortProvider.generateURL;

import java.io.File;
import java.util.Hashtable;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;

import org.apache.commons.io.FileUtils;
import org.jboss.resteasy.annotations.cache.Cache;
import org.jboss.resteasy.plugins.cache.server.ServerCacheFeature;
import org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer;
import org.jboss.resteasy.spi.Dispatcher;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.resteasy.test.TestPortProvider;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * RESTEASY-1105
 *
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @author <a href="ron.sigal@jboss.com">Ron Sigal</a>
 * @version $Revision: 1 $
 *
 *  * Copyright Mar 25, 2015
 */
public class PersistentServerCacheTest
{
   private static int count = 0;
   private static int plainCount = 0;
   private static int htmlCount = 0;

   private static NettyJaxrsServer server;
   protected static ResteasyDeployment deployment;
   protected static Dispatcher dispatcher;
   protected static Client client;

   @Path("/cache")
   public static class TestResource
   {
      @GET
      @Produces("text/plain")
      @Cache(maxAge = 2)
      public String get()
      {
         count++;
         return "hello world" + count;
      }

      @PUT
      @Consumes("text/plain")
      public void put(String val)
      {
      }

      @GET
      @Produces("text/plain")
      @Path("accepts")
      @Cache(maxAge = 2)
      public String getPlain()
      {
         plainCount++;
         return "plain" + plainCount;
      }

      @GET
      @Produces("text/html")
      @Path("accepts")
      @Cache(maxAge = 2)
      public String getHtml()
      {
         htmlCount++;
         return "html" + htmlCount;
      }

      @GET
      @Produces("text/plain")
      @Path("stuff")
      @Cache(maxAge = 2)
      public String getStuff()
      {
         count++;
         return "stuff";
      }

      @GET
      @Produces("text/plain")
      @Path("vary")
      @Cache(maxAge = 2)
      public Response getVary(@HeaderParam("X-Test-Vary") @DefaultValue("default") String testVary)
      {
         count++;
         return Response.ok(testVary).header(HttpHeaders.VARY, "X-Test-Vary").header("X-Count", count).build();
      }
   }

   @Path("/cache")
   public interface MyProxy
   {
      @GET
      @Produces("text/plain")
      String get();

   }

   @BeforeClass
   public static void beforeClass()
   {
      client = ClientBuilder.newClient();
   }

   @AfterClass
   public static void afterClass()
   {
      client.close();
   }

   @Before
   public void before() throws Exception
   {
      FileUtils.deleteDirectory(new File("target/TestCache"));
      Hashtable<String,String> initParams = new Hashtable<String,String>();
      Hashtable<String,String> contextParams = new Hashtable<String,String>();
      contextParams.put("server.request.cache.infinispan.config.file", "infinispan.xml");
      contextParams.put("server.request.cache.infinispan.cache.name", "TestCache");

      server = new NettyJaxrsServer();
      server.setPort(TestPortProvider.getPort());
      server.setRootResourcePath("/");
      deployment = server.getDeployment();
      deployment.start();

      dispatcher = deployment.getDispatcher();
      deployment.getProviderFactory().property("server.request.cache.infinispan.config.file", "infinispan.xml");
      deployment.getProviderFactory().property("server.request.cache.infinispan.cache.name", "TestCache");
      deployment.getProviderFactory().register(ServerCacheFeature.class);
      deployment.getRegistry().addPerRequestResource(TestResource.class);
      server.start();
   }

   @After
   public void after() throws Exception
   {
      FileUtils.deleteDirectory(new File("target/TestCache"));
      server.stop();
      server = null;
      dispatcher = null;
      deployment = null;
   }

   @Test
   public void testNoCacheHitValidation() throws Exception
   {
      // test that after a cache expiration NOT MODIFIED is still returned if matching etags

      count = 0;
      String etag = null;
      {
         Builder request = client.target(generateURL("/cache/stuff")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "stuff");
      }


      Thread.sleep(2000);

      {
         Builder request = client.target(generateURL("/cache/stuff")).request();
         request.header(HttpHeaders.IF_NONE_MATCH, etag);
         Response response = request.get();
         Assert.assertEquals(Response.Status.NOT_MODIFIED.getStatusCode(), response.getStatus());
         Assert.assertEquals(2, count);
         response.close();
      }
   }


   @Test
   public void testCache() throws Exception
   {
      count = 0;
      String etag = null;
      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "hello world" + 1);
      }


      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "hello world" + 1);
      }
      // test if-not-match
      {
         Builder request = client.target(generateURL("/cache")).request();
         request.header(HttpHeaders.IF_NONE_MATCH, etag);
         Response response = request.get();
         Assert.assertEquals(Response.Status.NOT_MODIFIED.getStatusCode(), response.getStatus());
         response.close();
      }


      Thread.sleep(2000);

      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "hello world" + 2);
      }

      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "hello world" + 2);
      }

      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.put(Entity.entity("yo", "text/plain"));
         response.close();
      }
      {
         Builder request = client.target(generateURL("/cache")).request();
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "hello world" + 3);
      }
   }


   @Test
   public void testAccepts() throws Exception
   {
      count = 0;
      plainCount = 0;
      htmlCount = 0;
      String etag = null;
      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/plain");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "plain" + 1);
      }

      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/plain");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "plain" + 1);
      }

      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/html");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "html" + 1);
      }
      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/html");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "html" + 1);
      }
   }

   @Test
   public void testPreferredAccepts() throws Exception
   {
      count = 0;
      plainCount = 0;
      htmlCount = 0;
      String etag = null;
      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/plain");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "plain" + 1);
      }

      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/html");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "html" + 1);
      }

      {
         Builder request = client.target(generateURL("/cache/accepts")).request();
         request.header(HttpHeaders.ACCEPT, "text/html;q=0.5, text/plain");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "plain" + 1);
      }
      {
         Builder request = client.target(generateURL("/cache/accepts")).request();
         request.header(HttpHeaders.ACCEPT, "text/plain;q=0.5, text/html");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "html" + 1);
      }
   }

   @Test
   public void testPreferredButNotCachedAccepts() throws Exception
   {
      count = 0;
      plainCount = 0;
      htmlCount = 0;
      String etag = null;
      {
         Builder request = client.target(generateURL("/cache/accepts")).request().accept("text/plain");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "plain" + 1);
      }

      // we test that the preferred can be handled
      {
         Builder request = client.target(generateURL("/cache/accepts")).request();
         request.header(HttpHeaders.ACCEPT, "text/plain;q=0.5, text/html");
         Response response = request.get();
         Assert.assertEquals(200, response.getStatus());
         String cc = response.getHeaderString(HttpHeaders.CACHE_CONTROL);
         Assert.assertNotNull(cc);
         etag = response.getHeaderString(HttpHeaders.ETAG);
         Assert.assertNotNull(etag);
         Assert.assertEquals(response.readEntity(String.class), "html" + 1);
      }
   }

   @Test
   public void testVary() throws Exception {
      int cachedCount;
      {
         Builder request = client.target(generateURL("/cache/vary")).request();
         Response foo = request.accept("text/plain").header("X-Test-Vary", "foo").get();
         Assert.assertEquals("foo", foo.readEntity(String.class));
         cachedCount = Integer.parseInt(foo.getHeaderString("X-Count"));
      }
      {
         Builder request = client.target(generateURL("/cache/vary")).request();
         Response bar = request.accept("text/plain").header("X-Test-Vary", "bar").get();
         Assert.assertEquals("bar", bar.readEntity(String.class));
      }
      {
         Builder request = client.target(generateURL("/cache/vary")).request();
         Response foo = request.accept("text/plain").header("X-Test-Vary", "foo").get();
         Assert.assertEquals("foo", foo.readEntity(String.class));
         int currentCount = Integer.parseInt(foo.getHeaderString("X-Count"));
         Assert.assertEquals(cachedCount, currentCount);
      }
   }
}
