package aQute.bnd.comm.tests;

import java.io.File;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.osgi.util.promise.Promise;

import aQute.bnd.http.HttpClient;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.progress.ProgressPlugin;
import aQute.bnd.service.url.State;
import aQute.bnd.service.url.TaggedData;
import aQute.bnd.url.HttpsVerification;
import aQute.http.testservers.HttpTestServer.Config;
import aQute.http.testservers.Httpbin;
import aQute.lib.io.IO;
import junit.framework.TestCase;

public class HttpClientTest extends TestCase {
	private TestServer	httpServer;
	private Httpbin		httpsServer;
	private File		tmp;

	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
		httpServer.close();
		httpsServer.close();
		IO.delete(tmp);
	}

	public static class TestServer extends Httpbin {
		boolean second = false;

		public TestServer(Config config) throws Exception {
			super(config);
		}

		public void _mftch(Request rq, Response rsp) throws Exception {
			Thread.sleep(1000);
			rsp.headers.put("ETag", "FOO");
			if (second) {
				rsp.code = 304;
				return;
			}
			rsp.code = 200;
			rsp.content = "OK!".getBytes();
			second = true;
		}

	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		Config config = new Config();
		config.https = false;
		httpServer = new TestServer(config);
		httpServer.start();

		Config configs = new Config();
		configs.https = true;
		httpsServer = new Httpbin(configs);
		httpsServer.start();

		tmp = IO.getFile("generated/tmp");
		IO.delete(tmp);
		tmp.mkdirs();
	}

	public void testTimeout() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			try {
				TaggedData tag = hc.build().asTag().timeout(1000).go(httpServer.getBaseURI("timeout/10"));
				assertNotNull(tag);
				assertEquals(200, tag.getResponseCode());
				IO.collect(tag.getInputStream());
				fail();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public void testCancel() throws Exception {
		final long deadline = System.currentTimeMillis() + 1000L;

		try (HttpClient hc = new HttpClient();) {
			Processor p = new Processor();
			p.addBasicPlugin(new ProgressPlugin() {

				@Override
				public Task startTask(String name, int size) {
					return new Task() {

						@Override
						public void worked(int units) {
							System.out.println("Worked " + units);
						}

						@Override
						public void done(String message, Throwable e) {
							System.out.println("Done " + message + " " + e);
						}

						@Override
						public boolean isCanceled() {
							System.out.println("Cancel check ");
							return System.currentTimeMillis() > deadline;
						}

					};
				}
			});
			hc.setRegistry(p);

			TaggedData tag = hc.build().asTag().go(httpServer.getBaseURI("timeout/50"));
			assertNotNull(tag);
			assertEquals(200, tag.getResponseCode());
			try {
				String s = IO.collect(tag.getInputStream());
				fail();
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}

	public void testCachingMultipleFetch() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			hc.setCache(tmp);

			Promise<TaggedData> a = hc.build()
					.useCache()
					.age(1, TimeUnit.DAYS)
					.asTag()
					.async(httpServer.getBaseURI("mftch"));
			Thread.sleep(100);

			Promise<TaggedData> b = hc.build()
					.useCache()
					.age(1, TimeUnit.DAYS)
					.asTag()
					.async(httpServer.getBaseURI("mftch"));

			TaggedData ta = a.getValue();
			assertEquals("FOO", ta.getTag());
			assertEquals(200, ta.getResponseCode());
			assertEquals(true, ta.hasPayload());
			assertEquals(true, ta.isOk());
			assertEquals(State.UPDATED, ta.getState());

			TaggedData tb = b.getValue();
			assertEquals("FOO", tb.getTag());
			assertEquals(304, tb.getResponseCode());
			assertEquals(false, tb.hasPayload());
			assertEquals(State.UNMODIFIED, tb.getState());

			System.out.println(a.getValue());
			System.out.println(b.getValue());
		}
	}

	public void testFetch() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			String text = hc.build().get(String.class).go(httpServer.getBaseURI("get"));
			assertNotNull(text);
			assertTrue(text.startsWith("{"));
		}
	}

	public void testRedirect() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData tag = hc.build().get(TaggedData.class).go(httpServer.getBaseURI("redirect/3/200"));
			assertNotNull(tag);
			assertEquals(200, tag.getResponseCode());
		}
	}

	public void testRedirectTooMany() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData tag = hc.build()
					.maxRedirects(3)
					.get(TaggedData.class)
					.go(httpServer.getBaseURI("redirect/200/200"));
			assertEquals(3, tag.getResponseCode() / 100);
		}
	}

	public void testRedirectURL() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			HttpsVerification httpsVerification = new HttpsVerification(httpsServer.getCertificateChain(), false,
					hc.getReporter());
			hc.addURLConnectionHandler(httpsVerification);
			URI uri = httpsServer.getBaseURI("get");
			URL go = httpServer.getBaseURI("xlocation").toURL();

			TaggedData tag = hc.build()
					.maxRedirects(3)
					.get(TaggedData.class)
					.headers("XLocation", uri.toString())
					.go(go);
			assertNotNull(tag);
			assertEquals(200, tag.getResponseCode());
		}
	}

	public void testETag() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build().get(TaggedData.class).go(httpServer.getBaseURI("etag/1234/0"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
		}
	}

	public void testNotModifiedEtag() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build()
					.get(TaggedData.class)
					.ifNoneMatch("1234")
					.go(httpServer.getBaseURI("etag/1234/0"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, data.getResponseCode());
		}
	}

	public void testModifiedWithEtag() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build()
					.get(TaggedData.class)
					.ifNoneMatch("0000")
					.go(httpServer.getBaseURI("etag/1234/0"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(200, data.getResponseCode());
		}
	}

	public void testNotModifiedSince() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build().get(TaggedData.class).ifNoneMatch("*").ifModifiedSince(20000).go(
					httpServer.getBaseURI("etag/1234/10000"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, data.getResponseCode());
		}
	}

	public void testNotModifiedSinceAtSameTime() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build().get(TaggedData.class).ifNoneMatch("*").ifModifiedSince(20000).go(
					httpServer.getBaseURI("etag/1234/20000"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, data.getResponseCode());
		}
	}

	public void testModifiedSince() throws Exception {
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build()
					.get(TaggedData.class)
					.ifModifiedSince(10000)
					.go(httpServer.getBaseURI("etag/1234/20000"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(200, data.getResponseCode());
		}
		try (HttpClient hc = new HttpClient();) {
			TaggedData data = hc.build()
					.get(TaggedData.class)
					.ifModifiedSince(20000)
					.go(httpServer.getBaseURI("etag/1234/10000"));
			assertNotNull(data);
			assertEquals("1234", data.getTag());
			assertEquals(304, data.getResponseCode());
		}
	}

	public void testMultipleProgressPlugins() throws Exception {
		final long deadline = System.currentTimeMillis() + 1000L;

		try (HttpClient hc = new HttpClient();) {
			Processor p = new Processor();

			final int[] counts = new int[2];
			counts[0] = counts[1] = 0;

			p.addBasicPlugin(new ProgressPlugin() {
				@Override
				public Task startTask(String name, int size) {
					return new Task() {
						@Override
						public void worked(int units) {
							counts[0]++;
						}

						@Override
						public void done(String message, Throwable e) {
							counts[0]++;
						}

						@Override
						public boolean isCanceled() {
							return false;
						}
					};
				}
			});
			p.addBasicPlugin(new ProgressPlugin() {
				@Override
				public Task startTask(String name, int size) {
					return new Task() {
						@Override
						public void worked(int units) {
							counts[1]++;
						}

						@Override
						public void done(String message, Throwable e) {
							counts[1]++;
						}

						@Override
						public boolean isCanceled() {
							return false;
						}
					};
				}
			});
			hc.setRegistry(p);

			String text = hc.build().get(String.class).go(httpServer.getBaseURI("get"));
			assertNotNull(text);
			assertTrue(counts[0] > 0);
			assertEquals(counts[0], counts[1]);
		}
	}

	public void testPut() throws URISyntaxException, Exception {
		try (Processor p = new Processor();) {
			final AtomicBoolean done = new AtomicBoolean();
			p.addBasicPlugin(new ProgressPlugin() {

				@Override
				public Task startTask(final String name, int size) {
					System.out.println("start " + name);
					return new Task() {

						@Override
						public void worked(int units) {
							System.out.println("worked " + name + " " + units);
						}

						@Override
						public void done(String message, Throwable e) {
							System.out.println("done " + name + " " + message + " " + e);
							done.set(true);
						}

						@Override
						public boolean isCanceled() {
							return false;
						}
					};
				}
			});
			try (HttpClient c = new HttpClient();) {
				c.setRegistry(p);
				TaggedData go = c.build().verb("PUT").upload("hello").asTag().go(httpServer.getBaseURI("put"));
				go.getInputStream().close();
				assertEquals(200, go.getResponseCode());
				assertTrue(done.get());
			}
		}
	}
}
