import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js';
import { publishMessagingStream } from '@/services/stream.js';
import { publishMessagingIndexStream } from '@/services/stream.js';
import { pushNotification } from '@/services/push-notification.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js';
import { In } from 'typeorm';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { UserGroup } from '@/models/entities/user-group.js';
import { toArray } from '@/prelude/array.js';
import { renderReadActivity } from '@/remote/activitypub/renderer/read.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import { deliver } from '@/queue/index.js';
import orderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';

/**
 * Mark messages as read
 */
export async function readUserMessagingMessage(
	userId: User['id'],
	otherpartyId: User['id'],
	messageIds: MessagingMessage['id'][]
) {
	if (messageIds.length === 0) return;

	const messages = await MessagingMessages.findBy({
		id: In(messageIds),
	});

	for (const message of messages) {
		if (message.recipientId !== userId) {
			throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).');
		}
	}

	// Update documents
	await MessagingMessages.update({
		id: In(messageIds),
		userId: otherpartyId,
		recipientId: userId,
		isRead: false,
	}, {
		isRead: true,
	});

	// Publish event
	publishMessagingStream(otherpartyId, userId, 'read', messageIds);
	publishMessagingIndexStream(userId, 'read', messageIds);

	if (!await Users.getHasUnreadMessagingMessage(userId)) {
		// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
		publishMainStream(userId, 'readAllMessagingMessages');
		pushNotification(userId, 'readAllMessagingMessages', undefined);
	} else {
		// そのユーザーとのメッセージで未読がなければイベント発行
		const count = await MessagingMessages.count({
			where: {
				userId: otherpartyId,
				recipientId: userId,
				isRead: false,
			},
			take: 1
		});

		if (!count) {
			pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId });
		}
	}
}

/**
 * Mark messages as read
 */
export async function readGroupMessagingMessage(
	userId: User['id'],
	groupId: UserGroup['id'],
	messageIds: MessagingMessage['id'][]
) {
	if (messageIds.length === 0) return;

	// check joined
	const joining = await UserGroupJoinings.findOneBy({
		userId: userId,
		userGroupId: groupId,
	});

	if (joining == null) {
		throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
	}

	const messages = await MessagingMessages.findBy({
		id: In(messageIds),
	});

	const reads: MessagingMessage['id'][] = [];

	for (const message of messages) {
		if (message.userId === userId) continue;
		if (message.reads.includes(userId)) continue;

		// Update document
		await MessagingMessages.createQueryBuilder().update()
			.set({
				reads: (() => `array_append("reads", '${joining.userId}')`) as any,
			})
			.where('id = :id', { id: message.id })
			.execute();

		reads.push(message.id);
	}

	// Publish event
	publishGroupMessagingStream(groupId, 'read', {
		ids: reads,
		userId: userId,
	});
	publishMessagingIndexStream(userId, 'read', reads);

	if (!await Users.getHasUnreadMessagingMessage(userId)) {
		// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
		publishMainStream(userId, 'readAllMessagingMessages');
		pushNotification(userId, 'readAllMessagingMessages', undefined);
	} else {
		// そのグループにおいて未読がなければイベント発行
		const unreadExist = await MessagingMessages.createQueryBuilder('message')
			.where(`message.groupId = :groupId`, { groupId: groupId })
			.andWhere('message.userId != :userId', { userId: userId })
			.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
			.andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない
			.getOne().then(x => x != null);

		if (!unreadExist) {
			pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId });
		}
	}
}

export async function deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) {
	messages = toArray(messages).filter(x => x.uri);
	const contents = messages.map(x => renderReadActivity(user, x));

	if (contents.length > 1) {
		const collection = orderedCollection(null, contents.length, undefined, undefined, contents);
		deliver(user, renderActivity(collection), recipient.inbox);
	} else {
		for (const content of contents) {
			deliver(user, renderActivity(content), recipient.inbox);
		}
	}
}
