// SPDX-License-Identifier: AGPL-3.0-or-later

import {ReportAlreadyResolvedError} from '@fluxer/errors/src/domains/moderation/ReportAlreadyResolvedError';
import {UnknownReportError} from '@fluxer/errors/src/domains/moderation/UnknownReportError';
import type {ChannelID, MessageID, ReportID, UserID} from '../BrandedTypes';
import {createChannelID, createGuildID, createMessageID, createReportID, createUserID} from '../BrandedTypes';
import {fetchMany, fetchOne, upsertOne} from '../database/CassandraQueryExecution';
import {Db} from '../database/CassandraTypes';
import type {
	DSAReportEmailVerificationRow,
	DSAReportTicketRow,
	MessageReportSubmissionByReporterRow,
} from '../database/types/ReportTypes';
import {
	DSAReportEmailVerifications,
	DSAReportTickets,
	IARSubmissions,
	MessageReportSubmissionsByReporter,
} from '../Tables';
import type {
	IARMessageContext,
	IARMessageContextRow,
	IARSubmission,
	IARSubmissionRow,
	IReportRepository,
} from './IReportRepository';

const GET_REPORT_QUERY = IARSubmissions.select({
	where: IARSubmissions.where.eq('report_id'),
	limit: 1,
});
const createFetchAllReportsPaginatedQuery = (limit: number) =>
	IARSubmissions.select({
		where: IARSubmissions.where.tokenGt('report_id', 'last_report_id'),
		limit,
	});
const GET_DSA_EMAIL_VERIFICATION_QUERY = DSAReportEmailVerifications.select({
	where: DSAReportEmailVerifications.where.eq('email_lower'),
	limit: 1,
});
const GET_DSA_REPORT_TICKET_QUERY = DSAReportTickets.select({
	where: DSAReportTickets.where.eq('ticket'),
	limit: 1,
});
const GET_MESSAGE_REPORT_SUBMISSION_BY_REPORTER_QUERY = MessageReportSubmissionsByReporter.select({
	where: [
		MessageReportSubmissionsByReporter.where.eq('reporter_id'),
		MessageReportSubmissionsByReporter.where.eq('channel_id'),
		MessageReportSubmissionsByReporter.where.eq('message_id'),
	],
	limit: 1,
});

function createFetchAllReportsFirstPageQuery(limit: number) {
	return IARSubmissions.select({limit});
}

export class ReportRepository implements IReportRepository {
	async createReport(data: IARSubmissionRow): Promise<IARSubmission> {
		await upsertOne(IARSubmissions.insert(data));
		return this.mapRowToSubmission(data);
	}

	async reserveMessageReportByReporter(data: MessageReportSubmissionByReporterRow): Promise<boolean> {
		const existing = await fetchOne<MessageReportSubmissionByReporterRow>(
			GET_MESSAGE_REPORT_SUBMISSION_BY_REPORTER_QUERY.bind({
				reporter_id: data.reporter_id,
				channel_id: data.channel_id,
				message_id: data.message_id,
			}),
		);
		if (existing) {
			return false;
		}
		await upsertOne(MessageReportSubmissionsByReporter.insert(data));
		return true;
	}

	async deleteMessageReportByReporter(reporterId: UserID, channelId: ChannelID, messageId: MessageID): Promise<void> {
		await MessageReportSubmissionsByReporter.deleteByPk({
			reporter_id: reporterId,
			channel_id: channelId,
			message_id: messageId,
		});
	}

	async getReport(reportId: ReportID): Promise<IARSubmission | null> {
		const row = await fetchOne<IARSubmissionRow>(GET_REPORT_QUERY.bind({report_id: reportId}));
		return row ? this.mapRowToSubmission(row) : null;
	}

	async resolveReport(
		reportId: ReportID,
		resolvedByAdminId: UserID,
		publicComment: string | null,
		auditLogReason: string | null,
	): Promise<IARSubmission> {
		const report = await this.getReport(reportId);
		if (!report) {
			throw new UnknownReportError();
		}
		if (report.status !== 0) {
			throw new ReportAlreadyResolvedError();
		}
		const resolvedAt = new Date();
		const newStatus = 1;
		await upsertOne(
			IARSubmissions.patchByPk(
				{report_id: reportId},
				{
					resolved_at: Db.set(resolvedAt),
					resolved_by_admin_id: Db.set(resolvedByAdminId),
					public_comment: Db.set(publicComment),
					audit_log_reason: Db.set(auditLogReason),
					status: Db.set(newStatus),
				},
			),
		);
		return {
			...report,
			resolvedAt,
			resolvedByAdminId,
			publicComment,
			auditLogReason,
			status: newStatus,
		};
	}

