package org.iri_research.renkan.test.rest;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;

import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.io.IOUtils;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.iri_research.renkan.models.Edge;
import org.iri_research.renkan.models.Node;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.Space;
import org.iri_research.renkan.repositories.EdgesRepository;
import org.iri_research.renkan.repositories.NodesRepository;
import org.iri_research.renkan.repositories.ProjectRevisionsRepository;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.iri_research.renkan.repositories.SpacesRepository;
import org.iri_research.renkan.rest.RestApplication;
import org.joda.time.DateTime;
import org.joda.time.format.ISODateTimeFormat;
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.MongoTemplate;
import org.springframework.data.mongodb.core.geo.Point;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;

//import com.sun.jersey.spi.spring.container.servlet.SpringServlet;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("rest-context.xml")
public class ProjectRestTest extends JerseyTest {

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

    @Autowired
    private ProjectsRepository projectsRepository;

    @Autowired
    private SpacesRepository spacesRepository;

    @Autowired
    private NodesRepository nodesRepository;

    @Autowired
    private EdgesRepository edgesRepository;

    @Autowired
    private ProjectRevisionsRepository projectRevisionsRepository;

    @Autowired
    private MongoTemplate mongoTemplate;

    private Project testProject;
    private List<Node> testNodes = new ArrayList<Node>();
    private List<Edge> testEdges = new ArrayList<>();

    private DateTime creationDate = new DateTime();

    private String spaceId = UUID.randomUUID().toString();
    private String projectId = UUID.randomUUID().toString();

    private File tempFile = null;

