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

import { Inject, Injectable } from '@nestjs/common';
import { And, In, MoreThan, Not } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { AutoRemovalConditionRepository, MiUserNotePining, NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
import { IdService } from '@/core/IdService.js';
import { RoleService } from '@/core/RoleService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';

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

	constructor(
		@Inject(DI.notesRepository)
		private notesRepository: NotesRepository,

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

		@Inject(DI.userNotePiningsRepository)
		private userNotePiningsRepository: UserNotePiningsRepository,

		@Inject(DI.autoRemovalConditionRepository)
		private autoRemovalConditionRepository: AutoRemovalConditionRepository,

		private idService: IdService,
		private roleService: RoleService,
		private noteDeleteService: NoteDeleteService,
		private queueLoggerService: QueueLoggerService,
	) {
		this.logger = this.queueLoggerService.logger.createSubLogger('auto-note-removal');
	}

	@bindThis
	public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> {
		this.logger.info('Checking notes that to remove automatically...');
		this.logger.info('Checking users that enabled note auto-removal');
		const users = await this.usersRepository.find({ where: { autoRemoval: true } });
		if (users.length < 1) {
			this.logger.info('Does not have any user that enabled autoRemoval');
			return;
		}
		const now = Date.now();

		for (const user of users) {
			const policies = await this.roleService.getUserPolicies(user.id);
			if (!policies.canUseAutoNoteRemoval) continue;
			const autoRemovalCondition = await this.autoRemovalConditionRepository.findOneByOrFail({ userId: user.id });
			const pinings: MiUserNotePining[] = await this.userNotePiningsRepository.findBy({ userId: user.id });
			const piningNoteIds: string[] = pinings.map(pining => pining.noteId); // pining.note always undefined (bug?)

			const specifiedNotes: MiNote[] = await this.notesRepository.findBy({
				userId: user.id,
				visibility: Not(In(['public', 'home', 'followers'])),
			});
			const specifiedNoteIds: string[] = specifiedNotes.map(note => note.id);
			const deleteAfter: number = autoRemovalCondition.deleteAfter * 86400000;

			// Delete notes
			let cursor: MiNote['id'] | null = null;
			let condition: string[] = [];
			if (autoRemovalCondition.noSpecifiedNotes) {
				condition = [...condition, ...specifiedNoteIds];
			}

			if (autoRemovalCondition.noPiningNotes) {
				condition = [...condition, ...piningNoteIds];
			}

			while (true) {
				const notes = await this.notesRepository.find({
					where: {
						userId: user.id,
						...(cursor ? {
							id: And(Not(In(condition)), MoreThan(cursor)),
						} : {
							id: Not(In(condition)),
						}),
					},
					take: 100,
					order: {
						id: 1,
					},
				}) as MiNote[];

				if (notes.length === 0) {
					break;
				}

				cursor = notes.at(-1)?.id ?? null;

				for (const note of notes) {
					const createdAt: number = this.idService.parse(note.id).date.getTime();
					const delta: number = now - createdAt;
					if (delta > deleteAfter) {
						Promise.bind(this.noteDeleteService.delete(user, note, false, user));
					}
				}
				await job.updateProgress(100 / users.length * users.indexOf(user));
			}

			await job.updateProgress(100);
			this.logger.succ('All of auto-removable notes deleted');
		}

		this.logger.succ('All notes to auto-remove has beed removed.');
	}
}
