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.TimeZone;
import java.util.UUID;
import java.util.Map.Entry;

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 UsersRepositoryTest {

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

    private Logger logger = LoggerFactory.getLogger(UsersRepositoryTest.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 testUserFieldMapping() {
        // query json directly with mongodb driver
        // check field values
        DBCollection coll = mongoTemplate.getCollection(mongoTemplate
                .getCollectionName(User.class));

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

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

            User user = this.usersList.get(id);

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

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

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

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

            Assert.assertTrue("mongo object must have locked field",
                    obj.containsField("locked"));
            Assert.assertEquals("Locked must be the same", user.isLocked(), obj.get("locked"));

            Assert.assertTrue("mongo object must have activated field",
                    obj.containsField("enabled"));
            Assert.assertEquals("Enabled must be the same", user.isEnabled(), obj.get("enabled"));

            Assert.assertTrue("mongo object must have expiration_date field",
                    obj.containsField("expiration_date"));
            Assert.assertEquals("expiration_date must be the same", user.getExpirationDate(), new LocalDate(obj.get("expiration_date")));

            Assert.assertTrue("mongo object must have credentials_expiration_date field",
                    obj.containsField("credentials_expiration_date"));
            Assert.assertEquals("credentials_expiration_date by must be the same", user.getCredentialsExpirationDate(), new LocalDate(obj.get("credentials_expiration_date")));

            Assert.assertTrue("mongo object must have email field",
                    obj.containsField("email"));
            Assert.assertEquals("Email must be the same", user.getEmail(), obj.get("email"));

            
        }
    }
    
    @Test
    public void testSetGroupsList() {

        User user = this.usersList.get(this.usersUuids.get(0));
        
        List<String> groupIds = this.groupsUuids;
        
        this.usersRepository.setGroupsList(user, groupIds);
        
        //reload user
        user = this.usersRepository.findOne(user.getId());
        
        Assert.assertEquals("user group is big enough", groupIds.size(), user.getGroups().size());
        Assert.assertTrue("Group list contains all group", user.getGroups().containsAll(groupIds));

        for (Group g : this.groupsRepository.findAll(groupIds)) {
            Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 1, g.getUsers().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        }
    }
    

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

        for (Group g : this.groupsRepository.findAll(this.groupsUuids)) {
            Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 1, g.getUsers().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        }

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

        Group g = this.groupsRepository.findOne(this.groupsUuids.get(GROUP_NB-1));
        Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 1, g.getUsers().size());
        Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        
        for(Group otherGroup: this.groupsRepository.findAll(this.groupsUuids.subList(0, GROUP_NB-2))) {
            Assert.assertEquals(String.format("user list for group %s must be size 0", otherGroup.getId()), 0, otherGroup.getUsers().size());
        }

    }
    
    @Test
    public void testAddGroupsListExisting() {
        
       // get first user
        User user = this.usersList.get(this.usersUuids.get(0));
        
        List<String> groupIds = this.groupsUuids;
        
        // set all groups for first user
        this.usersRepository.setGroupsList(user, groupIds);
        
        //reload user
        user = this.usersRepository.findOne(user.getId());
        
        //check that user has all group
        Assert.assertEquals("user group is big enough", groupIds.size(), user.getGroups().size());
        Assert.assertTrue("Group list contains all group", user.getGroups().containsAll(groupIds));

        // and that groups have all new user
        for (Group g : this.groupsRepository.findAll(groupIds)) {
            Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 1, g.getUsers().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        }

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

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

    }

    @Test
    public void testRemoveGroupsListExisting() {
        
       // get first user
        User user = this.usersList.get(this.usersUuids.get(0));
        
        List<String> groupIds = this.groupsUuids;
        
        // set all groups for first user
        this.usersRepository.setGroupsList(user, groupIds);
        
        //reload user
        user = this.usersRepository.findOne(user.getId());
        
        //check that user has all group
        Assert.assertEquals("user group is big enough", groupIds.size(), user.getGroups().size());
        Assert.assertTrue("Group list contains all group", user.getGroups().containsAll(groupIds));

        // and that groups have all new user
        for (Group g : this.groupsRepository.findAll(groupIds)) {
            Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 1, g.getUsers().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        }

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

        // check that last group has 3rd and 1rst user
        Group g = this.groupsRepository.findOne(this.groupsUuids.get(GROUP_NB-1));
        Assert.assertEquals(String.format("user list for group %s must be size 1", g.getId()), 2, g.getUsers().size());
        Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), user.getId()), g.getUsers().contains(user.getId()));
        Assert.assertTrue(String.format("user list for group %s must contains user %s", g.getId(), this.usersUuids.get(0)), g.getUsers().contains(this.usersUuids.get(0)));
        
        // check that other groups has only 1rst user 
        for(Group otherGroup: this.groupsRepository.findAll(this.groupsUuids.subList(0, GROUP_NB-2))) {
            Assert.assertEquals(String.format("user list for group %s must be size 0", otherGroup.getId()), 1, otherGroup.getUsers().size());
            Assert.assertTrue(String.format("user list for group %s must contains user %s", otherGroup.getId(), this.usersUuids.get(0)), otherGroup.getUsers().contains(this.usersUuids.get(0)));
        }

    }
    
    @Test
    public void testGetUsersMap() {
        
        User user = this.usersList.get(this.usersUuids.get(0));
        
        List<String> groupdIds = this.groupsUuids;
        
        this.usersRepository.setGroupsList(user, groupdIds);
        
        
        Map<String, Group> groupsMap = this.usersRepository.getGroupsMap(user);
        
        Assert.assertEquals ("Group map should have same length than groups list", this.groupsUuids.size(), groupsMap.size());
        Assert.assertTrue("Group map should contains all uuids", this.groupsUuids.containsAll(groupsMap.keySet()));
        for (Entry<String, Group> entry : groupsMap.entrySet()) {
            Assert.assertTrue("user id in user uuid", this.groupsUuids.contains(entry.getKey()));
            Assert.assertEquals("key id and value user id must be the same", entry.getKey(), entry.getValue().getId());
        }
    }
    

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

}
