package org.iri_research.renkan.test.repositories;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

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.ProjectRevision;
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.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.domain.Page;
import org.springframework.data.domain.PageRequest;
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.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.DBRef;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("repositories-context.xml")
public class ProjectsRepositoryTest {

	private final static int SPACE_NB = 3;
	
	private Logger logger = LoggerFactory.getLogger(ProjectsRepositoryTest.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 ArrayList<Project> testProjects = new ArrayList<Project>();
	private List<Node> testNodes = new ArrayList<Node>();
	private List<Edge> testEdges = new ArrayList<>();
	
	private Date creationDate = new Date();
	
	private List<String> spaceIds = new ArrayList<>(); 
	
	private Project copyProject;
	
	public ProjectsRepositoryTest() {
	}
	
	@Before
	public void setup() {

		logger.debug("Setup");
		ArrayList<Project> pl = new ArrayList<Project>();
		Space testSpace = null;
		for(int i=0; i<SPACE_NB; i++) {
			spaceIds.add(UUID.randomUUID().toString());
			testSpace = new Space(this.spaceIds.get(i), "test space " + i, "Test space " + i, null, null, null, "test_user", null, this.creationDate);
			testSpace = spacesRepository.save(testSpace);
			for(int j=0; j<SPACE_NB-1-i; j++) {
				pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test project "+((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), this.creationDate));
			}
		}
		for(Project p: projectsRepository.save(pl)) {
			this.testProjects.add(p);
		}
		copyProject = this.testProjects.get(0);
		
		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, copyProject.getId());
			node = this.nodesRepository.save(node);
			copyProject.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", copyProject.getId());
			edge = this.edgesRepository.save(edge);
			copyProject.getEdges().add(edge);
			this.testEdges.add(edge);
		}
		
