import { getAffiliateEntityUrl } from './affiliates.js';
import { censor } from './censor.js';
import entityPrefixes from './entities-prefixes.js';
import knex from './knex.js';
import initLogger from './logger.js';
import redis from './redis.js';

const logger = initLogger();

export function curateEntity(entity, context = {}) {
	if (!entity) {
		return null;
	}

	const curatedEntity = {
		id: entity.id,
		name: censor(entity.name, context.restriction),
		slug: entity.slug,
		type: entity.type,
		url: entity.url,
		isIndependent: entity.independent,
		hasLogo: context.restriction ? false : entity.has_logo,
		parent: curateEntity(entity.parent, context),
		tags: context?.tags?.map((tag) => ({
			id: tag.id,
			name: tag.name,
			slug: tag.slug,
		})),
		children: context?.children?.filter((child) => child.parent_id === entity.id).map((child) => curateEntity({
			...child,
			parent: entity,
		}, {
			parent: entity,
			restriction: context.restriction,
		})) || [],
		affiliate: entity.affiliate
			? {
					id: entity.affiliate.id,
					entityId: entity.affiliate.entity_id,
					url: entity.affiliate.url,
					parameters: entity.affiliate.parameters || {},
				}
			: null,
		...context?.append?.[entity.id],
		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) || [],
		},
	};

	curatedEntity.affiliateUrl = getAffiliateEntityUrl(curatedEntity);

	return curatedEntity;
}

export async function fetchEntities(options = {}, context = {}) {
	const entities = await knex('entities')
		.select('entities.*', knex.raw('row_to_json(parents) as parent'))
		.modify((builder) => {
			if (options.query) {
				builder.where((whereBuilder) => {
					whereBuilder
						.where((subBuilder) => {
							subBuilder
								.whereILike('entities.name', `%${options.query}%`)
								.orWhereILike('entities.slug', `%${options.query}%`)
								.orWhereILike(knex.raw('array_to_string(entities.alias, \',\', \'*\')'), `%${options.query}%`);
						})
						.whereNot('entities.type', 'info');
				});
			}

			if (options.type === 'primary') {
				builder
					.where((subBuilder) => {
						subBuilder
							.where('entities.type', 'network')
							.orWhere('entities.independent', true)
							.orWhereNull('entities.parent_id');
					})
					.whereNot('entities.type', 'info');

				return;
			}

			if (options.type) {
				builder.where('entities.type', options.type);
			}

			if (options.showInvisible !== true) {
				builder.where('entities.visible', true);
			}
		})
		.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
		.orderBy(...(options.order || ['name', 'asc']))
		.offset((options.page - 1) * options.limit)
		.limit(options.limit || 1000);

	const entitiesTags = await knex('entities_tags')
		.select('entity_id', 'tags.*')
		.leftJoin('tags', 'tags.id', 'tag_id')
		.whereIn('entity_id', entities.map((entity) => entity.id));

	return entities.map((entityEntry) => curateEntity(entityEntry, {
		...context,
		tags: entitiesTags.filter((tag) => tag.entity_id === entityEntry.id),
	}));
}

export async function fetchEntitiesById(entityIds, options = {}, reqUser, context) {
	const [entities, children, tags, alerts] = await Promise.all([
		knex('entities')
			.select(
				'entities.*',
				knex.raw('row_to_json(parents) as parent'),
				knex.raw('coalesce(row_to_json(affiliates), row_to_json(network_affiliates)) as affiliate'),
			)
			.whereIn('entities.id', entityIds)
			.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
			.leftJoin('affiliates', 'affiliates.entity_id', 'entities.id')
			.leftJoin('affiliates as network_affiliates', 'network_affiliates.entity_id', 'parents.id')
			.modify((builder) => {
				if (options.order) {
					builder.orderBy(...options.order);
				}
			})
			.groupBy('entities.id', 'parents.id', 'affiliates.id', 'affiliates.entity_id', 'network_affiliates.id', 'network_affiliates.entity_id'),
		options.includeChildren
			? knex('entities')
					.whereIn('entities.parent_id', entityIds)
					.whereNot('type', 'info')
					.orderBy([
						{ column: knex.raw('array_position(array[\'network\', \'channel\']::varchar[], type)'), order: 'asc' },
						{ column: 'independent', order: 'desc' },
						{ column: 'slug', order: 'asc' },
					])
			: [],
		knex('entities_tags')
			.select('entity_id', 'tags.*')
			.leftJoin('tags', 'tags.id', 'tag_id')
			.whereIn('entity_id', entityIds),
		reqUser
			? knex('alerts_users_entities')
					.where('user_id', reqUser.id)
					.whereIn('entity_id', entityIds)
			: [],
	]);

	if (options.order) {
		return entities.map((entityEntry) => curateEntity(entityEntry, {
			...context,
			append: options.append,
			children: children.filter((channel) => channel.parent_id === entityEntry.id),
			alerts: alerts.filter((alert) => alert.entity_id === entityEntry.id),
		}));
	}

	const curatedEntities = entityIds.map((entityId) => {
		const entity = entities.find((entityEntry) => entityEntry.id === entityId);

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

		return curateEntity(entity, {
			...context,
			append: options.append,
			children: children.filter((channel) => channel.parent_id === entity.id),
			tags: tags.filter((tag) => tag.entity_id === entity.id),
			alerts: alerts.filter((alert) => alert.entity_id === entity.id),
		});
	}).filter(Boolean);

	return curatedEntities;
}

export async function getEntityIdsBySlug(slugs, domain) {
	const ids = await Promise.all(slugs.map(async (slug) => {
		if (!slug) {
			return null;
		}

		if (Number(slug)) {
			return Number(slug); // already an ID or missing
		}

		const id = await redis.hGet(`traxxx:${domain}:id_by_slug`, slug);

		return Number(id);
	}));

	return ids.filter(Boolean);
}

export async function cacheEntityIds() {
	const entities = await knex('entities').select('id', 'slug', 'type');

	await redis.del('traxxx:entities:id_by_slug');
	await redis.hSet('traxxx:entities:id_by_slug', entities.map((entity) => [`${entityPrefixes[entity.type]}${entity.slug}`, entity.id]));

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

const sortMap = {
	releases: knex.raw('count(releases.id)'),
	latest: 'latest_release_date',
};

export async function fetchEntityHealths(options) {
	const entities = await knex('entities')
		.select(
			'entities.*',
			knex.raw('row_to_json(parents) as parent'),
			knex.raw('max(effective_date) as latest_release_date'),
			knex.raw('count(releases.id) as total_releases'),
		)
		.leftJoin('releases', 'releases.entity_id', 'entities.id')
		.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
		.orderBy(sortMap[options.sort] || options.sort || sortMap.releases, options.order || 'desc')
		.groupBy('entities.id', 'parents.id');

	const curatedEntities = entities.map((entity) => ({
		...curateEntity(entity),
		totalReleases: entity.total_releases,
		latestReleaseDate: entity.latest_release_date,
	}));

	return {
		entities: curatedEntities,
	};
}
