package org.iri_research.renkan.test.repositories;

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

import org.iri_research.renkan.models.Group;
import org.iri_research.renkan.models.User;
import org.iri_research.renkan.repositories.GroupsRepository;
import org.iri_research.renkan.repositories.UsersRepository;
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.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.mongodb.DBCollection;
import com.mongodb.DBObject;

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

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

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

    @Autowired
    private UsersRepository usersRepository;
    @Autowired
    private GroupsRepository groupsRepository;

    
    @Autowired
    private MongoTemplate mongoTemplate;

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

    
    @Before
    public void setup() {

        logger.debug("Setup");
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        usersRepository.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);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        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);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }

    @Test
    public void testGroupFieldMapping() {
        // query json directly with mongodb driver
        // check field values
        DBCollection coll = mongoTemplate.getCollection(mongoTemplate
                .getCollectionName(Group.class));

        for (DBObject obj : coll.find()) {
            Assert.assertTrue("mongo object must have _id field",
                    obj.containsField("_id"));

            String id = obj.get("_id").toString();

            Group group = this.groupsList.get(id);

            Assert.assertTrue("mongo object must have title field",
                    obj.containsField("title"));
            Assert.assertEquals("Titles must be the same",
                    group.getTitle(), obj.get("title"));

            Assert.assertTrue("mongo object must have description field",
                    obj.containsField("description"));
            Assert.assertEquals("description must be the same",
                    group.getDescription(), obj.get("description"));

            Assert.assertTrue("mongo object must have color field",
                    obj.containsField("color"));
            Assert.assertEquals("Color must be the same", group.getColor(), obj.get("color"));

            Assert.assertTrue("mongo object must have uri field",
                    obj.containsField("uri"));
            Assert.assertEquals("Uri must be the same", group.getUri(), obj.get("uri"));
            
            Assert.assertTrue("mongo object must have avatar field",
                    obj.containsField("avatar"));
            Assert.assertEquals("Avater must be the same", group.getAvatar(), obj.get("avatar"));

        }
    }
    
    @Test
    public void testSetUsersList() {

        Group group = this.groupsList.get(this.groupsUuids.get(0));
        
        List<String> userIds = this.usersUuids;
        
        this.groupsRepository.setUsersList(group, userIds);
        
        //reload user
        group = this.groupsRepository.findOne(group.getId());
        
        Assert.assertEquals("group user list is big enough", userIds.size(), group.getUsers().size());
        Assert.assertTrue("Group user list contains all users", group.getUsers().containsAll(userIds));

        for (User u : this.usersRepository.findAll(userIds)) {
            Assert.assertEquals(String.format("user list for user %s must be size 1", u.getId()), 1, u.getGroups().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        }
    }
    

    @Test
    public void testAddUsersList() {
        Group group = this.groupsList.get(this.groupsUuids.get(1));
        
        List<String> userIds = this.usersUuids.subList(0, 1);
        this.groupsRepository.setUsersList(group, userIds);
        
        group = this.groupsRepository.findOne(group.getId());
        
        Assert.assertEquals("group users is big enough", userIds.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(userIds));
        
        User user = this.usersRepository.findOne(userIds.get(0));
        Assert.assertEquals(String.format("group list for user %s must be size 1", user.getId()), 1, user.getGroups().size());
        Assert.assertTrue(String.format("group list for user %s must contains group %s", user.getId(), group.getId()), user.getGroups().contains(group.getId()));
        
        this.groupsRepository.setUsersList(group, this.usersUuids);
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        Assert.assertEquals("group user list is big enough", this.usersUuids.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(this.usersUuids));

        for (User u : this.usersRepository.findAll(this.usersUuids)) {
            Assert.assertEquals(String.format("group list for user %s must be size 1", u.getId()), 1, u.getGroups().size());
            Assert.assertTrue(String.format("group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        }

    }
    
    @Test
    public void testRemoveUsersList() {
        Group group = this.groupsList.get(this.groupsUuids.get(2));
        
        this.groupsRepository.setUsersList(group, this.usersUuids);
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        Assert.assertEquals("Group user list is big enough", this.usersUuids.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(this.usersUuids));
        
        for(User user : this.usersRepository.findAll(this.usersUuids)) {
            Assert.assertEquals(String.format("group list for user %s must be size 1", user.getId()), 1, user.getGroups().size());
            Assert.assertTrue(String.format("group list for user %s must contains group %s", user.getId(), group.getId()), user.getGroups().contains(group.getId()));
        }
        
        this.groupsRepository.setUsersList(group, this.usersUuids.subList(USER_NB-1, USER_NB));
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        Assert.assertEquals("group user list is big enough", 1, group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().contains(this.usersUuids.get(USER_NB-1)));

        User u = this.usersRepository.findOne(this.usersUuids.get(USER_NB-1));
        Assert.assertEquals(String.format("group list for user %s must be size 1", u.getId()), 1, u.getGroups().size());
        Assert.assertTrue(String.format("group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        
        for(User otherUser: this.usersRepository.findAll(this.usersUuids.subList(0, USER_NB-2))) {
            Assert.assertEquals(String.format("group list for user %s must be size 0", otherUser.getId()), 0, otherUser.getGroups().size());
        }

    }


    @Test
    public void testAddUsersListExisting() {
        
       // get first group
        Group group = this.groupsList.get(this.groupsUuids.get(0));
        
        List<String> userIds = this.usersUuids;
        
        // set all users for first group
        this.groupsRepository.setUsersList(group, userIds);
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        //check that group has all users
        Assert.assertEquals("Group user list is big enough", userIds.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(userIds));

        // and that users have all new group
        for (User u : this.usersRepository.findAll(userIds)) {
            Assert.assertEquals(String.format("Group list for user %s must be size 1", u.getId()), 1, u.getGroups().size());
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        }

        
        // get second group
        group = this.groupsList.get(this.groupsUuids.get(1));
        
        //first set one user
        List<String> secondUserIds = this.usersUuids.subList(0, 1);
        this.groupsRepository.setUsersList(group, secondUserIds);
        
        // reload group
        group = this.groupsRepository.findOne(group.getId());
        
        // check that second group has all users
        Assert.assertEquals("Group user list is big enough", secondUserIds.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all users", group.getUsers().containsAll(secondUserIds));
        
        // check that group has new and old user
        User user = this.usersRepository.findOne(secondUserIds.get(0));
        Assert.assertEquals(String.format("Group list for user %s must be size 2", user.getId()), 2, user.getGroups().size());
        Assert.assertTrue(String.format("Group list for user %s must contains group %s", user.getId(), group.getId()), user.getGroups().contains(group.getId()));
        Assert.assertTrue(String.format("Group list for user %s must contains group %s", user.getId(), groupsUuids.get(0)), user.getGroups().contains(groupsUuids.get(0)));
        
        // set all new users list for second group
        this.groupsRepository.setUsersList(group, this.usersUuids);
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        //check that group 2 has all users 
        Assert.assertEquals("Group user list is big enough", this.usersUuids.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(this.usersUuids));

        // check that all users had group 1 and group 2
        for (User u : this.usersRepository.findAll(this.usersUuids)) {
            Assert.assertEquals(String.format("Group list for user %s must be size 1", u.getId()), 2, u.getGroups().size());
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), groupsUuids.get(0)), u.getGroups().contains(groupsUuids.get(0)));
        }

    }

    
    @Test
    public void testRemoveGroupListExisting() {
        
        // get first group
        Group group = this.groupsList.get(this.groupsUuids.get(0));
        
        List<String> userIds = this.usersUuids;
        
        // set all users for first group
        this.groupsRepository.setUsersList(group, userIds);
        
        //reload group
        group = this.groupsRepository.findOne(group.getId());
        
        //check that group has all users
        Assert.assertEquals("Group users list is big enough", userIds.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all user", group.getUsers().containsAll(userIds));

        // and that users have all new group
        for (User u : this.usersRepository.findAll(userIds)) {
            Assert.assertEquals(String.format("Group list for user %s must be size 1", u.getId()), 1, u.getGroups().size());
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        }

        // get 3rd group
        group = this.groupsList.get(this.groupsUuids.get(2));
        
        // set all users for 3rd group
        this.groupsRepository.setUsersList(group, this.usersUuids);
        
        //reload 3rd group
        group = this.groupsRepository.findOne(group.getId());
        
        //check that all users are set for 3rd group
        Assert.assertEquals("Group users list is big enough", this.usersUuids.size(), group.getUsers().size());
        Assert.assertTrue("User list contains all users", group.getUsers().containsAll(this.usersUuids));
        
        //check that all users has 3rd group and 1st group
        for(User user : this.usersRepository.findAll(this.usersUuids)) {
            Assert.assertEquals(String.format("Group list for user %s must be size 2", user.getId()), 2, user.getGroups().size());
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", user.getId(), group.getId()), user.getGroups().contains(group.getId()));
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", user.getId(), this.groupsUuids.get(0)), user.getGroups().contains(this.groupsUuids.get(0)));
        }
        
        //set new user list for 3rd group
        this.groupsRepository.setUsersList(group, this.usersUuids.subList(USER_NB-1, USER_NB));
        
        //reload 3rd group
        group = this.groupsRepository.findOne(group.getId());
        
        //check that 3rd group has only one user (last user)
        Assert.assertEquals("Group users list is big enough", 1, group.getUsers().size());
        Assert.assertTrue("User list contains all users", group.getUsers().contains(this.usersUuids.get(USER_NB-1)));

        // check that last user has 3rd and 1rst group
        User u = this.usersRepository.findOne(this.usersUuids.get(USER_NB-1));
        Assert.assertEquals(String.format("Group list for user %s must be size 1", u.getId()), 2, u.getGroups().size());
        Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), group.getId()), u.getGroups().contains(group.getId()));
        Assert.assertTrue(String.format("Group list for user %s must contains group %s", u.getId(), this.groupsUuids.get(0)), u.getGroups().contains(this.groupsUuids.get(0)));
        
        // check that other users has only 1rst group 
        for(User otherUser: this.usersRepository.findAll(this.usersUuids.subList(0, USER_NB-2))) {
            Assert.assertEquals(String.format("Group list for user %s must be size 0", otherUser.getId()), 1, otherUser.getGroups().size());
            Assert.assertTrue(String.format("Group list for user %s must contains group %s", otherUser.getId(), this.groupsUuids.get(0)), otherUser.getGroups().contains(this.groupsUuids.get(0)));
        }

    }

    @Test
    public void testGetUsersMap() {
        
        Group group = this.groupsList.get(this.groupsUuids.get(0));
        
        List<String> userIds = this.usersUuids;
        
        this.groupsRepository.setUsersList(group, userIds);
        
        
        Map<String, User> usersMap = this.groupsRepository.getUsersMap(group);
        
        Assert.assertEquals ("user map should have same length than users list", this.usersUuids.size(), usersMap.size());
        Assert.assertTrue("user map should contains all uuids", this.usersUuids.containsAll(usersMap.keySet()));
        for (Entry<String, User> entry : usersMap.entrySet()) {
            User user = entry.getValue();
            String userId = entry.getKey();
            Assert.assertTrue("user id in user uuid", this.usersUuids.contains(userId));
            Assert.assertEquals("key id and value user id must be the same", userId, user.getId());
        }

    }
    

    @After
    public void teardown() {
        this.usersRepository.deleteAll();
        this.groupsRepository.deleteAll();
    }

}
