import config from 'config';

import { knexOwner as knex } from './knex.js';
import { indexApi } from './manticore.js';
import { HttpError } from './errors.js';
import slugify from './utils/slugify.js';
import initLogger from './logger.js';

const logger = initLogger();

let lastActorsViewRefresh = 0;

export function curateStash(stash, assets = {}) {
	if (!stash) {
		return null;
	}

	const curatedStash = {
		id: stash.id,
		name: stash.name,
		slug: stash.slug,
		primary: stash.primary,
		public: stash.public,
		createdAt: stash.created_at,
		stashedScenes: stash.stashed_scenes || null,
		stashedMovies: stash.stashed_movies || null,
		stashedActors: stash.stashed_actors || null,
		user: assets.user ? {
			id: assets.user.id,
			username: assets.user.username,
			avatar: `/media/avatars/${assets.user.id}_${assets.user.username}.png`,
			createdAt: assets.user.created_at,
		} : null,
	};

	return curatedStash;
}

function curateStashEntry(stash, user) {
	const curatedStashEntry = {
		user_id: user.id,
		name: stash.name,
		slug: slugify(stash.name),
		public: false,
	};

	return curatedStashEntry;
}

function verifyStashAccess(stash, sessionUser) {
	if (!stash || (!stash.public && stash.user_id !== sessionUser?.id)) {
		throw new HttpError('This stash does not exist, or you are not allowed access.', 404);
	}
}

export async function fetchStashById(stashId, sessionUser) {
	const stash = await knex('stashes')
		.where('id', stashId)
		.first();

	verifyStashAccess(stash, sessionUser);

	return curateStash(stash);
}

export async function fetchStashByUsernameAndSlug(username, stashSlug, sessionUser) {
	const user = await knex('users').where('username', username).first();

	if (!user) {
		throw new HttpError('This user does not exist.', 404);
	}

	const stash = await knex('stashes')
		.select('stashes.*', 'stashes_meta.*')
		.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id')
		.where('slug', stashSlug)
		.where('user_id', user.id)
		.first();

	verifyStashAccess(stash, sessionUser);

	return curateStash(stash, { user });
}

export async function fetchStashes(domain, itemId, sessionUser) {
	const stashes = await knex(`stashes_${domain}s`)
		.select('stashes.*')
		.where({
			[`${domain}_id`]: itemId,
			user_id: sessionUser.id,
		})
		.leftJoin('stashes', 'stashes.id', `stashes_${domain}s.stash_id`);

	return stashes.map((stash) => curateStash(stash));
}

export async function createStash(newStash, sessionUser) {
	if (!sessionUser) {
		throw new HttpError('You are not authenthicated', 401);
	}

	try {
		const stash = await knex('stashes')
			.insert(curateStashEntry(newStash, sessionUser))
			.returning('*');

		return curateStash(stash);
	} catch (error) {
		if (error.routine === '_bt_check_unique') {
			throw new HttpError('Stash name should be unique', 409);
		}

		throw error;
	}
}

export async function updateStash(stashId, newStash, sessionUser) {
	if (!sessionUser) {
		throw new HttpError('You are not authenthicated', 401);
	}

	const stash = await knex('stashes')
		.where({
			id: stashId,
			user_id: sessionUser.id,
		})
		.update(newStash)
		.returning('*');

	if (!stash) {
		throw new HttpError('You are not authorized to modify this stash', 403);
	}

	return curateStash(stash);
}

export async function removeStash(stashId, sessionUser) {
	if (!sessionUser) {
		throw new HttpError('You are not authenthicated', 401);
	}

	const removed = await knex('stashes')
		.where({
			id: stashId,
			user_id: sessionUser.id,
			primary: false,
		})
		.delete();

	if (removed === 0) {
		throw new HttpError('Unable to remove this stash', 400);
	}
}

export async function refreshActorsView() {
	if (new Date() - lastActorsViewRefresh > config.stashes.viewRefreshCooldown * 60000) {
		// don't refresh actors view more than once an hour
		lastActorsViewRefresh = new Date();

		logger.debug('Refreshing actors view');

		return knex.schema.refreshMaterializedView('actors_meta');
	}

	logger.silly('Skipping actors view refresh');

	return false;
}

export async function stashActor(actorId, stashId, sessionUser) {
	const stash = await fetchStashById(stashId, sessionUser);

	await knex('stashes_actors')
		.insert({
			stash_id: stash.id,
			actor_id: actorId,
		});

	refreshActorsView();

	return fetchStashes('actor', actorId, sessionUser);
}

export async function unstashActor(actorId, stashId, sessionUser) {
	await knex
		.from('stashes_actors AS deletable')
		.where('deletable.actor_id', actorId)
		.where('deletable.stash_id', stashId)
		.whereExists(knex('stashes_actors') // verify user owns this stash, complimentary to row-level security
			.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
			.where('stashes_actors.stash_id', knex.raw('deletable.stash_id'))
			.where('stashes.user_id', sessionUser.id))
		.delete();

	refreshActorsView();

	return fetchStashes('actor', actorId, sessionUser);
}

export async function stashScene(sceneId, stashId, sessionUser) {
	const stash = await fetchStashById(stashId, sessionUser);

	await knex('stashes_scenes')
		.insert({
			stash_id: stash.id,
			scene_id: sceneId,
		});

	return fetchStashes('scene', sceneId, sessionUser);
}

export async function unstashScene(sceneId, stashId, sessionUser) {
	await knex
		.from('stashes_scenes AS deletable')
		.where('deletable.scene_id', sceneId)
		.where('deletable.stash_id', stashId)
		.whereExists(knex('stashes_scenes') // verify user owns this stash, complimentary to row-level security
			.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
			.where('stashes_scenes.stash_id', knex.raw('deletable.stash_id'))
			.where('stashes.user_id', sessionUser.id))
		.delete();

	await indexApi.callDelete({
		index: 'scenes_stashed',
		query: {
			bool: {
				must: [
					{ equals: { id: sceneId } },
					{ equals: { stash_id: stashId } },
					{ equals: { user_id: sessionUser.id } },
				],
			},
		},
	});

	return fetchStashes('scene', sceneId, sessionUser);
}

export async function stashMovie(movieId, stashId, sessionUser) {
	const stash = await fetchStashById(stashId, sessionUser);

	await knex('stashes_movies')
		.insert({
			stash_id: stash.id,
			movie_id: movieId,
		});

	return fetchStashes('movie', movieId, sessionUser);
}

export async function unstashMovie(movieId, stashId, sessionUser) {
	await knex
		.from('stashes_movies AS deletable')
		.where('deletable.movie_id', movieId)
		.where('deletable.stash_id', stashId)
		.whereExists(knex('stashes_movies') // verify user owns this stash, complimentary to row-level security
			.leftJoin('stashes', 'stashes.id', 'stashes_movies.stash_id')
			.where('stashes_movies.stash_id', knex.raw('deletable.stash_id'))
			.where('stashes.user_id', sessionUser.id))
		.delete();

	return fetchStashes('movie', movieId, sessionUser);
}
