package com.gitblit.tests;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.gitblit.Keys;
import com.gitblit.manager.FilestoreManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.models.FilestoreModel.Status;
import com.gitblit.servlet.FilestoreServlet;
import com.gitblit.utils.FileUtils;

public class FilestoreServletTest extends GitblitUnitTest {
	
	private static final AtomicBoolean started = new AtomicBoolean(false);
	
	private static final String SHA256_EG = "9a712c5d4037503a2d5ee1d07ad191eb99d051e84cbb020c171a5ae19bbe3cbd";
	
	private static final String repoName = "helloworld.git";
	
    private static final String repoLfs = "/r/" + repoName + "/info/lfs/objects/";
	
	@BeforeClass
	public static void startGitblit() throws Exception {
		started.set(GitBlitSuite.startGitblit());
	}

	@AfterClass
	public static void stopGitblit() throws Exception {
		if (started.get()) {
			GitBlitSuite.stopGitblit();
		}
	}
	
	
	@Test
	public void testRegexGroups() throws Exception {
		
		Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
		
		String basicUrl = "https://localhost:8080/r/test.git/info/lfs/objects/";
		String batchUrl = basicUrl + "batch";
		String oidUrl = basicUrl + SHA256_EG; 
		
        Matcher m = p.matcher(batchUrl);
        assertTrue(m.find());
        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
        assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
        assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
        
        m = p.matcher(oidUrl);
        assertTrue(m.find());
        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
        assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
        assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
	}
	
	@Test
	public void testRegexGroupsNestedRepo() throws Exception {
		
		Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
		
		String basicUrl = "https://localhost:8080/r/nested/test.git/info/lfs/objects/";
		String batchUrl = basicUrl + "batch";
		String oidUrl = basicUrl + SHA256_EG; 
		
        Matcher m = p.matcher(batchUrl);
        assertTrue(m.find());
        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
        assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
        assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
        
        m = p.matcher(oidUrl);
        assertTrue(m.find());
        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
        assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
        assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
	}
	
	@Test
	public void testDownload() throws Exception {
		
		FileUtils.delete(filestore().getStorageFolder());
		filestore().clearFilestoreCache();
		
		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
		
		UserModel u = new UserModel("admin");
		u.canAdmin = true;

		//No upload limit
		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);

		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
		
		//Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
		assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
		
        final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
        
        HttpClient client = HttpClientBuilder.create().build();
    	HttpGet request = new HttpGet(downloadURL);

    	// add request header
    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
    	HttpResponse response = client.execute(request);
    	
		assertEquals(200, response.getStatusLine().getStatusCode());

		String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		
		String expectedContent = String.format("{%s:%s,%s:%d,%s:{%s:{%s:%s}}}",
				"\"oid\"", "\"" + blob.hash + "\"",
				"\"size\"", blob.length,
				"\"actions\"",
				"\"download\"",
				"\"href\"", "\"" + downloadURL + "\"");
		
		assertEquals(expectedContent, content);
		
		
		//Now try the binary download
		request.removeHeaders(HttpHeaders.ACCEPT);
		response = client.execute(request);
		
		assertEquals(200, response.getStatusLine().getStatusCode());
		
		byte[] dlData = IOUtils.toByteArray(response.getEntity().getContent());
				
