'use strict';

const validator = require('validator');

const db = require('../database');
const user = require('../user');
const utils = require('../utils');
const plugins = require('../plugins');

const intFields = ['timestamp', 'edited', 'fromuid', 'roomId', 'deleted', 'system'];

module.exports = function (Messaging) {
	Messaging.newMessageCutoff = 1000 * 60 * 3;

	Messaging.getMessagesFields = async (mids, fields) => {
		if (!Array.isArray(mids) || !mids.length) {
			return [];
		}

		const keys = mids.map(mid => 'message:' + mid);
		const messages = await (fields.length ? db.getObjectsFields(keys, fields) : db.getObjects(keys));

		return await Promise.all(messages.map(async (message, idx) => modifyMessage(message, fields, parseInt(mids[idx], 10))));
	};

	Messaging.getMessageField = async (mid, field) => {
		const fields = await Messaging.getMessageFields(mid, [field]);
		return fields ? fields[field] : null;
	};

	Messaging.getMessageFields = async (mid, fields) => {
		const messages = await Messaging.getMessagesFields([mid], fields);
		return messages ? messages[0] : null;
	};

	Messaging.setMessageField = async (mid, field, content) => {
		await db.setObjectField('message:' + mid, field, content);
	};

	Messaging.setMessageFields = async (mid, data) => {
		await db.setObject('message:' + mid, data);
	};

	Messaging.getMessagesData = async (mids, uid, roomId, isNew) => {
		let messages = await Messaging.getMessagesFields(mids, []);
		messages = await user.blocks.filter(uid, 'fromuid', messages);
		messages = messages
			.map(function (msg, idx) {
				if (msg) {
					msg.messageId = parseInt(mids[idx], 10);
					msg.ip = undefined;
				}
				return msg;
			})
			.filter(Boolean);

		const users = await user.getUsersFields(
			messages.map(msg => msg && msg.fromuid),
			['uid', 'username', 'userslug', 'picture', 'status', 'banned']
		);

		messages.forEach(function (message, index) {
			message.fromUser = users[index];
			message.fromUser.banned = !!message.fromUser.banned;
			message.fromUser.deleted = message.fromuid !== message.fromUser.uid && message.fromUser.uid === 0;

			var self = message.fromuid === parseInt(uid, 10);
			message.self = self ? 1 : 0;

			message.newSet = false;
			message.roomId = String(message.roomId || roomId);
			message.deleted = !!message.deleted;
			message.system = !!message.system;
		});

		messages = await Promise.all(messages.map(async (message) => {
			if (message.system) {
				message.content = validator.escape(String(message.content));
				message.cleanedContent = utils.stripHTMLTags(utils.decodeHTMLEntities(message.content));
				return message;
			}

			const result = await Messaging.parse(message.content, message.fromuid, uid, roomId, isNew);
			message.content = result;
			message.cleanedContent = utils.stripHTMLTags(utils.decodeHTMLEntities(result));
			return message;
		}));

		if (messages.length > 1) {
			// Add a spacer in between messages with time gaps between them
			messages = messages.map(function (message, index) {
				// Compare timestamps with the previous message, and check if a spacer needs to be added
				if (index > 0 && message.timestamp > messages[index - 1].timestamp + Messaging.newMessageCutoff) {
					// If it's been 5 minutes, this is a new set of messages
					message.newSet = true;
				} else if (index > 0 && message.fromuid !== messages[index - 1].fromuid) {
					// If the previous message was from the other person, this is also a new set
					message.newSet = true;
				} else if (index === 0) {
					message.newSet = true;
				}

				return message;
			});
		} else if (messages.length === 1) {
			// For single messages, we don't know the context, so look up the previous message and compare
			var key = 'uid:' + uid + ':chat:room:' + roomId + ':mids';
			const index = await db.sortedSetRank(key, messages[0].messageId);
			if (index > 0) {
				const mid = await db.getSortedSetRange(key, index - 1, index - 1);
				const fields = await Messaging.getMessageFields(mid, ['fromuid', 'timestamp']);
				if ((messages[0].timestamp > fields.timestamp + Messaging.newMessageCutoff) ||
					(messages[0].fromuid !== fields.fromuid)) {
					// If it's been 5 minutes, this is a new set of messages
					messages[0].newSet = true;
				}
			} else {
				messages[0].newSet = true;
			}
		} else {
			messages = [];
		}

		const data = await plugins.hooks.fire('filter:messaging.getMessages', {
			messages: messages,
			uid: uid,
			roomId: roomId,
			isNew: isNew,
			mids: mids,
		});

		return data && data.messages;
	};
};

async function modifyMessage(message, fields, mid) {
	if (message) {
		db.parseIntFields(message, intFields, fields);
		if (message.hasOwnProperty('timestamp')) {
			message.timestampISO = utils.toISOString(message.timestamp);
		}
		if (message.hasOwnProperty('edited')) {
			message.editedISO = utils.toISOString(message.edited);
		}
	}

	const payload = await plugins.hooks.fire('filter:messaging.getFields', {
		mid: mid,
		message: message,
		fields: fields,
	});

	return payload.message;
}
