import knex from './knex.js';
import redis from './redis.js';
import initLogger from './logger.js';

const logger = initLogger();

const entityPrefixes = {
	channel: '',
	network: '_',
	studio: '*',
	info: '@',
};

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

	return {
		id: entity.id,
		name: entity.name,
		slug: entity.slug,
		type: entity.type,
		url: entity.url,
		isIndependent: entity.independent,
		hasLogo: entity.has_logo,
		parent: curateEntity(entity.parent, context),
		children: context?.children?.filter((child) => child.parent_id === entity.id).map((child) => curateEntity({ ...child, parent: entity }, { parent: entity })) || [],
		affiliate: entity.affiliate ? {
			id: entity.affiliate.id,
			url: entity.affiliate.url,
			parameters: entity.affiliate.parameters,
		} : null,
		...context?.append?.[entity.id],
	};
}

export async function fetchEntities(options) {
	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}%`);
						})
						.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']))
		.limit(options.limit || 1000);

	return entities.map((entityEntry) => curateEntity(entityEntry));
}

export async function fetchEntitiesById(entityIds, options = {}) {
	const [entities, children] = await Promise.all([
		knex('entities')
			.select(
				'entities.*',
				knex.raw('row_to_json(parents) as parent'),
				knex.raw('row_to_json(affiliates) as affiliate'),
			)
			.whereIn('entities.id', entityIds)
			.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
			.leftJoin('affiliates', knex.raw('affiliates.entity_id in (entities.id, parents.id)'))
			.modify((builder) => {
				if (options.order) {
					builder.orderBy(...options.order);
				}
			})
			.groupBy('entities.id', 'parents.id', 'affiliates.id'),
		options.includeChildren ? knex('entities')
			.whereIn('entities.parent_id', entityIds)
			.orderBy('slug') : [],
	]);

	if (options.order) {
		return entities.map((entityEntry) => curateEntity(entityEntry, {
			append: options.append,
			children,
		}));
	}

	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, {
			append: options.append,
			children,
		});
	}).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');
}