import { RequestHandler } from 'express';
import Joi from 'joi';
import { InvalidPayloadException } from '../exceptions';
import { FailedValidationException } from '@directus/shared/exceptions';
import asyncHandler from '../utils/async-handler';
import { sanitizeQuery } from '../utils/sanitize-query';

export const validateBatch = (scope: 'read' | 'update' | 'delete'): RequestHandler =>
	asyncHandler(async (req, res, next) => {
		if (req.method.toLowerCase() === 'get') {
			req.body = {};
			return next();
		}

		if (!req.body) throw new InvalidPayloadException('Payload in body is required');

		if (req.singleton) return next();

		// Every cRUD action has either keys or query
		let batchSchema = Joi.object().keys({
			keys: Joi.array().items(Joi.alternatives(Joi.string(), Joi.number())),
			query: Joi.object().unknown(),
		});

		// In reads, you can't combine the two, and 1 of the two at  least is required
		if (scope !== 'read') {
			batchSchema = batchSchema.xor('query', 'keys');
		}

		// In updates, we add a required `data` that holds the update payload if an array isn't used
		if (scope === 'update') {
			if (Array.isArray(req.body)) return next();

			batchSchema = batchSchema.keys({
				data: Joi.object().unknown().required(),
			});
		}

		// In deletes, we want to keep supporting an array of just primary keys
		if (scope === 'delete' && Array.isArray(req.body)) {
			return next();
		}

		const { error } = batchSchema.validate(req.body);

		if (error) {
			throw new FailedValidationException(error.details[0]);
		}

		// In reads, the query in the body should override the query params for searching
		if (scope === 'read' && req.body.query) {
			req.sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
		}

		return next();
	});
