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 type { User, IRemoteUser } from "@/models/entities/user.js";
import type { 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 type { 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);
		}
	}
}