		assertArrayEquals(blob.blob,  dlData);
		
	}
	
	@Test
	public void testDownloadMultiple() throws Exception {
		
		FileUtils.delete(filestore().getStorageFolder());
		filestore().clearFilestoreCache();
		
		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
		
		UserModel u = new UserModel("admin");
		u.canAdmin = true;

		//No upload limit
		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);

		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
		
		//Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
		assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
		
        final String batchURL = GitBlitSuite.url + repoLfs + "batch";
		final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
        
        HttpClient client = HttpClientBuilder.create().build();
    	HttpPost request = new HttpPost(batchURL);

    	// add request header
    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
    	
    	String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d},{%s:%s,%s:%d}]}",
    			"\"operation\"", "\"download\"",
    			"\"objects\"",
    			"\"oid\"", "\"" + blob.hash + "\"",
    			"\"size\"", blob.length,
    			"\"oid\"", "\"" + SHA256_EG + "\"",
    			"\"size\"", 0);
    	
    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
    	request.setEntity(entity);

    	HttpResponse response = client.execute(request);
    	
    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		assertEquals(200, response.getStatusLine().getStatusCode());

		String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}},{%s:%s,%s:%d,%s:{%s:%s,%s:%d}}]}",
				"\"objects\"",
				"\"oid\"", "\"" + blob.hash + "\"",
				"\"size\"", blob.length,
				"\"actions\"",
				"\"download\"",
				"\"href\"", "\"" + downloadURL + "\"",
				"\"oid\"", "\"" + SHA256_EG + "\"",
				"\"size\"", 0,
				"\"error\"",
				"\"message\"", "\"Object not available\"",
				"\"code\"", 404
				);
		
		assertEquals(expectedContent, responseMessage);
	}
	
	@Test
	public void testDownloadUnavailable() throws Exception {
		
		FileUtils.delete(filestore().getStorageFolder());
		filestore().clearFilestoreCache();
		
		//No upload limit
		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);

		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
		
        final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
        
        HttpClient client = HttpClientBuilder.create().build();
    	HttpGet request = new HttpGet(downloadURL);

    	// add request header
    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
    	HttpResponse response = client.execute(request);
    	
		assertEquals(404, response.getStatusLine().getStatusCode());

		String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		
		String expectedError = String.format("{%s:%s,%s:%d}",
				"\"message\"", "\"Object not available\"",
				"\"code\"", 404);
		
		assertEquals(expectedError, content);
	}
	
	@Test
	public void testUpload() throws Exception {
		
		FileUtils.delete(filestore().getStorageFolder());
		filestore().clearFilestoreCache();
		
		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
		
		UserModel u = new UserModel("admin");
		u.canAdmin = true;

		//No upload limit
		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);

		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
        
        final String expectedUploadURL = GitBlitSuite.url + repoLfs + blob.hash;
        final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
        
        HttpClient client = HttpClientBuilder.create().build();
    	HttpPost request = new HttpPost(initialUploadURL);

    	// add request header
    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
    	
    	String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d}]}",
    			"\"operation\"", "\"upload\"",
    			"\"objects\"",
    			"\"oid\"", "\"" + blob.hash + "\"",
    			"\"size\"", blob.length);
    	
    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
    	request.setEntity(entity);
    	
    	HttpResponse response = client.execute(request);
    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		assertEquals(200, response.getStatusLine().getStatusCode());

		String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}}]}",
				"\"objects\"",
				"\"oid\"", "\"" + blob.hash + "\"",
				"\"size\"", blob.length,
				"\"actions\"",
				"\"upload\"",
				"\"href\"", "\"" + expectedUploadURL + "\"");
		
		assertEquals(expectedContent, responseMessage);
		
		
		//Now try to upload the binary download
		HttpPut putRequest = new HttpPut(expectedUploadURL);
		putRequest.setEntity(new ByteArrayEntity(blob.blob));
		response = client.execute(putRequest);
		
		responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		
		assertEquals(200, response.getStatusLine().getStatusCode());
		
		//Confirm behind the scenes that it is available
		ByteArrayOutputStream savedBlob = new ByteArrayOutputStream();
		assertEquals(Status.Available, filestore().downloadBlob(blob.hash, u, r, savedBlob));
		assertArrayEquals(blob.blob,  savedBlob.toByteArray());
	}

	@Test
	public void testMalformedUpload() throws Exception {
		
		FileUtils.delete(filestore().getStorageFolder());
		filestore().clearFilestoreCache();
		
		//No upload limit
		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);

		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
        
        final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
        
        HttpClient client = HttpClientBuilder.create().build();
    	HttpPost request = new HttpPost(initialUploadURL);

    	// add request header
    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
    	
    	//Malformed JSON, comma instead of colon and unquoted strings
    	String content = String.format("{%s:%s,%s:[{%s:%s,%s,%d}]}",
    			"operation", "upload",
    			"objects",
    			"oid", blob.hash,
    			"size", blob.length);
    	
    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
    	request.setEntity(entity);
    	
    	HttpResponse response = client.execute(request);
    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
		assertEquals(400, response.getStatusLine().getStatusCode());
		
		String expectedError = String.format("{%s:%s,%s:%d}",
				"\"message\"", "\"Malformed Git-LFS request\"",
				"\"code\"", 400);
				
		assertEquals(expectedError, responseMessage);
	}
	
}