	private mapRowToSubmission(row: IARSubmissionRow): IARSubmission {
		return {
			reportId: createReportID(row.report_id),
			reporterId: row.reporter_id ? createUserID(row.reporter_id) : null,
			reporterEmail: row.reporter_email,
			reporterFullLegalName: row.reporter_full_legal_name,
			reporterCountryOfResidence: row.reporter_country_of_residence,
			reportedAt: row.reported_at,
			status: row.status,
			reportType: row.report_type,
			category: row.category,
			additionalInfo: row.additional_info,
			reportedUserId: row.reported_user_id ? createUserID(row.reported_user_id) : null,
			reportedUserAvatarHash: row.reported_user_avatar_hash,
			reportedGuildId: row.reported_guild_id ? createGuildID(row.reported_guild_id) : null,
			reportedGuildName: row.reported_guild_name,
			reportedGuildIconHash: row.reported_guild_icon_hash,
			reportedMessageId: row.reported_message_id ? createMessageID(row.reported_message_id) : null,
			reportedChannelId: row.reported_channel_id ? createChannelID(row.reported_channel_id) : null,
			reportedChannelName: row.reported_channel_name,
			messageContext: row.message_context ? this.mapMessageContext(row.message_context) : null,
			guildContextId: row.guild_context_id ? createGuildID(row.guild_context_id) : null,
			resolvedAt: row.resolved_at,
			resolvedByAdminId: row.resolved_by_admin_id ? createUserID(row.resolved_by_admin_id) : null,
			publicComment: row.public_comment,
			auditLogReason: row.audit_log_reason,
			reportedGuildInviteCode: row.reported_guild_invite_code,
			reportedGuildNsfw: row.reported_guild_nsfw ?? null,
			reportedGuildContentWarningLevel: row.reported_guild_content_warning_level ?? null,
			reportedGuildContentWarningText: row.reported_guild_content_warning_text ?? null,
			reportedChannelNsfwOverride: row.reported_channel_nsfw_override ?? null,
			reportedChannelContentWarningLevel: row.reported_channel_content_warning_level ?? null,
			reportedChannelContentWarningText: row.reported_channel_content_warning_text ?? null,
			reportedChannelEffectiveNsfw: row.reported_channel_effective_nsfw ?? null,
			reportedChannelEffectiveContentWarningLevel: row.reported_channel_effective_content_warning_level ?? null,
			reportedChannelEffectiveContentWarningText: row.reported_channel_effective_content_warning_text ?? null,
		};
	}

	async listAllReportsPaginated(limit: number, lastReportId?: ReportID): Promise<Array<IARSubmission>> {
		let reports: Array<IARSubmissionRow>;
		if (lastReportId) {
			const query = createFetchAllReportsPaginatedQuery(limit);
			reports = await fetchMany<IARSubmissionRow>(query.bind({last_report_id: lastReportId}));
		} else {
			const query = createFetchAllReportsFirstPageQuery(limit);
			reports = await fetchMany<IARSubmissionRow>(query.bind({}));
		}
		return reports.map((report) => this.mapRowToSubmission(report));
	}

	async upsertDsaEmailVerification(row: DSAReportEmailVerificationRow): Promise<void> {
		await upsertOne(DSAReportEmailVerifications.insert(row));
	}

	async getDsaEmailVerification(emailLower: string): Promise<DSAReportEmailVerificationRow | null> {
		const row = await fetchOne<DSAReportEmailVerificationRow>(
			GET_DSA_EMAIL_VERIFICATION_QUERY.bind({email_lower: emailLower}),
		);
		return row ?? null;
	}

	async deleteDsaEmailVerification(emailLower: string): Promise<void> {
		await DSAReportEmailVerifications.deleteByPk({email_lower: emailLower});
	}

	async createDsaTicket(row: DSAReportTicketRow): Promise<void> {
		await upsertOne(DSAReportTickets.insert(row));
	}

	async getDsaTicket(ticket: string): Promise<DSAReportTicketRow | null> {
		const row = await fetchOne<DSAReportTicketRow>(GET_DSA_REPORT_TICKET_QUERY.bind({ticket}));
		return row ?? null;
	}

	async deleteDsaTicket(ticket: string): Promise<void> {
		await DSAReportTickets.deleteByPk({ticket});
	}

	private mapMessageContext(rawContext: Array<IARMessageContextRow>): Array<IARMessageContext> {
		const toBigintArray = (collection: ReadonlyArray<bigint> | Set<bigint> | null | undefined): Array<bigint> =>
			collection ? Array.from(collection) : [];
		return rawContext.map((msg) => ({
			messageId: createMessageID(msg.message_id),
			authorId: createUserID(msg.author_id),
			channelId: msg.channel_id ? createChannelID(msg.channel_id) : null,
			authorUsername: msg.author_username,
			authorDiscriminator: msg.author_discriminator,
			authorAvatarHash: msg.author_avatar_hash,
			content: msg.content,
			timestamp: msg.timestamp,
			editedTimestamp: msg.edited_timestamp,
			type: msg.type,
			flags: msg.flags,
			mentionEveryone: msg.mention_everyone,
			mentionUsers: toBigintArray(msg.mention_users),
			mentionRoles: toBigintArray(msg.mention_roles),
			mentionChannels: toBigintArray(msg.mention_channels),
			attachments: msg.attachments ?? [],
			embeds: msg.embeds ?? [],
			stickers: msg.sticker_items ?? [],
		}));
	}
}
