package org.iri_research.renkan.test.controller;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.codec.binary.Hex;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.Space;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.iri_research.renkan.repositories.SpacesRepository;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.util.NestedServletException;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"controller-context.xml", "file:src/main/webapp/WEB-INF/spring-servlet.xml"})
public class AdminControllerTest {
	
	private final static int SPACE_NB = 3;
	
	private Logger logger = LoggerFactory.getLogger(AdminControllerTest.class);
	
	@Autowired
	private SpacesRepository spacesRepository;
	@Autowired
	private ProjectsRepository projectsRepository;
	
	private Map<String, Space> spacesList = new HashMap<String, Space>(SPACE_NB);
	private List<String> spacesUuids = new ArrayList<>(SPACE_NB);
	
	private ArrayList<Project> testProjects = new ArrayList<Project>();
	
	@Autowired
	private WebApplicationContext context;
	private MockMvc mvc;
	
	
	@Before
	public void setup() {
		
		logger.debug("Setup");
		spacesRepository.deleteAll();
		projectsRepository.deleteAll();
		
		ArrayList<Project> pl = new ArrayList<Project>();
		for(int i=0;i<SPACE_NB;i++) {
			Date creationDate = new Date();
			String uuid = UUID.randomUUID().toString();
			spacesUuids.add(uuid);
			Space testSpace = new Space(uuid, "test " + i, "Test space " + 1, "{}", "http://ldt.iri.centrepompidou.fr", "#ababab", "test_user", "http://ldt.iri.centrepompidou.fr", creationDate);
			testSpace = spacesRepository.save(testSpace);
			this.spacesList.put(uuid, testSpace);
			for(int j=0; j<SPACE_NB-1-i; j++) {
				pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test"+((SPACE_NB-1)*i+j+1), "desc"+((SPACE_NB-1)*i+j+1), "http://localhost:8080/rest/projects/id"+((SPACE_NB-1)*i+j+1), creationDate));
			}
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		for(Project p: projectsRepository.save(pl)) {
			this.testProjects.add(p);
		}
		
		this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
	}
	
	@After
	public void teardown() {
		spacesRepository.deleteAll();
		projectsRepository.deleteAll();
	}

	
	@Test
	public void testContext() throws Exception {
		MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/");
		MvcResult res = this.mvc.perform(get)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andReturn();
		
		logger.debug("testContext resp : " + res.getResponse().getContentAsString());
		
	}
	
	@Test
	public void testSpacePostUpdate() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("id", this.spacesUuids.get(0));
		post = post.param("title", "New title");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isSeeOther())
				.andExpect(MockMvcResultMatchers.redirectedUrl("/admin/spaces"));
		
		Space sp = this.spacesRepository.findOne(this.spacesUuids.get(0));
		
