/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { Injectable, Inject } from '@nestjs/common';
import { MoreThan, IsNull } from 'typeorm';
import RE2 from 're2';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { RoleService } from '@/core/RoleService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { AbuseReportResolversRepository, AbuseUserReportsRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { QueueService } from '@/core/QueueService.js';
import { WebhookService } from '@/core/WebhookService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type { DbAbuseReportJobData } from '../types.js';
import type * as Bull from 'bullmq';

@Injectable()
export class ReportAbuseProcessorService {
	private logger: Logger;

	constructor(
		@Inject(DI.abuseReportResolversRepository)
		private abuseReportResolversRepository: AbuseReportResolversRepository,

		@Inject(DI.abuseUserReportsRepository)
		private abuseUserReportsRepository: AbuseUserReportsRepository,

		@Inject(DI.usersRepository)
		private usersRepository: UsersRepository,

		private queueLoggerService: QueueLoggerService,
		private globalEventService: GlobalEventService,
		private instanceActorService: InstanceActorService,
		private apRendererService: ApRendererService,
		private roleService: RoleService,
		private queueService: QueueService,
		private webhookService: WebhookService,
	) {
		this.logger = this.queueLoggerService.logger.createSubLogger('report-abuse');
	}

	@bindThis
	public async process(job: Bull.Job<DbAbuseReportJobData>): Promise<void> {
		this.logger.info('Running...');

		const resolvers = await this.abuseReportResolversRepository.find({
			where: [
				{ expirationDate: MoreThan(new Date()) },
				{ expirationDate: IsNull() },
			],
		});

		const targetUser = await this.usersRepository.findOneByOrFail({
			id: job.data.targetUserId,
		});

		const reporter = await this.usersRepository.findOneByOrFail({
			id: job.data.reporterId,
		});

		const actor = await this.instanceActorService.getInstanceActor();

		const targetUserAcct = targetUser.host ? `${targetUser.username.toLowerCase()}@${targetUser.host}` : targetUser.username.toLowerCase();
		const reporterAcct = reporter.host ? `${reporter.username.toLowerCase()}@${reporter.host}` : reporter.username.toLowerCase();

		for (const resolver of resolvers) {
			if (!(resolver.targetUserPattern || resolver.reporterPattern || resolver.reportContentPattern)) {
				continue;
			}
			const isTargetUserPatternMatched = resolver.targetUserPattern ? new RE2(resolver.targetUserPattern).test(targetUserAcct) : true;
			const isReporterPatternMatched = resolver.reporterPattern ? new RE2(resolver.reporterPattern).test(reporterAcct) : true;
			const isReportContentPatternMatched = resolver.reportContentPattern ? new RE2(resolver.reportContentPattern).test(job.data.comment) : true;

			if (isTargetUserPatternMatched && isReporterPatternMatched && isReportContentPatternMatched) {
				if (resolver.forward && job.data.targetUserHost !== null && job.data.reporterHost === null) {
					this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, job.data.comment)), targetUser.inbox, false);
				}

				await this.abuseUserReportsRepository.update(job.data.id, {
					resolved: true,
					assigneeId: actor.id,
					forwarded: resolver.forward && job.data.targetUserHost !== null && job.data.reporterHost === null,
				});

				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.on.includes('reportAutoResolved'));
				for (const webhook of webhooks) {
					if (await this.roleService.isAdministrator({ id: webhook.userId, isRoot: false })) {
						this.queueService.webhookDeliver(webhook, 'reportAutoResolved', {
							resolver: resolver,
							report: job.data,
						});
					}
				}

				return;
			}
		}

		// Publish event to moderators
		setImmediate(async () => {
			const moderators = await this.roleService.getModerators();

			for (const moderator of moderators) {
				this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', {
					id: job.data.id,
					targetUserId: job.data.targetUserId,
					reporterId: job.data.reporterId,
					comment: job.data.comment,
				});
			}
		});
	}
}
