import { RequestHandler } from "express";
import { z } from "zod/v4";
import { InternalApiError, ValidationApiError } from "../errors";
import { ExpressController } from "../express-controller";

/**
 * Middleware genérico para validar un objeto de Express
 * (`body`, `query`, o `params`) mediante un esquema Zod.
 *
 * @param schema  Esquema Zod que valida la entrada.
 * @param source  Parte del request que se valida ('body' | 'query' | 'params').
 *                Por defecto, 'body'.
 * @param options Opciones adicionales:
 *                - `sanitize`: Si se debe reescribir `req.body` con los datos validados.
 *
 * @example
 *  router.post('/invoices',
 *    validateZod(CreateInvoiceCommandSchema),        // body
 *    controller
 *  );
 *
 *  router.get('/invoices',
 *    validateZod(ListInvoicesQuerySchema, 'query'),  // query
 *    controller
 *  );
 *
 *  router.patch('/invoices/:id/status',
 *    validateZod(ChangeStatusParamsSchema, 'params'), // params
 *    controller
 *  );
 */

export type ValidateRequestWithSchemaOptions = {
  sanitize?: boolean; // Si se debe reescribir req.body con los datos validados
};

export const validateRequest = <T extends "body" | "query" | "params">(
  schema: z.ZodObject<any>,
  source: T = "body" as T,
  options: ValidateRequestWithSchemaOptions = { sanitize: true }
): RequestHandler => {
  return async (req, res, next) => {
    console.debug(`Validating request ${source} with schema.`);
    console.debug(req[source]);

    if (!schema) {
      console.debug("ERROR: Undefined schema!!");
      return ExpressController.errorResponse(new InternalApiError("Undefined schema"), req, res);
    }

    try {
      const result = schema.safeParse(req[source]);

      if (!result.success) {
        // Construye errores detallados
        const validationErrors = result.error.issues.map((err) => ({
          field: err.path.join("."),
          message: err.message,
        }));

        console.debug(validationErrors);

        return ExpressController.errorResponse(
          new ValidationApiError("Validation failed", validationErrors),
          req,
          res
        );
      }

      // Si pasa la validación, opcionalmente reescribe req.body
      if (options?.sanitize ?? true) {
        req[source] = result.data;
      }

      console.debug(`Request ${source} is valid.`);

      next();
    } catch (err) {
      const error = err as Error;
      console.error(error);

      return ExpressController.errorResponse(new InternalApiError(error.message), req, res);
    }
  };
};
