import { ClassConstructor, plainToClass } from 'class-transformer';
import { validateSync, ValidationError } from 'class-validator';
import { Request, NextFunction, RequestHandler } from 'express';
import { ErrorCodes } from '../../business/errors/errorCodes';
import { GenericInternalServerError } from '../../business/errors/genericInternalServerError';
import { MultipleBadRequestError } from '../../business/errors/multipleBadRequestError';
import { HumandLocals } from '../interfaces/locals';
import { HumandResponse } from '../types/response';
import { ValidationClass } from '../validationClasses/validationClass';

const checkAndPushErrors = (errors: ValidationError[], errorTexts: { [type: string]: string }[], prefix?: string) => {
    errors.forEach((error: ValidationError) => {
        if (error.constraints != null) {
            Object.entries(error.constraints).forEach(([key, value]) => {
                if (error.constraints) {
                    error.constraints[key] = `${prefix ? `${prefix}.` : ''}${value}`;
                }
            });
            errorTexts.push(error.constraints);
        } else if (error.children && error.children.length) {
            const nextPrefix = prefix ? `${prefix}.${error.property}` : error.property;
            checkAndPushErrors(error.children, errorTexts, nextPrefix);
        }
    });
};

export const genericValidateParams = <VC extends ValidationClass, T extends HumandLocals = HumandLocals>(
    inputFunction: (req: Request) => VC,
    outputFunction: (res: HumandResponse<T>, output: VC) => void,
    modelClass: ClassConstructor<VC>,
    throwError = false,
): RequestHandler<any, HumandResponse<T>, any, T> =>
    function internalValidateParams(req: Request, res: HumandResponse<T>, next: NextFunction) {
        const output: VC = plainToClass(modelClass, inputFunction(req), { excludeExtraneousValues: true });
        let errors: ValidationError[] = [];
        try {
            errors = validateSync(output, { skipMissingProperties: true });
        } catch (err) {
            next(new GenericInternalServerError('Unknown error validating parameters', ErrorCodes.UNKNOWN_ERROR, err));
        }
        // errors is an array of validation errors
        if (errors.length > 0) {
            const errorTexts: Record<string, string>[] = [];
            checkAndPushErrors(errors, errorTexts);
            const error = new MultipleBadRequestError(errorTexts);
            if (throwError) {
                throw error;
            } else {
                next(error);
            }
        } else {
            try {
                outputFunction(res, output);
            } catch (error) {
                next(error);
            }
            if (!throwError) next();
        }
    } as unknown as RequestHandler<any, HumandResponse<T>, any, T>;
