/**
 * Input validation and sanitization
 * Ported from Python validators.py
 */

// ============================================================================
// Types
// ============================================================================

export interface WebSocketMessage {
  type: string;
  userID?: string;
  value?: string | number;
  id?: string;
  response?: string;
  message?: Record<string, unknown>;
  [key: string]: unknown;
}

// ============================================================================
// Custom Error
// ============================================================================

export class ValidationError extends Error {
  public readonly field?: string;

  constructor(message: string, field?: string) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

// ============================================================================
// Validator Class
// ============================================================================

/**
 * Input validator with common patterns
 */
export class Validator {
  /**
   * Validate user ID format.
   */
  static validateUserId(userId: unknown): string {
    if (!userId || typeof userId !== 'string') {
      throw new ValidationError('User ID must be a non-empty string', 'userId');
    }

    // Only allow alphanumeric, dash, underscore
    if (!/^[a-zA-Z0-9_-]+$/.test(userId)) {
      throw new ValidationError('User ID contains invalid characters', 'userId');
    }

    if (userId.length > 64) {
      throw new ValidationError('User ID too long (max 64 characters)', 'userId');
    }

    return userId;
  }

  /**
   * Validate game command.
   */
  static validateCommand(command: unknown): string {
    const validCommands = new Set(['start', 'stop', 'restart', 'pause', 'resume']);

    if (typeof command !== 'string' || !validCommands.has(command)) {
      throw new ValidationError(
        `Invalid command. Must be one of: ${[...validCommands].join(', ')}`,
        'command'
      );
    }

    return command;
  }

  /**
   * Validate game speed.
   */
  static validateSpeed(speed: unknown): number {
    const speedNum = typeof speed === 'string' ? parseInt(speed, 10) : speed;

    if (typeof speedNum !== 'number' || isNaN(speedNum)) {
      throw new ValidationError('Speed must be a number', 'speed');
    }

    if (speedNum < 1 || speedNum > 10000) {
      throw new ValidationError('Speed must be between 1 and 10000', 'speed');
    }

    return speedNum;
  }

  /**
   * Validate question/answer ID.
   */
  static validateAnswerId(answerId: unknown): string {
    if (!answerId || typeof answerId !== 'string') {
      throw new ValidationError('Answer ID must be a non-empty string', 'answerId');
    }

    // Only allow alphanumeric and underscore (function names)
    if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(answerId)) {
      throw new ValidationError('Answer ID contains invalid characters', 'answerId');
    }

    if (answerId.length > 128) {
      throw new ValidationError('Answer ID too long', 'answerId');
    }

    return answerId;
  }

  /**
   * Validate and sanitize response text.
   */
  static validateResponseText(response: unknown): string {
    if (typeof response !== 'string') {
      throw new ValidationError('Response must be a string', 'response');
    }

    // Limit length
    if (response.length > 10000) {
      throw new ValidationError('Response too long (max 10000 characters)', 'response');
    }

    // Strip dangerous HTML/script tags
    let sanitized = response.replace(/<script[^>]*>.*?<\/script>/gi, '');
    sanitized = sanitized.replace(/<iframe[^>]*>.*?<\/iframe>/gi, '');

    return sanitized.trim();
  }

  /**
   * Validate email format.
   */
  static validateEmail(email: unknown): string {
    if (typeof email !== 'string') {
      throw new ValidationError('Email must be a string', 'email');
    }

    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      throw new ValidationError('Invalid email format', 'email');
    }

    if (email.length > 254) {
      throw new ValidationError('Email too long', 'email');
    }

    return email.toLowerCase().trim();
  }

  /**
   * Validate positive integer.
   */
  static validatePositiveInt(value: unknown, field = 'value'): number {
    const num = typeof value === 'string' ? parseInt(value, 10) : value;

    if (typeof num !== 'number' || isNaN(num) || num < 0 || !Number.isInteger(num)) {
      throw new ValidationError(`${field} must be a positive integer`, field);
    }

    return num;
  }

  /**
   * Validate UUID format.
   */
  static validateUuid(uuid: unknown, field = 'uuid'): string {
    if (typeof uuid !== 'string') {
      throw new ValidationError(`${field} must be a string`, field);
    }

    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    if (!uuidRegex.test(uuid)) {
      throw new ValidationError(`${field} is not a valid UUID`, field);
    }

    return uuid.toLowerCase();
  }

  /**
   * Sanitize string for SQL (escape quotes).
   * NOTE: Should use parameterized queries instead when possible.
   */
  static sanitizeSqlString(value: string): string {
    return value.replace(/'/g, "''").replace(/\\/g, '\\\\');
  }
}

// ============================================================================
// Message Validation
// ============================================================================

/**
 * Validate incoming WebSocket message.
 */
export function validateWebSocketMessage(message: unknown): WebSocketMessage {
  if (!message || typeof message !== 'object' || Array.isArray(message)) {
    throw new ValidationError('Message must be a JSON object');
  }

  const msg = message as Record<string, unknown>;

  // Validate type field
  if (!('type' in msg)) {
    throw new ValidationError("Message missing 'type' field");
  }

  if (typeof msg.type !== 'string') {
    throw new ValidationError('Message type must be a string');
  }

  const result: WebSocketMessage = { ...msg, type: msg.type };

  // Validate based on type
  switch (msg.type) {
    case 'init':
      if (!('userID' in msg)) {
        throw new ValidationError("Init message missing 'userID'");
      }
      result.userID = Validator.validateUserId(msg.userID);
      break;

    case 'command':
      if (!('value' in msg)) {
        throw new ValidationError("Command message missing 'value'");
      }
      result.value = Validator.validateCommand(msg.value);
      break;

    case 'speed':
      if (!('value' in msg)) {
        throw new ValidationError("Speed message missing 'value'");
      }
      result.value = Validator.validateSpeed(msg.value);
      break;

    case 'answer':
      if (!('id' in msg)) {
        throw new ValidationError("Answer message missing 'id'");
      }
      if (!('response' in msg)) {
        throw new ValidationError("Answer message missing 'response'");
      }
      result.id = Validator.validateAnswerId(msg.id);
      result.response = Validator.validateResponseText(msg.response);
      break;

    default:
      // Allow unknown types but log them
      console.log(`Unknown message type: ${msg.type}`);
  }

  return result;
}

// ============================================================================
// Export
// ============================================================================

export const validators = {
  ValidationError,
  Validator,
  validateWebSocketMessage,
};
