package org.iri_research.renkan.test.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

import org.iri_research.renkan.models.Edge;
import org.iri_research.renkan.models.EdgeStyle;
import org.iri_research.renkan.models.Node;
import org.iri_research.renkan.models.NodeStyle;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.Space;
import org.iri_research.renkan.models.View;
import org.iri_research.renkan.repositories.EdgesRepository;
import org.iri_research.renkan.repositories.NodesRepository;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.iri_research.renkan.repositories.SpacesRepository;
import org.iri_research.renkan.rest.ObjectMapperProvider;
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.data.mongodb.core.geo.Point;
import org.springframework.http.MediaType;
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.context.WebApplicationContext;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "controller-context.xml",
        "file:src/main/webapp/WEB-INF/spring-servlet.xml" })
public class RenkanControllerTest {

    private final static int SPACE_NB = 3;

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

    @Autowired
    private SpacesRepository spacesRepository;
    @Autowired
    private ProjectsRepository projectsRepository;
    @Autowired
    private NodesRepository nodesRepository;
    @Autowired
    private EdgesRepository edgesRepository;

    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<>();
    private ArrayList<Node> testNodes = new ArrayList<>();
    private ArrayList<Edge> testEdges = new ArrayList<>();

    @Autowired
    private WebApplicationContext context;
    private MockMvc mvc;

    @Autowired
    private ObjectMapperProvider objectMapperProvider;
    
    private void clean() {
        edgesRepository.deleteAll();
        nodesRepository.deleteAll();
        projectsRepository.deleteAll();
        spacesRepository.deleteAll();
    }

    @Before
    public void setup() {

        logger.debug("Setup");
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        this.clean();

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

        Project testProject = pl.get(0);
        List<String> nodesIds = new ArrayList<String>();
        
        for (int i = 0; i < 3; i++) {
            Node node = new Node("Node" + i, "Node" + i, "Node " + i,
                    "http://renkan.org/nodes/node" + i, new NodeStyle("#ffff0" + i, false, 1),
                    "test_user", new Point(0, i),
                    "http://renkan.org/images/node" + i, i, "circle", "Node",
                    false, testProject.getId());
            node = this.nodesRepository.save(node);
            nodesIds.add(node.getId());
            testProject.getNodes().add(node);
            this.testNodes.add(node);
        }

        for (int i = 0; i < 3; i++) {
            Edge edge = new Edge("Node" + i, "Node" + i, "Node " + i,
                    "http://renkan.org/edges/edge" + i, new EdgeStyle("#ffff0" + i, false, 1, true),
                    this.testNodes.get((i + 2) % 3), this.testNodes.get(i),
                    "test_user", testProject.getId());
            edge = this.edgesRepository.save(edge);
            testProject.getEdges().add(edge);
            this.testEdges.add(edge);
        }
        
        testProject.getViews().add(
                new View("view_1", "view_1", "desc view_1",
                        "http://renkan.org/views/view_1", "#ffffff",
                        "test_user", 1.0, new Point(0, 0), nodesIds));

        for (Project p : projectsRepository.save(pl)) {
            this.testProjects.add(p);
        }

        this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }


    @After
    public void teardown() {
        this.clean();
    }

    @Test
    public void testExportProject() throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
        MvcResult res = this.mvc.perform(get)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andReturn();

        logger.debug("testExportProject resp : "
                + res.getResponse().getContentAsString());
    }

    @Test
    public void testExportProjectContent() throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
        MvcResult res = this.mvc.perform(get)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("title").exists())
                .andExpect(MockMvcResultMatchers.jsonPath("title").value("test1"))
                .andExpect(MockMvcResultMatchers.jsonPath("description").exists())
                .andExpect(MockMvcResultMatchers.jsonPath("description").value("desc1"))
                .andExpect(MockMvcResultMatchers.jsonPath("nodes").isArray())
                .andExpect(MockMvcResultMatchers.jsonPath("edges").isArray())
                .andExpect(MockMvcResultMatchers.jsonPath("views").isArray())
                .andExpect(MockMvcResultMatchers.jsonPath("views[0].hidden_nodes").isArray())
                .andReturn();

        logger.debug("testExportProjectContent resp : "
                + res.getResponse().getContentAsString());
    }

    
    @Test
    public void testViewIdProject() throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
        MvcResult res = this.mvc.perform(get)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andReturn();

        String resp = res.getResponse().getContentAsString();
        ObjectMapper mapper = this.objectMapperProvider.getContext(ObjectMapper.class);
        
        ObjectNode jsonNode = mapper.readValue(resp, ObjectNode.class);
        
        Iterator<JsonNode> nodes = jsonNode.get("nodes").elements();
        
        List<String> nodeIds = new ArrayList<String>();
        
        while(nodes.hasNext()) {
            nodeIds.add(nodes.next().get("@id").asText());
        }
        
        JsonNode views = jsonNode.get("views");
        Assert.assertEquals("Must have 1 node",  1, views.size());
        
        JsonNode view = views.get(0);
        
        Assert.assertTrue("Must have hidden_nodes", view.hasNonNull("hidden_nodes"));
        
        JsonNode hiddenNodes = view.get("hidden_nodes");
        Assert.assertEquals("hidden_nodes size must be 3", 3, hiddenNodes.size());
        
        Iterator<JsonNode> hiddenNodesIterator = hiddenNodes.elements();
        
        while(hiddenNodesIterator.hasNext()) {
            String hiddenNodeId = hiddenNodesIterator.next().asText();
            Assert.assertTrue("Node must be in id list", nodeIds.contains(hiddenNodeId));
            Assert.assertFalse("Id must not start with 'node'", hiddenNodeId.startsWith("node"));
        }

        logger.debug("testExportProjectContent resp : " + resp);

    }

    @Test
    public void testExportProjectExclude() throws Exception {
        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
        MvcResult res = this.mvc.perform(get)
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("id").doesNotExist())
                .andExpect(MockMvcResultMatchers.jsonPath("nodes[*].id").doesNotExist())
                .andExpect(MockMvcResultMatchers.jsonPath("nodes[*].@id").exists())
                .andExpect(MockMvcResultMatchers.jsonPath("edges[*].id").doesNotExist())
                .andExpect(MockMvcResultMatchers.jsonPath("views[*].id").doesNotExist())
                .andReturn();

        logger.debug("testExportProjectContentExclude resp : "
                + res.getResponse().getContentAsString());
    }

}
