'use strict';

const assert = require('assert');
const nconf = require('nconf');
const util = require('util');

const db = require('../mocks/databasemock');

const helpers = require('../helpers');

const meta = require('../../src/meta');
const user = require('../../src/user');
const groups = require('../../src/groups');
const plugins = require('../../src/plugins');
const utils = require('../../src/utils');

describe('email confirmation (library methods)', () => {
	let uid;
	async function dummyEmailerHook(data) {
		// pretend to handle sending emails
	}

	before(() => {
		// Attach an emailer hook so related requests do not error
		plugins.hooks.register('emailer-test', {
			hook: 'filter:email.send',
			method: dummyEmailerHook,
		});
	});

	beforeEach(async () => {
		uid = await user.create({
			username: utils.generateUUID().slice(0, 10),
			password: utils.generateUUID(),
		});
	});

	after(async () => {
		plugins.hooks.unregister('emailer-test', 'filter:email.send');
	});

	describe('isValidationPending', () => {
		it('should return false if user did not request email validation', async () => {
			const pending = await user.email.isValidationPending(uid);

			assert.strictEqual(pending, false);
		});

		it('should return false if user did not request email validation (w/ email checking)', async () => {
			const email = 'test@example.org';
			const pending = await user.email.isValidationPending(uid, email);

			assert.strictEqual(pending, false);
		});

		it('should return true if user requested email validation', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			const pending = await user.email.isValidationPending(uid);

			assert.strictEqual(pending, true);
		});

		it('should return true if user requested email validation (w/ email checking)', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			const pending = await user.email.isValidationPending(uid, email);

			assert.strictEqual(pending, true);
		});
	});

	describe('getValidationExpiry', () => {
		it('should return null if there is no validation available', async () => {
			const expiry = await user.email.getValidationExpiry(uid);

			assert.strictEqual(expiry, null);
		});

		it('should return a number smaller than configured expiry if validation available', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			const expiry = await user.email.getValidationExpiry(uid);

			assert(isFinite(expiry));
			assert(expiry > 0);
			assert(expiry <= meta.config.emailConfirmExpiry * 24 * 60 * 60 * 1000);
		});
	});

	describe('expireValidation', () => {
		it('should invalidate any confirmation in-progress', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			await user.email.expireValidation(uid);

			assert.strictEqual(await user.email.isValidationPending(uid), false);
			assert.strictEqual(await user.email.isValidationPending(uid, email), false);
			assert.strictEqual(await user.email.canSendValidation(uid, email), true);
		});
	});

	describe('canSendValidation', () => {
		it('should return true if no validation is pending', async () => {
			const ok = await user.email.canSendValidation(uid, 'test@example.com');

			assert(ok);
		});

		it('should return false if it has been too soon to re-send confirmation', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			const ok = await user.email.canSendValidation(uid, email);

			assert.strictEqual(ok, false);
		});

		it('should return true if it has been long enough to re-send confirmation', async () => {
			const email = 'test@example.org';
			await user.email.sendValidationEmail(uid, {
				email,
			});
			const code = await db.get(`confirm:byUid:${uid}`);
			await db.setObjectField(`confirm:${code}`, 'expires', Date.now() + 1000);
			const ok = await user.email.canSendValidation(uid, email);
			assert(ok);
		});
	});
});

describe('email confirmation (v3 api)', () => {
	let userObj;
	let jar;
	const register = data => new Promise((resolve, reject) => {
		helpers.registerUser(data, (err, jar, response, body) => {
			if (err) {
				return reject(err);
			}

			resolve({ jar, response, body });
		});
	});

	before(async () => {
		// If you're running this file directly, uncomment these lines
		await register({
			username: 'fake-user',
			password: 'derpioansdosa',
			email: 'b@c.com',
			gdpr_consent: true,
		});

		({ body: userObj, jar } = await register({
			username: 'email-test',
			password: 'abcdef',
			email: 'test@example.org',
			gdpr_consent: true,
		}));
	});

	it('should have a pending validation', async () => {
		const code = await db.get(`confirm:byUid:${userObj.uid}`);
		assert.strictEqual(await user.email.isValidationPending(userObj.uid, 'test@example.org'), true);
	});

	it('should not list their email', async () => {
		const { res, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, {
			jar,
			json: true,
		});

		assert.strictEqual(res.statusCode, 200);
		assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{"emails":[]}}'));
	});

	it('should not allow confirmation if they are not an admin', async () => {
		const { res } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
			jar,
			json: true,
		});

		assert.strictEqual(res.statusCode, 403);
	});

	it('should not confirm an email that is not pending or set', async () => {
		await groups.join('administrators', userObj.uid);
		const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, {
			jar,
			json: true,
		});

		assert.strictEqual(res.statusCode, 404);
		await groups.leave('administrators', userObj.uid);
	});

	it('should confirm their email (using the pending validation)', async () => {
		await groups.join('administrators', userObj.uid);
		const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
			jar,
			json: true,
		});

		assert.strictEqual(res.statusCode, 200);
		assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
		await groups.leave('administrators', userObj.uid);
	});

	it('should still confirm the email (as email is set in user hash)', async () => {
		await user.email.remove(userObj.uid);
		await user.setUserField(userObj.uid, 'email', 'test@example.org');
		({ jar } = await helpers.loginUser('email-test', 'abcdef')); // email removal logs out everybody
		await groups.join('administrators', userObj.uid);

		const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
			jar,
			json: true,
		});

		assert.strictEqual(res.statusCode, 200);
		assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
		await groups.leave('administrators', userObj.uid);
	});
});