import {
  ArgumentMetadata,
  HttpException,
  HttpStatus,
  Injectable,
  PipeTransform,
} from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { validate, ValidationError } from 'class-validator';

/**
 * CustomValidationPipe - Custom validation pipe for DTO validation
 *
 * This pipe validates incoming request data against DTO classes using
 * class-validator and class-transformer. It provides enhanced error
 * handling with structured error responses.
 *
 * Features:
 * - Automatic DTO validation for request payloads
 * - Structured error responses with field-specific messages
 * - Skip validation for primitive types (String, Boolean, Number, Array, Object)
 * - Early return for valid data to improve performance
 *
 * @example
 * // Apply to controller method
 * @Post('create')
 * createUser(@Body(new CustomValidationPipe()) createUserDto: CreateUserDto) {
 *   return this.userService.create(createUserDto);
 * }
 *
 * @example
 * // Error response format
 * {
 *   "success": false,
 *   "errors": {
 *     "email": "email must be an email",
 *     "password": "password must be longer than or equal to 8 characters"
 *   },
 *   "data": null
 * }
 */
@Injectable()
export class CustomValidationPipe implements PipeTransform<any> {
  /**
   * Transforms and validates incoming request data
   *
   * Process:
   * 1. Checks if metatype needs validation (skips primitives)
   * 2. Transforms plain object to class instance
   * 3. Validates against class-validator decorators
   * 4. Returns original value if valid
   * 5. Throws structured HttpException if validation fails
   *
   * @param value - Incoming request data to validate
   * @param metadata - Argument metadata containing type information
   * @returns Original value if validation passes
   * @throws HttpException with structured error response if validation fails
   *
   * @note Returns early for valid data to avoid unnecessary processing
   * @note Throws on first validation error encountered
   */
  async transform(value: any, { metatype }: ArgumentMetadata) {
    // Skip validation for primitive types and arrays/objects without decorators
    if (!metatype || !this.validateMetaType(metatype)) {
      return value;
    }

    // Transform plain object to class instance for validation
    const object = plainToClass(metatype, value);

    // Validate against class-validator decorators
    const errors = await validate(object);

    // Return original value if no validation errors
    if (errors.length === 0) return value;

    // Process validation errors and throw structured exception
    this.handleValidationErrors(errors);
  }

  /**
   * Handles validation errors and throws structured exception
   *
   * Extracts field-specific error messages and throws an HttpException
   * with a clean, structured error response.
   *
   * @param errors - Array of validation errors from class-validator
   * @throws HttpException with field-specific error messages
   *
   * @note Only processes first error per field
   * @note Uses the last constraint message as error text
   */
  private handleValidationErrors(errors: ValidationError[]): void {
    // Process first error to throw immediately (fail-fast)
    const firstError = errors[0];
    const key = firstError.property;

    // Extract error message from constraints
    const errorMessage = this.extractErrorMessage(firstError, key);

    // Create structured error object
    const errorResponse = {
      success: false,
      errors: { [key]: errorMessage },
      data: null,
    };

    // Throw structured exception
    throw new HttpException(errorResponse, HttpStatus.BAD_REQUEST);
  }

  /**
   * Extracts error message from validation error
   *
   * Gets the most appropriate error message from validation constraints,
   * with fallback to generic error message.
   *
   * @param error - Validation error from class-validator
   * @param key - Field name that failed validation
   * @returns Error message string
   */
  private extractErrorMessage(error: ValidationError, key: string): string {
    const { constraints } = error;

    // Check if constraints exist and are valid
    if (constraints && Object.keys(constraints).length > 0) {
      // Use the last constraint message (often the most specific)
      return Object.values(constraints).pop() as string;
    }

    // Fallback to generic error message
    return `Invalid value entry for ${key} field.`;
  }

  /**
   * Determines if a metatype should be validated
   *
   * Primitive types (String, Boolean, Number, Array, Object) don't have
   * class-validator decorators, so we skip validation for them.
   *
   * @param metatype - Constructor/type to check
   * @returns true if the type should be validated, false otherwise
   */
  private validateMetaType(metatype: any): boolean {
    const primitiveTypes: any[] = [String, Boolean, Number, Array, Object];
    return !primitiveTypes.includes(metatype);
  }

  /**
   * Enhanced error handling that could be added:
   *
   * private collectAllErrors(errors: ValidationError[]): Record<string, string[]> {
   *   // Collect all errors for all fields instead of failing on first error
   * }
   *
   * private transformNestedErrors(error: ValidationError, prefix = ''): Record<string, string> {
   *   // Handle nested object validation errors
   * }
   *
   * private getValidationGroups(metatype: any): string[] {
   *   // Support validation groups from DTO metadata
   * }
   */
}
