import { NextFunction, Request, Response } from 'express';
import { HttpStatus } from './httpStatus';
import { ZodError, ZodSchema } from 'zod';
import { ResponseStatus, ServiceResponse } from './ServiceResponse';
import log from '@logger';
import { LogOpts } from '@root/src/types';
import { errMsg } from './errMsg';

const logOpts: LogOpts = {
  scope: 'validateRequests',
  itemType: 'type',
  msgType: 'VALIDATION',
};

const sensitiveKeys = new Set([
  'password',
  'currentpassword',
  'newpassword',
  'apikey',
  'sso_client_secret',
  'sso_cert',
  'clientsecret',
  'secret',
  'token',
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sanitizeValueForLogging = (value: any): any => {
  if (Array.isArray(value)) {
    return value.map((item) => sanitizeValueForLogging(item));
  }
  if (value && typeof value === 'object') {
    return Object.entries(value).reduce((acc, [key, val]) => {
      if (sensitiveKeys.has(key.toLowerCase())) {
        acc[key] = '[REDACTED]';
      } else {
        acc[key] = sanitizeValueForLogging(val);
      }
      return acc;
    }, Array.isArray(value) ? [] : {} as Record<string, unknown>);
  }
  return value;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getReceivedValueFromRequest(request: { body: any; query: any; params: any }, path: (string | number)[]): any {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let currentValue: any = request;
  path.forEach(segment => {
    currentValue = currentValue[segment];
  });
  return currentValue;
}

export const validateRequest = (schema: ZodSchema, endpoint = '') => (req: Request, res: Response, next: NextFunction) => {
  try {
    const parsed = schema.parse({
      body: req.body,
      query: req.query,
      params: req.params
    });
    if (parsed.body) req.body = parsed.body;
    next();
  } catch (err) {

    if (err instanceof ZodError) {
      // ZodError always has .errors property (array of ZodIssue)
      // Use Array.isArray check to ensure we have a valid array
      const zodErrors = Array.isArray(err.errors) ? err.errors : [];

      if (zodErrors.length === 0) {
        log.error(`ZodError with empty errors array! Raw error: ${JSON.stringify(err)}`, logOpts);
      }

      const sanitizedBody = sanitizeValueForLogging(req.body);
      const sanitizedQuery = sanitizeValueForLogging(req.query);
      const sanitizedParams = sanitizeValueForLogging(req.params);
      const errors = zodErrors.map((e: any) => {
        const receivedValue = getReceivedValueFromRequest(
          { body: sanitizedBody, query: sanitizedQuery, params: sanitizedParams },
          e.path
        );
        return `\nError path: '${e.path.join('.')}': \nError ${e.message}, but received ${JSON.stringify(receivedValue)}`;
      }).join(', ');

      const errorMessage = ` ${endpoint ? '\nValidation error in the endpoint: "' + endpoint + '"' : ''}`
        + `${errors}, \nHTTP PROPERTIES:\n\tbody: ${JSON.stringify(sanitizedBody, null, '\t')}, `
        + `\n\tquery: ${JSON.stringify(sanitizedQuery, null, '\t')}, \n\tparams: ${JSON.stringify(sanitizedParams, null, '\t')}`;

      const statusCode = HttpStatus.BAD_REQUEST;
      log.error(errorMessage, logOpts);
      res.status(statusCode).send(new ServiceResponse<null>(ResponseStatus.Failed, errorMessage, null, statusCode));
    } else {
      log.error(`Unexpected error: ${errMsg(err)}`, logOpts);
      next(err);
    }
  }
};
