/**
 * Validation service for data management
 * Handles validation of data records, IDs, and data types
 */

import { DataRecord } from './lowdbService';

/**
 * Custom validation error class
 */
export class ValidationError extends Error {
  constructor(
    message: string,
    public field?: string,
    public details?: any
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

/**
 * Validate that a value is not empty
 */
export function validateRequired(value: any, fieldName: string): void {
  if (value === undefined || value === null || value === '') {
    throw new ValidationError(`Missing required field: ${fieldName}`, fieldName);
  }
}

/**
 * Validate file name format
 * File names can contain letters, numbers, underscores, hyphens, and Chinese characters
 */
export function validateFileName(fileName: string): void {
  if (!fileName || typeof fileName !== 'string') {
    throw new ValidationError('Invalid fileName parameter', 'fileName');
  }
  
  // Trim whitespace
  const trimmed = fileName.trim();
  if (trimmed === '') {
    throw new ValidationError('fileName cannot be empty', 'fileName');
  }
  
  // Check for invalid characters in file name (allow Chinese characters)
  // Disallow: / \ : * ? " < > |
  if (/[/\\:*?"<>|]/.test(trimmed)) {
    throw new ValidationError(
      'fileName cannot contain the following characters: / \\ : * ? " < > |',
      'fileName'
    );
  }
}

/**
 * Validate that ID field exists and is a valid string or number
 * Requirement 3.2: Validate the required id field before persisting
 * Updated to support both string and number IDs
 */
export function validateIdField(id: any): void {
  if (id === undefined || id === null) {
    throw new ValidationError('Missing required field: id', 'id');
  }
  
  // Accept both string and number types
  if (typeof id !== 'string' && typeof id !== 'number') {
    throw new ValidationError('Invalid id: must be a string or number', 'id');
  }
  
  // For string IDs, check if empty
  if (typeof id === 'string' && id.trim() === '') {
    throw new ValidationError('Invalid id: cannot be empty', 'id');
  }
  
  // For number IDs, check if valid (not NaN, not Infinity)
  if (typeof id === 'number' && (!Number.isFinite(id))) {
    throw new ValidationError('Invalid id: must be a finite number', 'id');
  }
}

/**
 * Validate data types for record fields
 * Requirement 3.4: Validate data types for all fields
 */
export function validateDataTypes(data: any): void {
  if (!data || typeof data !== 'object') {
    throw new ValidationError('Invalid data: must be an object');
  }
  
  // Check if data is an array (not allowed for single record)
  if (Array.isArray(data)) {
    throw new ValidationError('Invalid data: expected object, got array');
  }
  
  // Validate that all values are of acceptable types
  for (const [key, value] of Object.entries(data)) {
    // Skip id field as it's validated separately
    if (key === 'id') continue;
    
    // Check for undefined values (null is allowed)
    if (value === undefined) {
      throw new ValidationError(
        `Invalid data type for field '${key}': undefined is not allowed`,
        key
      );
    }
    
    // Check for functions (not allowed in JSON)
    if (typeof value === 'function') {
      throw new ValidationError(
        `Invalid data type for field '${key}': functions are not allowed`,
        key
      );
    }
    
    // Check for symbols (not allowed in JSON)
    if (typeof value === 'symbol') {
      throw new ValidationError(
        `Invalid data type for field '${key}': symbols are not allowed`,
        key
      );
    }
    
    // Nested objects are allowed, but check for circular references
    if (typeof value === 'object' && value !== null) {
      try {
        JSON.stringify(value);
      } catch (e) {
        throw new ValidationError(
          `Invalid data type for field '${key}': circular reference or non-serializable value`,
          key
        );
      }
    }
  }
}

/**
 * Check for duplicate ID in existing records
 * Requirement 3.3: Detect duplicate IDs
 * Updated to support both string and number IDs using loose equality
 */
export function checkDuplicateId(id: string | number, existingRecords: DataRecord[], excludeId?: string | number): void {
  const duplicate = existingRecords.find(record => {
    // If excludeId is provided (for updates), skip that record
    if (excludeId !== undefined && record.id == excludeId) {
      return false;
    }
    // Use loose equality to match both string and number IDs
    return record.id == id;
  });
  
  if (duplicate) {
    throw new ValidationError(
      `Duplicate id: a record with id '${id}' already exists`,
      'id',
      { duplicateId: id }
    );
  }
}

/**
 * Validate a complete record for insertion
 * Combines all validation checks
 */
export function validateRecordForInsert(data: any): void {
  // Validate data structure
  validateDataTypes(data);
  
  // If id is provided, validate it
  if (data.id !== undefined) {
    validateIdField(data.id);
  }
}

/**
 * Validate a record for update
 * Similar to insert but allows partial data
 */
export function validateRecordForUpdate(data: any): void {
  // Validate data structure
  validateDataTypes(data);
  
  // If id is provided in update data, validate it
  if (data.id !== undefined) {
    validateIdField(data.id);
  }
}

/**
 * Validate CSV import data
 * Checks each row for valid structure
 */
export function validateCSVData(csvData: any[]): void {
  if (!Array.isArray(csvData)) {
    throw new ValidationError('Invalid CSV data: must be an array');
  }
  
  if (csvData.length === 0) {
    throw new ValidationError('Invalid CSV data: cannot be empty');
  }
  
  // Validate each row
  csvData.forEach((row, index) => {
    try {
      validateDataTypes(row);
      
      // If row has id, validate it (support both string and number)
      if (row.id !== undefined && row.id !== null && row.id !== '') {
        validateIdField(row.id);
      }
    } catch (e) {
      if (e instanceof ValidationError) {
        throw new ValidationError(
          `Invalid data in CSV row ${index + 1}: ${e.message}`,
          e.field,
          { row: index + 1, ...e.details }
        );
      }
      throw e;
    }
  });
}
