import { HttpException, HttpStatus } from '@nestjs/common';
import type { ValidationError } from 'class-validator';
import { AppException } from './app.exception';
import { ErrorCode, type ErrorCodeValue, type ValidationErrorDetail } from './error-codes';

const KNOWN_VALIDATION_CODES = new Set<string>(Object.values(ErrorCode));

function isKnownValidationCode(value: string): value is ErrorCodeValue {
  return KNOWN_VALIDATION_CODES.has(value);
}

function constraintToCode(constraintKey: string, message: string): ErrorCodeValue {
  if (isKnownValidationCode(message)) {
    return message;
  }

  switch (constraintKey) {
    case 'isEmail':
      return ErrorCode.VALIDATION_EMAIL_INVALID;
    case 'minLength':
      return ErrorCode.VALIDATION_PASSWORD_TOO_SHORT;
    case 'isEnum':
      return ErrorCode.VALIDATION_ORGANIZATION_TYPE_INVALID;
    case 'matches':
    case 'isDateString':
    case 'isUuid':
    case 'isInt':
    case 'isBoolean':
    case 'isArray':
      return ErrorCode.VALIDATION_INVALID_REQUEST;
    case 'isIn':
      return ErrorCode.VALIDATION_LANGUAGE_INVALID;
    case 'whitelistValidation':
      return ErrorCode.VALIDATION_INVALID_REQUEST;
    default:
      return ErrorCode.VALIDATION_FIELD_REQUIRED;
  }
}

function flattenValidationErrors(
  errors: ValidationError[],
  parentPath = '',
): ValidationErrorDetail[] {
  const details: ValidationErrorDetail[] = [];

  for (const error of errors) {
    const field = parentPath ? `${parentPath}.${error.property}` : error.property;

    if (error.constraints) {
      const [constraintKey, message] = Object.entries(error.constraints)[0];
      details.push({
        field,
        code: constraintToCode(constraintKey, message),
      });
    }

    if (error.children?.length) {
      details.push(...flattenValidationErrors(error.children, field));
    }
  }

  return details;
}

export function validationExceptionFactory(errors: ValidationError[]): HttpException {
  const details = flattenValidationErrors(errors);

  if (details.length === 0) {
    return new AppException(ErrorCode.VALIDATION_FAILED, HttpStatus.BAD_REQUEST);
  }

  return new AppException(ErrorCode.VALIDATION_FAILED, HttpStatus.BAD_REQUEST, details);
}
