import { censor } from './censor.js';
import knex from './knex.js';
import initLogger from './logger.js';
import { curateMedia } from './media.js';

import redis from './redis.js';

const logger = initLogger();

function curateTag(tag, context = {}) {
	return {
		id: tag.id,
		name: censor(tag.name, context.restriction),
		slug: tag.slug,
		description: context.restriction ? null : tag.description, // censor interferes with markdown
		priority: tag.priority,
		poster: tag.poster && curateMedia(tag.poster),
		photos: tag.photos?.map((photo) => curateMedia(photo)) || [],
		aliasFor: tag.alias_slug,
		alerts: {
			only: context?.alerts?.filter((alert) => alert.is_only).flatMap((alert) => alert.alert_ids) || [],
			multi: context?.alerts?.filter((alert) => !alert.is_only).flatMap((alert) => alert.alert_ids) || [],
		},
		...context?.append?.[tag.id],
	};
}

export async function fetchTags(options = {}, context = {}) {
	const query = options.query?.trim();

	const [tags, posters] = await Promise.all([
		knex('tags')
			.select('aliases.*')
			.leftJoin(knex.raw('tags AS aliases ON aliases.id = tags.alias_for OR (tags.alias_for IS NULL AND aliases.id = tags.id)'))
			.modify((builder) => {
				if (query) {
					builder
						.whereILike('tags.name', `%${query}%`)
						.orWhereILike('tags.slug', `%${query}%`)
						.groupBy('aliases.id')
						.orderBy([
							{
								column: knex.raw('similarity(aliases.slug, :query)', { query }),
								order: 'desc',
							},
							{
								column: 'aliases.priority',
								order: 'desc',
							},
							{
								column: 'aliases.slug',
								order: 'asc',
							},
						]);
				}
				else if (!options.includeAliases) {
					builder.whereNull('tags.alias_for');
				}
			}),
		knex('tags_posters')
			.select('media.*', 'tags_posters.tag_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
			.select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
			.leftJoin('media', 'media.id', 'tags_posters.media_id')
			.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
			.leftJoin('entities', 'entities.id', 'media.entity_id')
			.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
			.groupBy('media.id', 'tags_posters.tag_id', 'sfw_media.id', 'entities.id', 'parents.id'),
	]);

	const postersByTagId = Object.fromEntries(posters.map((poster) => [poster.tag_id, poster]));

	return tags.map((tagEntry) => curateTag({
		...tagEntry,
		poster: postersByTagId[tagEntry.id],
	}, context));
}

export async function fetchTagsById(tagIds, options = {}, reqUser, context = {}) {
	const [tags, posters, photos, alerts] = await Promise.all([
		knex('tags')
			.select('tags.*', 'aliases.slug as alias_slug')
			.leftJoin('tags as aliases', 'aliases.id', 'tags.alias_for')
			.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
			.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'))
			.modify((builder) => {
				if (options.order) {
					builder.orderBy(...options.order);
				}
			}),
		knex('tags_posters')
			.select('media.*', 'tags_posters.tag_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
			.select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
			.leftJoin('tags', 'tags.id', 'tags_posters.tag_id')
			.leftJoin('media', 'media.id', 'tags_posters.media_id')
			.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
			.leftJoin('entities', 'entities.id', 'media.entity_id')
			.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
			.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
			.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'))
			.groupBy('media.id', 'tags_posters.tag_id', 'sfw_media.id', 'entities.id', 'parents.id'),
		context.restriction
			? []
			: knex('tags_photos')
					.select('tags_photos.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
					.leftJoin('tags', 'tags.id', 'tags_photos.tag_id')
					.leftJoin('media', 'media.id', 'tags_photos.media_id')
					.leftJoin('entities', 'entities.id', 'media.entity_id')
					.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
					.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
					.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')),
		reqUser
			? knex('alerts_users_tags')
					.select('alerts_users_tags.*')
					.leftJoin('tags', 'tags.id', 'alerts_users_tags.tag_id')
					.where('user_id', reqUser.id)
					.where((whereBuilder) => {
						whereBuilder
							.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
							.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'));
					})
			: [],
	]);

	const postersByTagId = Object.fromEntries(posters.map((poster) => [poster.tag_id, poster]));

	if (options.order) {
		return tags.map((tagEntry) => curateTag({
			...tagEntry,
			poster: postersByTagId[tagEntry.id],
			photos: photos.filter((photo) => photo.tag_id === tagEntry.id),
		}, {
			alerts: alerts.filter((alert) => alert.tag_id === tagEntry.id),
			append: options.append,
			...context,
		}));
	}

	const curatedTags = tagIds.map((tagId) => {
		const tag = tags.find((tagEntry) => (typeof tagId === 'string' ? tagEntry.slug === tagId : tagEntry.id === tagId));

		if (!tag) {
			console.warn(`Can't match tag ${tagId}`);
			return null;
		}

		return curateTag({
			...tag,
			poster: postersByTagId[tag.id],
			photos: photos.filter((photo) => photo.tag_id === tag.id),
		}, {
			alerts: alerts.filter((alert) => alert.tag_id === tag.id),
			append: options.append,
			...context,
		});
	}).filter(Boolean);

	return curatedTags;
}

export async function cacheTagIds() {
	const tags = await knex('tags')
		.select('id', 'slug')
		.whereNull('alias_for')
		.whereNotNull('slug');

	await redis.del('traxxx:tags:id_by_slug');
	await redis.hSet('traxxx:tags:id_by_slug', tags.map((tag) => [tag.slug, tag.id]));

	logger.info('Cached tag IDs by slug');
}
