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.Group;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.Space;
import org.iri_research.renkan.models.User;
import org.iri_research.renkan.repositories.GroupsRepository;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.iri_research.renkan.repositories.SpacesRepository;
import org.iri_research.renkan.repositories.UsersRepository;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
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.security.crypto.password.PasswordEncoder;
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 GroupsAdminControllerTest {

    private final static int SPACE_NB = 3;
    private final static int USER_NB = 3;
    private final static int GROUP_NB = 3;

    private Logger logger = LoggerFactory.getLogger(GroupsAdminControllerTest.class);

    @Autowired
    private SpacesRepository spacesRepository;
    @Autowired
    private ProjectsRepository projectsRepository;
    @Autowired
    private UsersRepository usersRepository;
    @Autowired
    private GroupsRepository groupsRepository;

    
    
    @Autowired
    private PasswordEncoder renkanPasswordEncoder;

    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>();
    
    private Map<String, User> usersList = new HashMap<String, User>(USER_NB);
    private List<String> usersUuids = new ArrayList<>(USER_NB);

    private Map<String, Group> groupsList = new HashMap<String, Group>(GROUP_NB);
    private List<String> groupsUuids = new ArrayList<>(GROUP_NB);
    
    

    @Autowired
    private WebApplicationContext context;
    private MockMvc mvc;

    @Before
    public void setup() {

        logger.debug("Setup");
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        spacesRepository.deleteAll();
        projectsRepository.deleteAll();
        usersRepository.deleteAll();
        groupsRepository.deleteAll();

        for(int i=0; i < USER_NB; i++) {
            String uuid = UUID.randomUUID().toString();
            User user = new User(uuid, "user" + i, "User nb " + i, "http://www.iri.centrepompidou.fr", "#ababab");
            user.setLocked(false);
            user.setEnabled(true);
            user.setAvatar("A pretty picture");
            user.setExpirationDate(new LocalDate());
            user.setCredentialsExpirationDate(new LocalDate());
            user.setEmail(String.format("user%d@mail.com", i));
            user = usersRepository.save(user);
            this.usersUuids.add(uuid);
            this.usersList.put(uuid, user);
        }
        
        
        for(int i=0; i < GROUP_NB; i++) {
            String uuid = UUID.randomUUID().toString();
            Group group = new Group(uuid, "group" + i, "Group nb " + i, "http://www.iri.centrepompidou.fr/group/"+uuid, "#ababab");
            group.setAvatar("A pretty group picture " + i);
            group = groupsRepository.save(group);
            this.groupsUuids.add(uuid);
            this.groupsList.put(uuid, group);
        }
        
        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++) {
                Project p = 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);
                p.addUser(this.usersList.get(this.usersUuids.get(0)));
                pl.add(p);
            }
            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();
        usersRepository.deleteAll();
        groupsRepository.deleteAll();

    }

    @Test
    public void testGroupPostUpdate() throws Exception {

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/save");
        post = post.param("id", this.groupsUuids.get(0));
        post = post.param("title", "New name");
        post = post.param("description", "New description");
        post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
        post = post.param("color", "#ffffff");

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isSeeOther())
                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups?version=NULL"));

        Group group = this.groupsRepository.findOne(this.groupsUuids.get(0));

        Assert.assertNotNull("Should find space", group);
        Assert.assertEquals("Title equals", "New name", group.getTitle());
        Assert.assertEquals("Description equals", "New description",
                group.getDescription());
        Assert.assertEquals("Uri equals",
                "http://ldt.iri.centrepompidou.fr/new/uri", group.getUri());
        Assert.assertEquals("Color equals", "#ffffff", group.getColor());
    }

    @Test
    public void testGroupPostCreate() throws Exception {

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/save")
                    .param("title", "New name")
                    .param("description", "New description")
                    .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
                    .param("color", "#ffffff")
                    .param("avatar", "A pretty avatar");

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isSeeOther())
                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups?version=NULL"));

        Assert.assertEquals("Must have one more group", GROUP_NB + 1,
                this.groupsRepository.count());

        for (Group group : this.groupsRepository.findAll()) {
            if (this.groupsList.containsKey(group.getId())) {
                continue;
            }
            else {
                Assert.assertNotNull("Should find group", group);
                Assert.assertEquals("Title equals", "New name", group.getTitle());
                Assert.assertEquals("Description equals", "New description",
                        group.getDescription());
                Assert.assertEquals("Uri equals",
                        "http://ldt.iri.centrepompidou.fr/new/uri", group.getUri());
                Assert.assertEquals("Color equals", "#ffffff", group.getColor());
                Assert.assertEquals("Avatar equals", "A pretty avatar", group.getAvatar());
                Assert.assertTrue(
                        "id sould match uuid regex",
                        group.getId()
                                .matches(
                                        "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"));
            }
        }
    }

    @Test
    public void testGroupPostUpdateEmptyTitle() throws Exception {

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/save")
                .param("id", this.groupsUuids.get(0))
                .param("title", "")
                .param("description", "New description")
                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
                .param("color", "#ffffff")
                .param("avatar", "A pretty avatar");

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.view().name("admin/groupEdit"))
                .andExpect(MockMvcResultMatchers.model().hasErrors())
                .andExpect(MockMvcResultMatchers.model().errorCount(1))
                .andExpect(MockMvcResultMatchers.model().attributeHasErrors("group"))
                .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("group", "title"));

        Group group = this.groupsRepository.findOne(this.groupsUuids.get(0));

        Assert.assertNotNull("Should find group", group);
        Assert.assertEquals("name equals", "group0", group.getTitle());
        
        Assert.assertEquals("name equals", "Group nb 0", group.getDescription());

    }

    @Test
    public void testGroupPostCreateEmptyName() throws Exception {

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/save")
                .param("title", "")
                .param("description", "New description")
                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
                .param("color", "#ffffff")
                .param("avatar", "A pretty avatar");
                
        
        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.view().name("admin/groupEdit"))
                .andExpect(MockMvcResultMatchers.model().hasErrors())
                .andExpect(MockMvcResultMatchers.model().errorCount(1))
                .andExpect(MockMvcResultMatchers.model().attributeHasErrors("group"))
                .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("group", "title"));

        Assert.assertEquals("Must not have one more group", GROUP_NB,this.groupsRepository.count());

    }

    @Test
    public void testUserPostCreateBadPassword() throws Exception {

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/users/save")
                .param("title", "user")
                .param("description", "New description")
                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
                .param("color", "#ffffff")
                .param("expirationDate","2007-11-24")
                .param("credentialsExpirationDate","2009-11-29")
                .param("password", "test")
                .param("passwordConfirm", "test2");

        this.mvc.perform(post)
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.view().name("admin/userEdit"))
            .andExpect(MockMvcResultMatchers.model().hasErrors())
            .andExpect(MockMvcResultMatchers.model().errorCount(1))
            .andExpect(MockMvcResultMatchers.model().attributeHasErrors("user"))
            .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("user", "password"));
        
        Assert.assertEquals("Must not have one more user", USER_NB,this.usersRepository.count());

    }


    @Test
    public void testDeleteGroup() throws Exception {

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders
                .get("/admin/groups/delete/"
                        + this.groupsUuids.get(GROUP_NB - 1));

        MvcResult res = this.mvc
                .perform(get)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(
                        MockMvcResultMatchers.view().name(
                                "admin/groupDeleteConfirm"))
                .andExpect(
                        MockMvcResultMatchers.model().attributeExists(
                                "groupObj", "key", "salt")).andReturn();

        Map<String, Object> model = res.getModelAndView().getModel();

        Group group = (Group) model.get("groupObj");
        Assert.assertNotNull("Group is not null", group);
        Assert.assertEquals("Must be first group id",
                this.groupsUuids.get(GROUP_NB - 1), group.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", group.checkKey(key, salt));

    }

    @Test
    public void testDeleteFakeGroup() throws Exception {

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders
                .get("/admin/groups/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 testDoDeleteGroupNoKey() throws Exception {
        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/delete/"
                        + this.spacesUuids.get(GROUP_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 group", GROUP_NB,
                this.groupsRepository.count());

    }

    @Test
    public void testDoDeleteGroup() throws Exception {

        Group group = this.groupsList.get(this.groupsUuids.get(GROUP_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 = group.getKey(salt);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String
                .format("/admin/groups/delete/%s?key=%s&salt=%s",
                        this.groupsUuids.get(GROUP_NB - 1), key, salt));

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isSeeOther())
                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups?version=NULL"));

        Assert.assertEquals("Must have one less space", GROUP_NB - 1,
                this.groupsRepository.count());

        group = this.groupsRepository.findOne(this.groupsUuids
                .get(GROUP_NB - 1));

        Assert.assertNull("Group " + this.groupsUuids.get(GROUP_NB - 1)
                + " deleted", group);

    }

    @Test
    public void testDoDeleteGroupFake() throws Exception {

        Group group = this.groupsList.get(this.groupsUuids.get(GROUP_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 = group.getKey(salt);

        MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String
                .format("/admin/groups/delete/%s?key=%s&salt=%s",
                        UUID.randomUUID(), key, salt));

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isSeeOther())
                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups?version=NULL"));

        Assert.assertEquals("Must have the same nb of group", GROUP_NB,
                this.groupsRepository.count());

    }
    
    @Test
    public void testUpdateUserGroups() throws Exception {
        
        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
                .post("/admin/groups/save");
        post = post.param("id", this.groupsUuids.get(0));
        post = post.param("title", "New name");
        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("users", this.usersUuids.toArray(new String[GroupsAdminControllerTest.GROUP_NB]));

        this.mvc.perform(post)
                .andExpect(MockMvcResultMatchers.status().isSeeOther())
                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups?version=NULL"));

        Group group = this.groupsRepository.findOne(this.groupsUuids.get(0));

        Assert.assertNotNull("Should find group", group);
        Assert.assertEquals("Title equals", "New name", group.getTitle());
        Assert.assertEquals("Description equals", "New description",
                group.getDescription());
        Assert.assertEquals("Uri equals",
                "http://ldt.iri.centrepompidou.fr/new/uri", group.getUri());
        Assert.assertEquals("Color equals", "#ffffff", group.getColor());

        Assert.assertNotNull("Users must be set", group.getUsers());
        Assert.assertEquals("all users must be set", USER_NB, group.getUsers().size());
        Assert.assertTrue("users list must contains all users", this.usersUuids.containsAll(group.getUsers()));
        
    }


}