		this.projectsRepository.save(copyProject);

	}
	
	@After
	public void teardown() {
		logger.debug("Teardown");
		edgesRepository.deleteAll();
		nodesRepository.deleteAll();
		projectRevisionsRepository.deleteAll();
		projectsRepository.deleteAll();
		spacesRepository.deleteAll();
	}
	
	@Test
	public void testInitialRevCounter() {
		
		Project p = projectsRepository.findOne(this.testProjects.get(0).getId());
		
		Assert.assertEquals("Initial rev counter should be 1", 1, p.getRevCounter());
	}
	
	@Test
	public void testIncrementRevCounter() {
		
		int revCounter = projectsRepository.getRevCounter(this.testProjects.get(0).getId());
		Assert.assertEquals("After 1 call rev counter should be 1", 1, revCounter);
		revCounter = projectsRepository.getRevCounter(this.testProjects.get(0).getId());
		Assert.assertEquals("After 2 calls rev counter should be 2", 2, revCounter);

		for(int i=0; i<10; i++) {
			revCounter = projectsRepository.getRevCounter(this.testProjects.get(0).getId());
		}
		
		Assert.assertEquals("After 10 more calls rev counter should be 12", 12, revCounter);
		
		Project p = projectsRepository.findOne(this.testProjects.get(0).getId());
		
		Assert.assertEquals("next rev counter should be 13", 13, p.getRevCounter());
		
		p = projectsRepository.findOne(this.testProjects.get(1).getId());
		
		Assert.assertEquals("other project next rev counter should be 1", 1, p.getRevCounter());
		
	}
	
	
	@Test
	public void testIncrementNonExisting() {
		
		int revCounter = projectsRepository.getRevCounter("aaaa");
		Assert.assertEquals("Rev counter non existing == -1", -1, revCounter);
		
	}
	
	@Test
	public void testIncrementNull() {
		
		int revCounter = projectsRepository.getRevCounter(null);
		Assert.assertEquals("Rev counter null == -1", -1, revCounter);
		
	}

	@Test
	public void testIncrementEmpty() {
		
		int revCounter = projectsRepository.getRevCounter("");
		Assert.assertEquals("Rev counter empty == -1", -1, revCounter);
		
	}
	
	@Test
	public void testDateCreation() {
		
		for (Project p : projectsRepository.findAll()) {
			Assert.assertEquals(this.creationDate, p.getCreated());
		} 
	}
	
	@Test
	public void testGetCountBySpace() {
		
		Map<String, Integer> groupRes = projectsRepository.getCountBySpace();
		
		Assert.assertNotNull("GroupRes not null", groupRes);
		Assert.assertEquals("Group res size", SPACE_NB-1, groupRes.size());
		
		for(int i=0;i<(SPACE_NB-1);i++) {
			Integer count = groupRes.get(this.spaceIds.get(i));
			Assert.assertNotNull("count not null", count);
			Assert.assertEquals("Nb of project/space", 2-i, count.intValue());
		}
		
		Assert.assertNull("Last space id has no project i.e count is null", groupRes.get(this.spaceIds.get(SPACE_NB-1)));		
	}
	
	@Test
	public void testGetCountBySpaceFilter() {
		
		List<String> spacesIdsFilter = Arrays.asList(this.spaceIds.get(0));
		
		Map<String, Integer> groupRes = projectsRepository.getCountBySpace(spacesIdsFilter);
		
		Assert.assertNotNull("GroupRes not null", groupRes);
		Assert.assertEquals("Group res size", 1, groupRes.size());
		
		Integer count = groupRes.get(this.spaceIds.get(0));
		Assert.assertNotNull("count not null", count);
		Assert.assertEquals("Nb of project/space", 2, count.intValue());
		
		for(int i=1; i<SPACE_NB; i++) {
			Assert.assertNull("other space id has no project i.e count is null", groupRes.get(this.spaceIds.get(i)));
		}
	}
	
	@Test
	public void testCopyProjectCreation() {
		DBCollection coll = mongoTemplate.getCollection(mongoTemplate.getCollectionName(Project.class));
		
		DBObject filter = new BasicDBObject();
		filter.put("_id", copyProject.getId());
		
		DBCursor resFind = coll.find(filter);
		
		Assert.assertEquals("The project must be found", 1, resFind.count());
		
		for(DBObject obj: coll.find(filter)) {
			Assert.assertEquals("id must be equal", copyProject.getId(), obj.get("_id"));
		}
	}
	
	@Test
	public void testCopyProjectCopy() {
		
		Project newProject = this.projectsRepository.copy(this.copyProject, "copy_project");
		
		DBCollection coll = mongoTemplate.getCollection(mongoTemplate.getCollectionName(Project.class));
		
		DBObject filter = new BasicDBObject();
		filter.put("_id", newProject.getId());
		
		DBCursor resFind = coll.find(filter);
		
		Assert.assertEquals("The project must be found", 1, resFind.count());
		
		for(DBObject obj: resFind) {
			Assert.assertEquals("id must be equal", newProject.getId(), obj.get("_id"));
			Assert.assertEquals("title must be must be copy_project", "copy_project", obj.get("title"));
			Assert.assertEquals("space_id must be must be same than copyProject", this.copyProject.getSpaceId(), obj.get("space_id"));
		}
		
		
		filter = new BasicDBObject();
		filter.put("project_id", newProject.getId());
		
		DBCollection nodesColl = mongoTemplate.getCollection(mongoTemplate.getCollectionName(Node.class));
		resFind = nodesColl.find(filter);
		
		Assert.assertEquals("Must have 3 nodes", this.copyProject.getNodes().size(), resFind.count());
		
		for(DBObject obj: resFind) {
			// find node with same title in copy project
			Node originalNode = null;
			for(Node n: this.copyProject.getNodes()) {
				if(n.getTitle().equals(obj.get("title"))) {
					originalNode = n;
					break;
				}
			}
			
			Assert.assertNotNull("Must fincd original Node", originalNode);
			
			//color, desc, id diff, uri, created_by, image, position, diff projectif, size
			Assert.assertNotSame("id must be differents", originalNode.getId(), obj.get("_id"));
			Assert.assertNotSame("project id must be differents", originalNode.getProjectId(), obj.get("project_id"));

			Assert.assertEquals("same color", originalNode.getColor(), obj.get("color"));
			Assert.assertEquals("same desc", originalNode.getDescription(), obj.get("description"));
			Assert.assertEquals("same uri", originalNode.getUri(), obj.get("uri"));
			Assert.assertEquals("same image", originalNode.getImage(), obj.get("image"));
			Assert.assertEquals("same position", originalNode.getPosition().getX(), ((DBObject)obj.get("position")).get("x"));
			Assert.assertEquals("same position", originalNode.getPosition().getY(), ((DBObject)obj.get("position")).get("y"));
			Assert.assertEquals("same size", originalNode.getSize(), obj.get("size"));			
			
		}
		
		DBCollection edgesColl = mongoTemplate.getCollection(mongoTemplate.getCollectionName(Edge.class));
		resFind = edgesColl.find(filter);
		
		Assert.assertEquals("Must have 3 edges", this.copyProject.getEdges().size(), resFind.count());
		
		for(DBObject obj: resFind) {
			// find node with same title in copy project
			Edge originalEdge = null;
			for(Edge e: this.copyProject.getEdges()) {
				if(e.getTitle().equals(obj.get("title"))) {
					originalEdge = e;
					break;
				}
			}
			
			Assert.assertNotNull("Must find original Node", originalEdge);
			
			//color, desc, id diff, uri, created_by, image, position, diff projectif, size
			Assert.assertFalse("id must be differents", originalEdge.getId().equals(obj.get("_id")));
			Assert.assertFalse("project id must be differents", originalEdge.getProjectId().equals(obj.get("project_id")));

			Assert.assertEquals("same color", originalEdge.getColor(), obj.get("color"));
			Assert.assertEquals("same desc", originalEdge.getDescription(), obj.get("description"));
			Assert.assertEquals("same uri", originalEdge.getUri(), obj.get("uri"));
			
			
			// get FromNode
			DBObject fromNode = nodesColl.findOne(((DBRef)obj.get("from")).getId());
			Assert.assertNotNull("fromNode must exits ", fromNode);
			Assert.assertFalse("must be different from node", originalEdge.getFrom().equals(fromNode.get("_id")));
			Assert.assertEquals("same from title", originalEdge.getFromNode().getTitle(), fromNode.get("title"));
			
			DBObject toNode = nodesColl.findOne(((DBRef)obj.get("to")).getId());
			Assert.assertNotNull("toNode must exits", toNode);
			Assert.assertFalse("must be different to node", originalEdge.getTo().equals(toNode.get("_id")));
			Assert.assertEquals("same to title", originalEdge.getToNode().getTitle(), toNode.get("title"));
			
		}		
	}
	
	@Test
	public void testDeleteRecursive() {
		
		List<Node> nodes= copyProject.getNodes();
		List<Edge> edges= copyProject.getEdges();
		
		this.projectsRepository.deleteRecursive(copyProject);
		
		DBObject filter = new BasicDBObject();
		filter.put("_id", copyProject.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", copyProject.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);
		}
		
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void testDeleteRecursiveProjectRevision() {
		
		List<Node> nodes= copyProject.getNodes();
		List<Edge> edges= copyProject.getEdges();
		
		int revCounter = copyProject.getRevCounter();
		
		this.projectsRepository.deleteRecursive(copyProject);

		DBCollection coll = mongoTemplate.getCollection(mongoTemplate.getCollectionName(ProjectRevision.class));
		
		DBObject filter = new BasicDBObject();
		filter.put("project", new DBRef(mongoTemplate.getDb(), "projects", copyProject.getId()));
		filter.put("revision", new Integer(revCounter));
		
		DBObject revision = coll.findOne(filter);
		
		Assert.assertNotNull("Must find revision", revision);
		
		for(DBObject nobj: ((Iterable<DBObject>)revision.get("nodes"))) {
			Node node = null;
			for(Node n: nodes) {
				if(n.getId().equals(nobj.get("_id"))) {
					node = n;
					break;
				}
			}
			Assert.assertNotNull("Node must be in the original list", node);
			Assert.assertEquals("Nodes must have same title", node.getTitle(), nobj.get("title"));
		}
		
		for(DBObject eobj: ((Iterable<DBObject>)revision.get("edges"))) {
			Edge edge = null;
			for(Edge e: edges) {
				if(e.getId().equals(eobj.get("_id"))) {
					edge = e;
					break;
				}
			}
			Assert.assertNotNull("Edge must be in the original list", edge);
			Assert.assertEquals("Edges must have same title", edge.getTitle(), eobj.get("title"));
		}
		
	}
	
	@Test
	public void testFindBySpaceIdAndTitleRegex() {
		
		List<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*");
		
		Assert.assertEquals("res must have length " + (SPACE_NB -1) , SPACE_NB-1, res.size());
		for(Project p: res) {
			Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
			Assert.assertNotNull("project title must not be null", p.getTitle());
			Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
		}		
	}
	
	@Test
	public void testFindBySpaceIdAndTitleRegexBad() {
		
		List<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*foo.*");
		
		Assert.assertEquals("res must have zero length", 0, res.size());
	}
	
	@Test
	public void testFindBySpaceIdAndTitleRegexPageable() {
		
		PageRequest pr = new PageRequest(0, 1);
		
		Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
		
		Assert.assertEquals("res must have length 1", 1, res.getNumberOfElements());
		for(Project p: res) {
			Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
			Assert.assertNotNull("project title must not be null", p.getTitle());
			Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
		}		
	}
	
	@Test
	public void testFindBySpaceIdAndTitleRegexPageableBadSize() {
		
		PageRequest pr = new PageRequest(0, 3);
		
		Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
		
		Assert.assertEquals("res must have length 2", 2, res.getNumberOfElements());
		for(Project p: res) {
			Assert.assertEquals("project must belong to the same space", this.spaceIds.get(0), p.getSpaceId());
			Assert.assertNotNull("project title must not be null", p.getTitle());
			Assert.assertTrue("project tilte must contains title", p.getTitle().matches(".*project.*"));
		}		
	}	
	
	@Test
	public void testFindBySpaceIdAndTitleRegexPageableBadPage() {
		
		PageRequest pr = new PageRequest(1, 3);
		
		Page<Project> res = this.projectsRepository.findBySpaceIdAndTitleRegex(this.spaceIds.get(0), ".*Project.*", pr);
		
		Assert.assertEquals("res must have length 0", 0, res.getNumberOfElements());
	}
}
