/*******************************************************************************
 * Copyright (c) 2020 The University of York, Aston University.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
 *
 * Contributors:
 *     Jonathan Co - initial API and implementation
 ******************************************************************************/
package org.eclipse.hawk.http;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.List;
import java.util.Locale;

import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.HttpParams;
import org.eclipse.hawk.core.IModelIndexer;
import org.eclipse.hawk.core.VcsChangeType;
import org.eclipse.hawk.core.VcsCommitItem;
import org.eclipse.hawk.core.util.DefaultConsole;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class HTTPManagerTest {

	private static final String DEFAULT_URL = "http://localhost:9080/file.xml";

	@ClassRule
	public static TemporaryFolder folder = new TemporaryFolder();

	private HTTPManager vcs;
	private CloseableHttpClient httpClient;

	@Before
	public void setup() throws Exception {
		httpClient = mock(CloseableHttpClient.class);
		vcs = createVcs(DEFAULT_URL, httpClient);
	}

	@Test
	public void getDeltaNoHeaders() throws Exception {
		final HttpResponse response = createResponse();
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals(vcs.getRepositoryPath("/file.xml"), vcsCommitItem.getPath());
		assertEquals(VcsChangeType.UPDATED, vcsCommitItem.getChangeType());

		final List<VcsCommitItem> delta2 = vcs.getDelta(vcs.getCurrentRevision());
		assertEquals(0, delta2.size());
	}

	@Test
	public void getDeltaEtag() throws Exception {
		final String etag = "abcdef";
		final HttpResponse response = createResponse();
		response.addHeader(HttpHeaders.ETAG, etag);
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals(etag, vcsCommitItem.getCommit().getRevision());
		assertEquals(vcs.getRepositoryPath("/file.xml"), vcsCommitItem.getPath());
		assertEquals(VcsChangeType.UPDATED, vcsCommitItem.getChangeType());

		final List<VcsCommitItem> delta2 = vcs.getDelta(vcs.getCurrentRevision());
		assertEquals(0, delta2.size());
	}

	@Test
	public void getDeltaLastModifiedHeader() throws Exception {
		final Instant time = Instant.now().truncatedTo(ChronoUnit.SECONDS);
		final HttpResponse response = createResponse();
		response.addHeader(HttpHeaders.LAST_MODIFIED,
				DateTimeFormatter.RFC_1123_DATE_TIME.format(time.atOffset(ZoneOffset.UTC)));
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals(Long.toString(time.toEpochMilli()), vcsCommitItem.getCommit().getRevision());
		assertEquals(vcs.getRepositoryPath("/file.xml"), vcsCommitItem.getPath());
		assertEquals(VcsChangeType.UPDATED, vcsCommitItem.getChangeType());

		final List<VcsCommitItem> delta2 = vcs.getDelta(vcs.getCurrentRevision());
		assertEquals(0, delta2.size());
	}

	@Test
	public void getDeltaDigest() throws Exception {
		final String contents = "Hello World";

		// Build SHA digest for comparison
		MessageDigest md = MessageDigest.getInstance("SHA");
		md.update(contents.getBytes(Charset.defaultCharset()));
		final String digest = Base64.getEncoder().encodeToString(md.digest());

		final HttpResponse response = createResponse();
		response.setEntity(new StringEntity(contents, Charset.defaultCharset()));
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals(digest, vcsCommitItem.getCommit().getRevision());
		assertEquals(vcs.getRepositoryPath("/file.xml"), vcsCommitItem.getPath());
		assertEquals(VcsChangeType.UPDATED, vcsCommitItem.getChangeType());

		final List<VcsCommitItem> delta2 = vcs.getDelta(vcs.getCurrentRevision());
		assertEquals(0, delta2.size());
	}

	@Test
	public void getDeltaUrlFilename() throws Exception {
		String file = "some-other-file.xml";
		String repositoryUrl = "http://example.org/" + file;
		vcs = createVcs(repositoryUrl, httpClient);

		final HttpResponse response = createResponse();
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals("/" + file, vcsCommitItem.getPath());
	}

	@Test
	public void getDeltaHeaderFilename() throws Exception {
		String file = "some-other-file.xml";
		String repositoryUrl = "http://example.org/without-the-extension";
		vcs = createVcs(repositoryUrl, httpClient);

		final HttpResponse response = createResponse();
		response.addHeader("Content-Disposition", "attachment; filename=\"" + file + "\"");
		setResponse(response);

		final List<VcsCommitItem> delta = vcs.getDelta(vcs.getCurrentRevision());
		final VcsCommitItem vcsCommitItem = delta.get(0);
		assertNotNull(vcsCommitItem.getCommit().getRevision());
		assertEquals("/without-the-extension/" + file, vcsCommitItem.getPath());
	}

	private HTTPManager createVcs(String url, CloseableHttpClient httpClient) throws URISyntaxException {
		final IModelIndexer indexer = mock(IModelIndexer.class);
		when(indexer.getConsole()).thenReturn(new DefaultConsole());
		HTTPManager vcs = new HTTPManager() {
			protected CloseableHttpClient createClient() {
				return httpClient;
			};
		};
		vcs.init(url, indexer);
		return vcs;
	}

	private HttpResponse createResponse() throws UnsupportedEncodingException {
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null);
		response.setEntity(new StringEntity("Hello World"));
		return response;
	}

	private void setResponse(HttpResponse response) throws ClientProtocolException, IOException {
		when(httpClient.execute(any())).thenReturn(new CloseableHttpResponse() {

			public StatusLine getStatusLine() {
				return response.getStatusLine();
			}

			public void setStatusLine(StatusLine statusline) {
				response.setStatusLine(statusline);
			}

			public ProtocolVersion getProtocolVersion() {
				return response.getProtocolVersion();
			}

			public void setStatusLine(ProtocolVersion ver, int code) {
				response.setStatusLine(ver, code);
			}

			public boolean containsHeader(String name) {
				return response.containsHeader(name);
			}

			public void setStatusLine(ProtocolVersion ver, int code, String reason) {
				response.setStatusLine(ver, code, reason);
			}

			public Header[] getHeaders(String name) {
				return response.getHeaders(name);
			}

			public void setStatusCode(int code) throws IllegalStateException {
				response.setStatusCode(code);
			}

			public Header getFirstHeader(String name) {
				return response.getFirstHeader(name);
			}

			public void setReasonPhrase(String reason) throws IllegalStateException {
				response.setReasonPhrase(reason);
			}

			public Header getLastHeader(String name) {
				return response.getLastHeader(name);
			}

			public HttpEntity getEntity() {
				return response.getEntity();
			}

			public void setEntity(HttpEntity entity) {
				response.setEntity(entity);
			}

			public Header[] getAllHeaders() {
				return response.getAllHeaders();
			}

			public void addHeader(Header header) {
				response.addHeader(header);
			}

			public void addHeader(String name, String value) {
				response.addHeader(name, value);
			}

			public Locale getLocale() {
				return response.getLocale();
			}

			public void setHeader(Header header) {
				response.setHeader(header);
			}

			public void setLocale(Locale loc) {
				response.setLocale(loc);
			}

			public void setHeader(String name, String value) {
				response.setHeader(name, value);
			}

			public void setHeaders(Header[] headers) {
				response.setHeaders(headers);
			}

			public void removeHeader(Header header) {
				response.removeHeader(header);
			}

			public void removeHeaders(String name) {
				response.removeHeaders(name);
			}

			public HeaderIterator headerIterator() {
				return response.headerIterator();
			}

			public HeaderIterator headerIterator(String name) {
				return response.headerIterator(name);
			}

			public HttpParams getParams() {
				return response.getParams();
			}

			public void setParams(HttpParams params) {
				response.setParams(params);
			}

			@Override
			public void close() throws IOException {
				// TODO Auto-generated method stub

			}
		});
	}

}