		Assert.assertNotNull("Should find space", sp);
		Assert.assertEquals("Title equals", "New title", sp.getTitle());
		Assert.assertEquals("Description equals", "New description", sp.getDescription());
		Assert.assertEquals("Uri equals", "http://ldt.iri.centrepompidou.fr/new/uri", sp.getUri());
		Assert.assertEquals("Color equals", "#ffffff", sp.getColor());
		Assert.assertEquals("BinConfig equals", "{}", sp.getBinConfig());
		
	}

	@Test
	public void testSpacePostCreate() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("title", "New title");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isSeeOther())
				.andExpect(MockMvcResultMatchers.redirectedUrl("/admin/spaces"));
		
		
		Assert.assertEquals("Must have one more space", SPACE_NB + 1, this.spacesRepository.count());
		
		for (Space sp : this.spacesRepository.findAll()) {
			if(this.spacesList.containsKey(sp.getId())) {
				continue;
			}
			else {
				Assert.assertNotNull("Should find space", sp);
				Assert.assertEquals("Title equals", "New title", sp.getTitle());
				Assert.assertEquals("Description equals", "New description", sp.getDescription());
				Assert.assertEquals("Uri equals", "http://ldt.iri.centrepompidou.fr/new/uri", sp.getUri());
				Assert.assertEquals("Color equals", "#ffffff", sp.getColor());
				Assert.assertEquals("BinConfig equals", "{}", sp.getBinConfig());
				Assert.assertTrue("id sould match uuid regex", sp.getId().matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"));
				Assert.assertNotNull("Date created should be not null", sp.getCreated());
			}
		}
	}
	
	@Test
	public void testSpacePostUpdateEmptyTitle() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("id", this.spacesUuids.get(0));
		post = post.param("title", "");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(1))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "title"));
		
		Space sp = this.spacesRepository.findOne(this.spacesUuids.get(0));
		
		Assert.assertNotNull("Should find space", sp);
		Assert.assertEquals("Title equals", "test 0", sp.getTitle());
		
	}
	
	@Test
	public void testSpacePostCreateEmptyTitle() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("title", "");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(1))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "title"));
				
		Assert.assertEquals("Must not have one more space", SPACE_NB, this.spacesRepository.count());
		
	}

	@Test
	public void testSpacePostUpdateBadJson() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("id", this.spacesUuids.get(0));
		post = post.param("title", "New Title");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(1))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "binConfig"));
		
		Space sp = this.spacesRepository.findOne(this.spacesUuids.get(0));
		
		Assert.assertNotNull("Should find space", sp);
		Assert.assertEquals("Bin config equals", "{}", sp.getBinConfig());
		
	}
	
	@Test
	public void testSpacePostCreateBadJson() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("title", "New Title");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(1))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "binConfig"));
				
		Assert.assertEquals("Must not have one more space", SPACE_NB, this.spacesRepository.count());
		
	}

	@Test
	public void testSpacePostUpdateAllErrors() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("id", this.spacesUuids.get(0));
		post = post.param("title", "");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "{");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(2))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "title"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "binConfig"));
		
		Space sp = this.spacesRepository.findOne(this.spacesUuids.get(0));
		
		Assert.assertNotNull("Should find space", sp);
		Assert.assertEquals("Bin config equals", "{}", sp.getBinConfig());
		Assert.assertEquals("title equals", "test 0", sp.getTitle());
		
	}
	
	@Test
	public void testSpacePostCreateAllErrors() throws Exception {
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/save");
		post = post.param("title", "");
		post = post.param("description", "New description");
		post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
		post = post.param("color", "#ffffff");
		post = post.param("binConfig", "}");
		
		this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isOk())
				.andExpect(MockMvcResultMatchers.view().name("admin/spaceEdit"))
				.andExpect(MockMvcResultMatchers.model().hasErrors())
				.andExpect(MockMvcResultMatchers.model().errorCount(2))
				.andExpect(MockMvcResultMatchers.model().attributeHasErrors("space"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "title"))
				.andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("space", "binConfig"));
				
		Assert.assertEquals("Must not have one more space", SPACE_NB, this.spacesRepository.count());
		
	}
	
	@Test
	public void testDeleteSpace() throws Exception {
		
		MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/admin/spaces/delete/"+this.spacesUuids.get(SPACE_NB-1));
		
		MvcResult res = this.mvc.perform(get)
			.andExpect(MockMvcResultMatchers.status().isOk())			
			.andExpect(MockMvcResultMatchers.view().name("admin/spaceDeleteConfirm"))
			.andExpect(MockMvcResultMatchers.model().attributeExists("spaceObj", "key", "salt"))
			.andReturn();
		
		Map<String, Object> model = res.getModelAndView().getModel();
		
		Space space = (Space)model.get("spaceObj");
		Assert.assertNotNull("Space is not null", space);
		Assert.assertEquals("Must be first space id", this.spacesUuids.get(SPACE_NB-1), space.getId());
		
		String key = (String)model.get("key");
		Assert.assertNotNull("key is not null", key);

		String salt = (String)model.get("salt");
		Assert.assertNotNull("salt is not null", salt);
		
		Assert.assertTrue("Key must be checked", space.checkKey(key, salt));
		
	}

	@Test
	public void testDeleteFakeSpace() throws Exception {
		
		MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/admin/spaces/delete/" + UUID.randomUUID().toString());
		
		try {
			this.mvc.perform(get)
				.andExpect(MockMvcResultMatchers.status().isNotFound());
		}
		catch(NestedServletException e) {
			Assert.assertNotNull("Nested exception must not be null", e.getCause());
			Assert.assertEquals("Inner exception must be a HttpClientErrorException", HttpClientErrorException.class, e.getCause().getClass());
			Assert.assertEquals("Exception error status must be not found", HttpStatus.NOT_FOUND, ((HttpClientErrorException)e.getCause()).getStatusCode());
		}
		
	}
	
	@Test
	public void testDeleteSpaceProject() throws Exception {
		
		MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/admin/spaces/delete/" + this.spacesUuids.get(0));
		
		try {
			this.mvc.perform(get)
				.andExpect(MockMvcResultMatchers.status().isBadRequest());
		}
		catch(NestedServletException e) {
			Assert.assertNotNull("Nested exception must not be null", e.getCause());
			Assert.assertEquals("Inner exception must be a HttpClientErrorException", HttpClientErrorException.class, e.getCause().getClass());
			Assert.assertEquals("Exception error status must be not found", HttpStatus.BAD_REQUEST, ((HttpClientErrorException)e.getCause()).getStatusCode());
		}
		
	}
	
	@Test
	public void testDoDeleteSpaceNoKey() throws Exception {
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post("/admin/spaces/delete/"+this.spacesUuids.get(SPACE_NB-1));
		
		try {
			this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isBadRequest());
		}
		catch(NestedServletException e) {
			Assert.assertNotNull("Nested exception must not be null", e.getCause());
			Assert.assertEquals("Inner exception must be a HttpClientErrorException", HttpClientErrorException.class, e.getCause().getClass());
			Assert.assertEquals("Exception error status must be not found", HttpStatus.BAD_REQUEST, ((HttpClientErrorException)e.getCause()).getStatusCode());
		}

		
		Assert.assertEquals("Must have same nb of space", SPACE_NB, this.spacesRepository.count());
		
	}
	
	@Test
	public void testDoDeleteSpace() throws Exception {
		
		Space space = this.spacesList.get(this.spacesUuids.get(SPACE_NB-1));
		
		SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
		rand.setSeed(System.currentTimeMillis());
		byte[] rawSalt = new byte[50];
		rand.nextBytes(rawSalt);
		String salt = Hex.encodeHexString(rawSalt);
		String key = space.getKey(salt);
		
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String.format("/admin/spaces/delete/%s?key=%s&salt=%s",this.spacesUuids.get(SPACE_NB-1), key, salt));
		
		this.mvc.perform(post)
			.andExpect(MockMvcResultMatchers.status().isSeeOther())
			.andExpect(MockMvcResultMatchers.redirectedUrl("/admin/spaces"));
		
		Assert.assertEquals("Must have one less space", SPACE_NB-1, this.spacesRepository.count());
		
		space = this.spacesRepository.findOne(this.spacesUuids.get(SPACE_NB-1));
		
		Assert.assertNull("Space " + this.spacesUuids.get(SPACE_NB-1) + " deleted", space);
		
	}

	
	@Test
	public void testDoDeleteSpaceFake() throws Exception {
		
		Space space = this.spacesList.get(this.spacesUuids.get(SPACE_NB-1));
		
		SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
		rand.setSeed(System.currentTimeMillis());
		byte[] rawSalt = new byte[50];
		rand.nextBytes(rawSalt);
		String salt = Hex.encodeHexString(rawSalt);
		String key = space.getKey(salt);
		
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String.format("/admin/spaces/delete/%s?key=%s&salt=%s",UUID.randomUUID(), key, salt));
		
		this.mvc.perform(post)
			.andExpect(MockMvcResultMatchers.status().isSeeOther())
			.andExpect(MockMvcResultMatchers.redirectedUrl("/admin/spaces"));			
		
		Assert.assertEquals("Must have the same nb of space", SPACE_NB, this.spacesRepository.count());
				
	}

	@Test
	public void testDoDeleteSpaceProject() throws Exception {
		
		Space space = this.spacesList.get(this.spacesUuids.get(0));
		
		SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
		rand.setSeed(System.currentTimeMillis());
		byte[] rawSalt = new byte[50];
		rand.nextBytes(rawSalt);
		String salt = Hex.encodeHexString(rawSalt);
		String key = space.getKey(salt);
		
		
		MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String.format("/admin/spaces/delete/%s?key=%s&salt=%s",this.spacesUuids.get(0), key, salt));
		
		try {
			this.mvc.perform(post)
				.andExpect(MockMvcResultMatchers.status().isBadRequest());
		}
		catch(NestedServletException e) {
			Assert.assertNotNull("Nested exception must not be null", e.getCause());
			Assert.assertEquals("Inner exception must be a HttpClientErrorException", HttpClientErrorException.class, e.getCause().getClass());
			Assert.assertEquals("Exception error status must be not found", HttpStatus.BAD_REQUEST, ((HttpClientErrorException)e.getCause()).getStatusCode());
		}
		
		Assert.assertEquals("Must have the same nb of space", SPACE_NB, this.spacesRepository.count());
				
	}

}