    private ObjectMapper getObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());
        return mapper;
    }

    public ProjectRestTest() {
    }

    // @Override
    // protected AppDescriptor configure() {
    // return new WebAppDescriptor.Builder("org.iri_research.renkan.rest")
    // .contextPath("rest")
    // .contextParam("contextConfigLocation",
    // "classpath:/org/iri_research/renkan/test/rest/rest-context.xml")
    // .initParam("com.sun.jersey.config.property.packages",
    // "org.iri_research.renkan.rest")
    // .servletClass(SpringServlet.class)
    // .contextListenerClass(ContextLoaderListener.class)
    // .build();
    // };

    @Override
    protected Application configure() {
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        ResourceConfig rc = new RestApplication()
                .property("contextConfigLocation",
                        "classpath:/org/iri_research/renkan/test/rest/rest-context.xml");
        rc.setApplicationName("rest");
        return rc;
    };

    @Before
    public void setup() {

        logger.debug("Setup");
        Space testSpace = new Space(this.spaceId, "test space", "Test space",
                null, null, null, "test_user", null, this.creationDate);
        testSpace = spacesRepository.save(testSpace);
        testProject = new Project(testSpace.getId(), this.projectId, "test", "desc",
                "http://localhost:8080/rest/projects/id", this.creationDate);

        for (int i = 0; i < 3; i++) {
            Node node = new Node("Node" + i, "Node" + i, "Node " + i,
                    "http://renkan.org/nodes/node" + i, "#ffff0" + i,
                    "test_user", new Point(0, i),
                    "http://renkan.org/images/node" + i, i, "circle", "Node",
                    false, testProject.getId());
            node = this.nodesRepository.save(node);
            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, "#ffff0" + i,
                    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);
        }

        this.projectsRepository.save(testProject);
        try {
            this.tempFile = File.createTempFile("export_json", ".json");
        }
        catch (IOException e) {
            e.printStackTrace();
        }

    }

    @After
    public void teardown() {
        logger.debug("Teardown");
        edgesRepository.deleteAll();
        nodesRepository.deleteAll();
        projectRevisionsRepository.deleteAll();
        projectsRepository.deleteAll();
        spacesRepository.deleteAll();
        if(this.tempFile != null) {
            this.tempFile.deleteOnExit();
        }
    }

    @Test
    public void testGetProject() throws JsonProcessingException, IOException {
        WebTarget webResource = this.target();
        String respString = webResource.path("projects")
                .path(testProject.getId()).request().acceptEncoding("UTF-8").accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
        Assert.assertNotNull("get resp String not empty", respString);
        Assert.assertFalse("get resp String non empty", respString.isEmpty());

        PrintWriter writer = new PrintWriter(this.tempFile, "UTF-8");
        writer.write(respString);
        writer.close();

        logger.debug("Test get Project : respString : " + respString);

        ObjectMapper mapper = this.getObjectMapper();

        JsonNode projectNode = mapper.readTree(respString);

        Assert.assertNotNull("project node not null", projectNode);

        Assert.assertNotNull("Must have an id", projectNode.get("id"));
        Assert.assertEquals("id must match", this.projectId, projectNode.get("id").asText());
        Assert.assertNotNull("Must have a created date", projectNode.get("created"));
        Assert.assertEquals("creation date must match", this.creationDate.toString(ISODateTimeFormat.dateTime()), projectNode.get("created").asText());
    }

    @Test
    public void testDeleteProject() {

        List<Node> nodes = testProject.getNodes();
        List<Edge> edges = testProject.getEdges();

        WebTarget webResource = this.target();
        String respString = webResource.path("projects")
                .path(testProject.getId()).request().delete(String.class);

        Assert.assertTrue("Response text must contain deleted",
                respString.contains("deleted"));
        Assert.assertTrue("Response text must contain id",
                respString.contains(this.testProject.getId()));

        DBObject filter = new BasicDBObject();
        filter.put("_id", testProject.getId());

        DBCollection coll = mongoTemplate.getCollection(mongoTemplate
                .getCollectionName(Project.class));
        DBCursor resFind = coll.find(filter);

        Assert.assertEquals("The project must not be found", 0, resFind.count());

        filter = new BasicDBObject();
        filter.put("project_id", testProject.getId());

        DBCollection nodesColl = mongoTemplate.getCollection(mongoTemplate
                .getCollectionName(Node.class));
        resFind = nodesColl.find(filter);

        Assert.assertEquals("no nodes left", 0, resFind.count());

        for (Node n : nodes) {
            DBObject nobj = nodesColl.findOne(n.getId());
            Assert.assertNull("node should be deleted", nobj);
        }

        DBCollection edgesColl = mongoTemplate.getCollection(mongoTemplate
                .getCollectionName(Edge.class));
        resFind = edgesColl.find(filter);

        Assert.assertEquals("no edges left", 0, resFind.count());

        for (Edge e : edges) {
            DBObject eobj = edgesColl.findOne(e.getId());
            Assert.assertNull("edge should be deleted", eobj);
        }

    }

    @Test
    public void testPostProject() throws IOException {
        InputStream in = this.getClass().getResourceAsStream("/org/iri_research/renkan/test/rest/test-project.json");
        StringWriter sw = new StringWriter();
        IOUtils.copy(in, sw, "utf-8");

        String jsonStr = sw.toString().replaceAll("\\<space_id\\>", this.spaceId);

        WebTarget webResource = this.target();
        Response resp = webResource.path("projects").request(MediaType.APPLICATION_JSON).post(Entity.entity(jsonStr, MediaType.APPLICATION_JSON), Response.class);

        Assert.assertEquals("Status must be OK", 201, resp.getStatus());

        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(resp.readEntity(String.class));

        Assert.assertNotNull("Must have a project in response", node);

        JsonNode idNode = node.findValue("id");
        Assert.assertNotNull("Project must have an id", idNode);
        String id = idNode.asText();
        Assert.assertNotNull("Project must have an id not null", id);
        Assert.assertNotEquals("Project must have an id not empty", "", id);

        Iterator<JsonNode> edges = node.get("edges").elements();
        int totalEdges = 0;
        while(edges.hasNext()) {
            ObjectNode edgeNode = (ObjectNode) edges.next();
            totalEdges++;
            JsonNode edgeIdNode = edgeNode.get("id");
            Assert.assertNotNull("Edge must have an id", edgeIdNode);
            String edgeId = edgeIdNode.asText();
            Assert.assertNotNull("Edge must have an id not null",edgeId);
            Assert.assertNotEquals("Edge must have an id not empty", "", edgeId);
            JsonNode projIdNode = edgeNode.findValue("project_id");
            Assert.assertNotNull("Edge project id node not null",projIdNode);
            Assert.assertNotNull("Edge project id not null",projIdNode.asText());
            Assert.assertEquals("Edge project id must be" + id, id, projIdNode.asText());
        }
        Assert.assertEquals("Must have one edge", 1, totalEdges);

        Iterator<JsonNode> nodes = node.get("nodes").elements();
        int totalNodes = 0;
        while(nodes.hasNext()) {
            ObjectNode nodeNode = (ObjectNode) nodes.next();
            totalNodes++;
            JsonNode nodeIdNode = nodeNode.get("id");
            Assert.assertNotNull("Edge must have an id", nodeIdNode);
            String nodeId = nodeIdNode.asText();
            Assert.assertNotNull("Edge must have an id not null",nodeId);
            Assert.assertNotEquals("Edge must have an id not empty", "", nodeId);
            JsonNode projIdNode = nodeNode.findValue("project_id");
            Assert.assertNotNull("Edge project id node not null",projIdNode);
            Assert.assertNotNull("Edge project id not null",projIdNode.asText());
            Assert.assertEquals("Edge project id must be" + id, id, projIdNode.asText());
        }
        Assert.assertEquals("Must have 2 nodes", 2, totalNodes);

    }

}
