package org.iri_research.renkan.test.controller;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
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.joda.time.DateTime;
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 SpacesAdminControllerTest {

    private final static int SPACE_NB = 3;

    private Logger logger = LoggerFactory.getLogger(SpacesAdminControllerTest.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");
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        spacesRepository.deleteAll();
        projectsRepository.deleteAll();

        ArrayList<Project> pl = new ArrayList<Project>();
        for (int i = 0; i < SPACE_NB; i++) {
            DateTime creationDate = new DateTime();
            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 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?version=NULL"));

        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?version=NULL"));

        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?version=NULL"));

        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?version=NULL"));

        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());

    }

}
