/**
 * Validation Helper
 * Utility untuk membantu validasi data dengan error message yang jelas
 */

const { createValidationError, createNotFoundError } = require('./createError');

class ValidationHelper {
	/**
	 * Validate required fields
	 * @param {Object} data - Data to validate
	 * @param {Array<string>} requiredFields - Array of required field names
	 * @throws {AppError} If validation fails
	 */
	static validateRequired(data, requiredFields) {
		const errors = {};
		
		requiredFields.forEach((field) => {
			if (data[field] === undefined || data[field] === null || data[field] === '') {
				errors[field] = `${this.formatFieldName(field)} is required`;
			}
		});
		
		if (Object.keys(errors).length > 0) {
			throw createValidationError(errors);
		}
	}

	/**
	 * Validate field exists in database
	 * @param {Object} model - Sequelize model
	 * @param {string|number} id - ID to check
	 * @param {string} fieldName - Field name for error message
	 * @returns {Promise<Object>} Found record
	 * @throws {AppError} If not found
	 */
	static async validateExists(model, id, fieldName = 'Record') {
		if (!id) {
			throw createValidationError({
				[fieldName.toLowerCase()]: `${fieldName} ID is required`,
			});
		}

		const record = await model.findByPk(id);
		if (!record) {
			throw createNotFoundError(fieldName, id);
		}

		return record;
	}

	/**
	 * Validate unique value
	 * @param {Object} model - Sequelize model
	 * @param {string} field - Field name
	 * @param {*} value - Value to check
	 * @param {number|null} excludeId - ID to exclude from check (for updates)
	 * @throws {AppError} If value already exists
	 */
	static async validateUnique(model, field, value, excludeId = null) {
		const where = { [field]: value };
		if (excludeId) {
			where.id = { [require('sequelize').Op.ne]: excludeId };
		}

		const existing = await model.findOne({ where });
		if (existing) {
			throw createValidationError({
				[field]: `${this.formatFieldName(field)} already exists`,
			});
		}
	}

	/**
	 * Validate email format
	 * @param {string} email - Email to validate
	 * @param {string} fieldName - Field name for error
	 * @throws {AppError} If invalid
	 */
	static validateEmail(email, fieldName = 'email') {
		if (!email) return;
		
		const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
		if (!emailRegex.test(email)) {
			throw createValidationError({
				[fieldName]: 'Invalid email format',
			});
		}
	}

	/**
	 * Validate number range
	 * @param {number} value - Value to validate
	 * @param {number} min - Minimum value
	 * @param {number} max - Maximum value
	 * @param {string} fieldName - Field name for error
	 * @throws {AppError} If out of range
	 */
	static validateRange(value, min, max, fieldName = 'value') {
		if (value === undefined || value === null) return;
		
		const numValue = Number(value);
		if (isNaN(numValue)) {
			throw createValidationError({
				[fieldName]: `${this.formatFieldName(fieldName)} must be a number`,
			});
		}

		if (numValue < min || numValue > max) {
			throw createValidationError({
				[fieldName]: `${this.formatFieldName(fieldName)} must be between ${min} and ${max}`,
			});
		}
	}

	/**
	 * Validate string length
	 * @param {string} value - Value to validate
	 * @param {number} min - Minimum length
	 * @param {number} max - Maximum length
	 * @param {string} fieldName - Field name for error
	 * @throws {AppError} If invalid length
	 */
	static validateLength(value, min, max, fieldName = 'value') {
		if (value === undefined || value === null) return;
		
		const strValue = String(value);
		if (strValue.length < min || strValue.length > max) {
			throw createValidationError({
				[fieldName]: `${this.formatFieldName(fieldName)} must be between ${min} and ${max} characters`,
			});
		}
	}

	/**
	 * Validate enum value
	 * @param {*} value - Value to validate
	 * @param {Array} allowedValues - Array of allowed values
	 * @param {string} fieldName - Field name for error
	 * @throws {AppError} If not in allowed values
	 */
	static validateEnum(value, allowedValues, fieldName = 'value') {
		if (value === undefined || value === null) return;
		
		if (!allowedValues.includes(value)) {
			throw createValidationError({
				[fieldName]: `${this.formatFieldName(fieldName)} must be one of: ${allowedValues.join(', ')}`,
			});
		}
	}

	/**
	 * Format field name untuk error message (camelCase to Title Case)
	 * @param {string} field - Field name
	 * @returns {string} Formatted name
	 */
	static formatFieldName(field) {
		return field
			.replace(/([A-Z])/g, ' $1')
			.replace(/^./, (str) => str.toUpperCase())
			.trim();
	}

	/**
	 * Validate multiple fields at once
	 * @param {Object} data - Data to validate
	 * @param {Object} rules - Validation rules
	 * @example
	 * validateFields(data, {
	 *   email: { required: true, email: true },
	 *   age: { required: true, range: [18, 100] },
	 *   name: { required: true, length: [3, 50] }
	 * });
	 */
	static validateFields(data, rules) {
		const errors = {};

		Object.keys(rules).forEach((field) => {
			const rule = rules[field];
			const value = data[field];

			// Required check
			if (rule.required && (value === undefined || value === null || value === '')) {
				errors[field] = `${this.formatFieldName(field)} is required`;
				return;
			}

			// Skip other validations if value is empty and not required
			if (!rule.required && (value === undefined || value === null || value === '')) {
				return;
			}

			// Email validation
			if (rule.email) {
				const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
				if (!emailRegex.test(value)) {
					errors[field] = `${this.formatFieldName(field)} must be a valid email`;
				}
			}

			// Range validation
			if (rule.range) {
				const [min, max] = rule.range;
				const numValue = Number(value);
				if (isNaN(numValue) || numValue < min || numValue > max) {
					errors[field] = `${this.formatFieldName(field)} must be between ${min} and ${max}`;
				}
			}

			// Length validation
			if (rule.length) {
				const [min, max] = rule.length;
				const strValue = String(value);
				if (strValue.length < min || strValue.length > max) {
					errors[field] = `${this.formatFieldName(field)} must be between ${min} and ${max} characters`;
				}
			}

			// Enum validation
			if (rule.enum) {
				if (!rule.enum.includes(value)) {
					errors[field] = `${this.formatFieldName(field)} must be one of: ${rule.enum.join(', ')}`;
				}
			}

			// Custom validator
			if (rule.validator && typeof rule.validator === 'function') {
				try {
					const result = rule.validator(value, data);
					if (result !== true && typeof result === 'string') {
						errors[field] = result;
					}
				} catch (err) {
					errors[field] = err.message || 'Validation failed';
				}
			}
		});

		if (Object.keys(errors).length > 0) {
			throw createValidationError(errors);
		}
	}
}

module.exports = ValidationHelper;
