'use strict';
/*global require, before, after*/

var assert = require('assert');
var async = require('async');

var db = require('./mocks/databasemock');
var Groups = require('../src/groups');
var User = require('../src/user');

describe('Groups', function () {
	var adminUid;
	var testUid;
	before(function (done) {
		Groups.resetCache();
		async.series([
			function (next) {
				// Create a group to play around with
				Groups.create({
					name: 'Test',
					description: 'Foobar!'
				}, next);
			},
			function (next) {
				Groups.create({
					name: 'PrivateNoJoin',
					description: 'Private group',
					private: 1,
					disableJoinRequests: 1
				}, next);
			},
			function (next) {
				Groups.create({
					name: 'PrivateCanJoin',
					description: 'Private group',
					private: 1,
					disableJoinRequests: 0
				}, next);
			},
			function (next) {
				// Create a new user
				User.create({
					username: 'testuser',
					email: 'b@c.com'
				}, next);
			},
			function (next) {
				User.create({
					username: 'admin',
					email: 'admin@admin.com'
				}, next);
			},
			function (next) {
				// Also create a hidden group
				Groups.join('Hidden', 'Test', next);
			}
		], function (err, results) {
			assert.ifError(err);
			testUid = results[3];
			adminUid = results[4];
			Groups.join('administrators', adminUid, done);
		});
	});

	describe('.list()', function () {
		it('should list the groups present', function (done) {
			Groups.getGroupsFromSet('groups:createtime', 0, 0, -1, function (err, groups) {
				assert.ifError(err);
				assert.equal(groups.length, 6);
				done();
			});
		});
	});

	describe('.get()', function () {
		before(function (done) {
			Groups.join('Test', testUid, done);
		});

		it('with no options, should show group information', function (done) {
			Groups.get('Test', {}, function (err, groupObj) {
				assert.ifError(err);
				assert.equal(typeof groupObj, 'object');
				assert(Array.isArray(groupObj.members));
				assert.strictEqual(groupObj.name, 'Test');
				assert.strictEqual(groupObj.description, 'Foobar!');
				assert.strictEqual(groupObj.memberCount, 1);
				assert.equal(typeof groupObj.members[0], 'object');

				done();
			});
		});
	});

	describe('.search()', function () {
		it('should return the "Test" group when searched for', function (done) {
			Groups.search('test', {}, function (err, groups) {
				assert.ifError(err);
				assert.equal(1, groups.length);
				assert.strictEqual('Test', groups[0].name);
				done();
			});
		});
	});

	describe('.isMember()', function () {
		it('should return boolean true when a user is in a group', function (done) {
			Groups.isMember(1, 'Test', function (err, isMember) {
				assert.ifError(err);
				assert.strictEqual(isMember, true);
				done();
			});
		});

		it('should return boolean false when a user is not in a group', function (done) {
			Groups.isMember(2, 'Test', function (err, isMember) {
				assert.ifError(err);
				assert.strictEqual(isMember, false);
				done();
			});
		});
	});

	describe('.isMemberOfGroupList', function () {
		it('should report that a user is part of a groupList, if they are', function (done) {
			Groups.isMemberOfGroupList(1, 'Hidden', function (err, isMember) {
				assert.ifError(err);
				assert.strictEqual(isMember, true);
				done();
			});
		});

		it('should report that a user is not part of a groupList, if they are not', function (done) {
			Groups.isMemberOfGroupList(2, 'Hidden', function (err, isMember) {
				assert.ifError(err);
				assert.strictEqual(isMember, false);
				done();
			});
		});
	});

	describe('.exists()', function () {
		it('should verify that the test group exists', function (done) {
			Groups.exists('Test', function (err, exists) {
				assert.ifError(err);
				assert.strictEqual(exists, true);
				done();
			});
		});

		it('should verify that a fake group does not exist', function (done) {
			Groups.exists('Derp', function (err, exists) {
				assert.ifError(err);
				assert.strictEqual(exists, false);
				done();
			});
		});

		it('should check if group exists using an array', function (done) {
			Groups.exists(['Test', 'Derp'], function (err, groupsExists) {
				assert.ifError(err);
				assert.strictEqual(groupsExists[0], true);
				assert.strictEqual(groupsExists[1], false);
				done();
			});
		});
	});

	describe('.create()', function () {
		it('should create another group', function (done) {
			Groups.create({
				name: 'foo',
				description: 'bar'
			}, function (err) {
				assert.ifError(err);
				Groups.get('foo', {}, done);
			});
		});

		it('should fail to create group with duplicate group name', function (done) {
			Groups.create({name: 'foo'}, function (err) {
				assert(err);
				assert.equal(err.message, '[[error:group-already-exists]]');
				done();
			});
		});
	});

	describe('.hide()', function () {
		it('should mark the group as hidden', function (done) {
			Groups.hide('foo', function (err) {
				assert.ifError(err);

				Groups.get('foo', {}, function (err, groupObj) {
					assert.ifError(err);
					assert.strictEqual(true, groupObj.hidden);
					done();
				});
			});
		});
	});

	describe('.update()', function () {
		before(function (done) {
			Groups.create({
				name: 'updateTestGroup',
				description: 'bar',
				system: 0,
				hidden: 0
			}, done);
		});

		it('should change an aspect of a group', function (done) {
			Groups.update('updateTestGroup', {
				description: 'baz'
			}, function (err) {
				assert.ifError(err);

				Groups.get('updateTestGroup', {}, function (err, groupObj) {
					assert.ifError(err);
					assert.strictEqual('baz', groupObj.description);
					done();
				});
			});
		});

		it('should rename a group if the name was updated', function (done) {
			Groups.update('updateTestGroup', {
				name: 'updateTestGroup?'
			}, function (err) {
				assert.ifError(err);

				Groups.get('updateTestGroup?', {}, function (err, groupObj) {
					assert.ifError(err);
					assert.strictEqual('updateTestGroup?', groupObj.name);
					assert.strictEqual('updatetestgroup', groupObj.slug);
					done();
				});
			});
		});
	});

	describe('.destroy()', function () {
		before(function (done) {
			Groups.join('foobar?', 1, done);
		});

		it('should destroy a group', function (done) {
			Groups.destroy('foobar?', function (err) {
				assert.ifError(err);

				Groups.get('foobar?', {}, function (err) {
					assert(err, 'Group still exists!');

					done();
				});
			});
		});

		it('should also remove the members set', function (done) {
			db.exists('group:foo:members', function (err, exists) {
				assert.ifError(err);

				assert.strictEqual(false, exists);

				done();
			});
		});
	});

	describe('.join()', function () {
		before(function (done) {
			Groups.leave('Test', testUid, done);
		});

		it('should add a user to a group', function (done) {
			Groups.join('Test', testUid, function (err) {
				assert.ifError(err);

				Groups.isMember(testUid, 'Test', function (err, isMember) {
					assert.ifError(err);
					assert.strictEqual(true, isMember);

					done();
				});
			});
		});
	});

	describe('.leave()', function () {
		it('should remove a user from a group', function (done) {
			Groups.leave('Test', testUid, function (err) {
				assert.ifError(err);

				Groups.isMember(testUid, 'Test', function (err, isMember) {
					assert.ifError(err);
					assert.strictEqual(false, isMember);

					done();
				});
			});
		});
	});

	describe('.leaveAllGroups()', function () {
		it('should remove a user from all groups', function (done) {
			Groups.leaveAllGroups(testUid, function (err) {
				assert.ifError(err);

				var	groups = ['Test', 'Hidden'];
				async.every(groups, function (group, next) {
					Groups.isMember(testUid, group, function (err, isMember) {
						assert.ifError(err);
						next(!isMember);
					});
				}, function (result) {
					assert(result);

					done();
				});
			});
		});
	});

	describe('.show()', function () {
		it('should make a group visible', function (done) {
			Groups.show('Test', function (err) {
				assert.ifError(err);
				assert.equal(arguments.length, 1);
				db.isSortedSetMember('groups:visible:createtime', 'Test', function (err, isMember) {
					assert.ifError(err);
					assert.strictEqual(isMember, true);
					done();
				});
			});
		});
	});

	describe('.hide()', function () {
		it('should make a group hidden', function (done) {
			Groups.hide('Test', function (err) {
				assert.ifError(err);
				assert.equal(arguments.length, 1);
				db.isSortedSetMember('groups:visible:createtime', 'Test', function (err, isMember) {
					assert.ifError(err);
					assert.strictEqual(isMember, false);
					done();
				});
			});
		});
	});


	describe('socket methods', function () {
		var socketGroups = require('../src/socket.io/groups');
		var meta = require('../src/meta');


		it('should error if data is null', function (done) {
			socketGroups.before({uid: 0}, 'groups.join', null, function (err) {
				assert.equal(err.message, '[[error:invalid-data]]');
				done();
			});
		});

		it('should not error if data is valid', function (done) {
			socketGroups.before({uid: 0}, 'groups.join', {}, function (err) {
				assert.ifError(err);
				done();
			});
		});

		it('should return error if not logged in', function (done) {
			socketGroups.join({uid: 0}, {}, function (err) {
				assert.equal(err.message, '[[error:invalid-uid]]');
				done();
			});
		});

		it('should return error if group name is special', function (done) {
			socketGroups.join({uid: adminUid}, {groupName: 'administrators'}, function (err) {
				assert.equal(err.message, '[[error:not-allowed]]');
				done();
			});
		});

		it('should error if group does not exist', function (done) {
			socketGroups.join({uid: adminUid}, {groupName: 'doesnotexist'}, function (err) {
				assert.equal(err.message, '[[error:no-group]]');
				done();
			});
		});

		it('should join test group', function (done) {
			meta.config.allowPrivateGroups = 0;
			socketGroups.join({uid: adminUid}, {groupName: 'Test'}, function (err) {
				assert.ifError(err);
				Groups.isMember(adminUid, 'Test', function (err, isMember) {
					assert.ifError(err);
					assert(isMember);
					done();
				});
			});
		});

		it('should error if not logged in', function (done) {
			socketGroups.leave({uid: 0}, {}, function (err) {
				assert.equal(err.message, '[[error:invalid-uid]]');
				done();
			});
		});

		it('should return error if group name is special', function (done) {
			socketGroups.leave({uid: adminUid}, {groupName: 'administrators'}, function (err) {
				assert.equal(err.message, '[[error:cant-remove-self-as-admin]]');
				done();
			});
		});

		it('should leave test group', function (done) {
			socketGroups.leave({uid: adminUid}, {groupName: 'Test'}, function (err) {
				assert.ifError(err);
				Groups.isMember('Test', adminUid, function (err, isMember) {
					assert.ifError(err);
					assert(!isMember);
					done();
				});
			});
		});

		it('should fail to join if group is private and join requests are disabled', function (done) {
			meta.config.allowPrivateGroups = 1;
			socketGroups.join({uid: testUid}, {groupName: 'PrivateNoJoin'}, function (err) {
				assert.equal(err.message, '[[error:join-requests-disabled]]');
				done();
			});
		});

		it('should join if user is admin', function (done) {
			socketGroups.join({uid: adminUid}, {groupName: 'PrivateCanJoin'}, function (err) {
				assert.ifError(err);
				Groups.isMember(adminUid, 'PrivateCanJoin', function (err, isMember) {
					assert.ifError(err);
					assert(isMember);
					done();
				});
			});
		});

		it('should request membership for regular user', function (done) {
			socketGroups.join({uid: testUid}, {groupName: 'PrivateCanJoin'}, function (err) {
				assert.ifError(err);
				Groups.isPending(testUid, 'PrivateCanJoin', function (err, isPending) {
					assert.ifError(err);
					assert(isPending);
					done();
				});
			});
		});

		it('should accept membership of user', function (done) {
			socketGroups.accept({uid: adminUid}, {groupName: 'PrivateCanJoin', toUid: testUid}, function (err) {
				assert.ifError(err);
				Groups.isMember(testUid, 'PrivateCanJoin', function (err, isMember) {
					assert.ifError(err);
					assert(isMember);
					done();
				});
			});
		});

		it('should grant ownership to user', function (done) {
			socketGroups.grant({uid: adminUid}, {groupName: 'PrivateCanJoin', toUid: testUid}, function (err) {
				assert.ifError(err);
				Groups.ownership.isOwner(testUid, 'PrivateCanJoin', function (err, isOwner) {
					assert.ifError(err);
					assert(isOwner);
					done();
				});
			});
		});

		it('should rescind ownership from user', function (done) {
			socketGroups.rescind({uid: adminUid}, {groupName: 'PrivateCanJoin', toUid: testUid}, function (err) {
				assert.ifError(err);
				Groups.ownership.isOwner(testUid, 'PrivateCanJoin', function (err, isOwner) {
					assert.ifError(err);
					assert(!isOwner);
					done();
				});
			});
		});

		it('should kick user from group', function (done) {
			socketGroups.kick({uid: adminUid}, {groupName: 'PrivateCanJoin', uid: testUid}, function (err) {
				assert.ifError(err);
				Groups.isMember(testUid, 'PrivateCanJoin', function (err, isMember) {
					assert.ifError(err);
					assert(!isMember);
					done();
				});
			});
		});

	});

	after(function (done) {
		db.emptydb(done);
	});
});