<?php
/**
 * Input Validator for Ton Sui Mining API
 * 
 * Provides consistent input validation and sanitization for all API endpoints
 */

class InputValidator {
    // Validation rules cache
    private static $rules = [];
    
    // Validation errors
    private static $errors = [];
    
    // Default validation rules
    private static $defaultRules = [
        'required' => [
            'pattern' => '/\S+/',
            'message' => 'This field is required'
        ],
        'email' => [
            'pattern' => '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
            'message' => 'Please enter a valid email address'
        ],
        'alphanumeric' => [
            'pattern' => '/^[a-zA-Z0-9]+$/',
            'message' => 'Only alphanumeric characters are allowed'
        ],
        'numeric' => [
            'pattern' => '/^[0-9]+$/',
            'message' => 'Only numeric values are allowed'
        ],
        'decimal' => [
            'pattern' => '/^[0-9]+(\.[0-9]+)?$/',
            'message' => 'Please enter a valid decimal number'
        ],
        'username' => [
            'pattern' => '/^[a-zA-Z0-9_]{3,30}$/',
            'message' => 'Username must be 3-30 characters and can only contain letters, numbers, and underscores'
        ],
        'password' => [
            'pattern' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/',
            'message' => 'Password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one number, and one special character'
        ],
        'phone' => [
            'pattern' => '/^\+?[0-9\s-()]{10,20}$/',
            'message' => 'Please enter a valid phone number'
        ],
        'url' => [
            'pattern' => '/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/',
            'message' => 'Please enter a valid URL'
        ],
        'date' => [
            'callback' => 'validateDate',
            'message' => 'Please enter a valid date (YYYY-MM-DD)'
        ],
        'datetime' => [
            'callback' => 'validateDateTime',
            'message' => 'Please enter a valid date and time (YYYY-MM-DD HH:MM:SS)'
        ],
        'boolean' => [
            'callback' => 'validateBoolean',
            'message' => 'Please enter a valid boolean value (true/false, 1/0)'
        ]
    ];
    
    /**
     * Initialize the validator with custom rules
     */
    public static function init($customRules = []) {
        self::$rules = array_merge(self::$defaultRules, $customRules);
    }
    
    /**
     * Validate input data against rules
     * 
     * @param array $data Input data to validate
     * @param array $validationRules Validation rules
     * @return bool True if validation passes, false otherwise
     */
    public static function validate($data, $validationRules) {
        self::$errors = [];
        
        foreach ($validationRules as $field => $rules) {
            $value = $data[$field] ?? null;
            $isRequired = in_array('required', $rules);
            
            // Skip validation if field is not required and empty
            if (!$isRequired && (is_null($value) || $value === '')) {
                continue;
            }
            
            // Check required fields
            if ($isRequired && (is_null($value) || $value === '')) {
                self::addError($field, 'This field is required');
                continue;
            }
            
            // Apply validation rules
            foreach ($rules as $rule) {
                if ($rule === 'required') {
                    continue; // Already checked
                }
                
                // Handle custom validation rules
                if (strpos($rule, ':') !== false) {
                    list($ruleName, $ruleParam) = explode(':', $rule, 2);
                    if (!self::applyRule($ruleName, $value, $ruleParam)) {
                        break; // Stop validation on first error
                    }
                } else {
                    if (!self::applyRule($rule, $value)) {
                        break; // Stop validation on first error
                    }
                }
            }
        }
        
        return empty(self::$errors);
    }
    
    /**
     * Apply a single validation rule
     */
    private static function applyRule($rule, $value, $param = null) {
        if (!isset(self::$rules[$rule])) {
            throw new Exception("Validation rule '$rule' is not defined");
        }
        
        $ruleConfig = self::$rules[$rule];
        $isValid = false;
        
        // Check if this is a callback rule
        if (isset($ruleConfig['callback'])) {
            $callback = $ruleConfig['callback'];
            if (is_callable([__CLASS__, $callback])) {
                $isValid = self::$callback($value, $param);
            } else if (is_callable($callback)) {
                $isValid = $callback($value, $param);
            } else {
                throw new Exception("Validation callback '$callback' is not callable");
            }
        } 
        // Check if this is a regex pattern
        elseif (isset($ruleConfig['pattern'])) {
            $isValid = (bool) preg_match($ruleConfig['pattern'], $value);
        }
        
        if (!$isValid) {
            $message = $ruleConfig['message'] ?? "Validation failed for rule '$rule'";
            self::addError($rule, $message);
            return false;
        }
        
        return true;
    }
    
    /**
     * Add a validation error
     */
    private static function addError($field, $message) {
        if (!isset(self::$errors[$field])) {
            self::$errors[$field] = [];
        }
        self::$errors[$field][] = $message;
    }
    
    /**
     * Get all validation errors
     */
    public static function getErrors() {
        return self::$errors;
    }
    
    /**
     * Get the first error message for a field
     */
    public static function getFirstError($field) {
        return self::$errors[$field][0] ?? null;
    }
    
    /**
     * Get all error messages as a flat array
     */
    public static function getAllErrorMessages() {
        $messages = [];
        foreach (self::$errors as $fieldErrors) {
            $messages = array_merge($messages, $fieldErrors);
        }
        return $messages;
    }
    
    /**
     * Sanitize input data
     */
    public static function sanitize($data, $rules = []) {
        $sanitized = [];
        
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $sanitized[$key] = self::sanitize($value, $rules[$key] ?? []);
            } else {
                // Apply default sanitization
                $sanitizedValue = trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
                
                // Apply custom sanitization if specified in rules
                if (isset($rules[$key])) {
                    $fieldRules = is_array($rules[$key]) ? $rules[$key] : [$rules[$key]];
                    
                    foreach ($fieldRules as $rule) {
                        switch ($rule) {
                            case 'string':
                                $sanitizedValue = (string) $sanitizedValue;
                                break;
                                
                            case 'int':
                            case 'integer':
                                $sanitizedValue = (int) $sanitizedValue;
                                break;
                                
                            case 'float':
                                $sanitizedValue = (float) $sanitizedValue;
                                break;
                                
                            case 'bool':
                            case 'boolean':
                                $sanitizedValue = (bool) $sanitizedValue;
                                break;
                                
                            case 'email':
                                $sanitizedValue = filter_var($sanitizedValue, FILTER_SANITIZE_EMAIL);
                                break;
                                
                            case 'url':
                                $sanitizedValue = filter_var($sanitizedValue, FILTER_SANITIZE_URL);
                                break;
                                
                            case 'special_chars':
                                $sanitizedValue = htmlspecialchars($sanitizedValue, ENT_QUOTES, 'UTF-8');
                                break;
                                
                            case 'strip_tags':
                                $sanitizedValue = strip_tags($sanitizedValue);
                                break;
                                
                            case 'trim':
                                $sanitizedValue = trim($sanitizedValue);
                                break;
                                
                            case 'uppercase':
                                $sanitizedValue = strtoupper($sanitizedValue);
                                break;
                                
                            case 'lowercase':
                                $sanitizedValue = strtolower($sanitizedValue);
                                break;
                                
                            case 'ucfirst':
                                $sanitizedValue = ucfirst(strtolower($sanitizedValue));
                                break;
                                
                            case 'ucwords':
                                $sanitizedValue = ucwords(strtolower($sanitizedValue));
                                break;
                        }
                    }
                }
                
                $sanitized[$key] = $sanitizedValue;
            }
        }
        
        return $sanitized;
    }
    
    /**
     * Validate a date string (YYYY-MM-DD)
     */
    private static function validateDate($value) {
        $date = date_parse($value);
        return $date['error_count'] === 0 && checkdate($date['month'], $date['day'], $date['year']);
    }
    
    /**
     * Validate a datetime string (YYYY-MM-DD HH:MM:SS)
     */
    private static function validateDateTime($value) {
        $datetime = date_parse($value);
        return $datetime['error_count'] === 0 && checkdate(
            $datetime['month'], 
            $datetime['day'], 
            $datetime['year']
        );
    }
    
    /**
     * Validate a boolean value
     */
    private static function validateBoolean($value) {
        $valid = [true, false, 0, 1, '0', '1', 'true', 'false', 'yes', 'no', 'on', 'off'];
        return in_array($value, $valid, true);
    }
    
    /**
     * Validate that a value is at least a certain length
     */
    public static function min($value, $min) {
        if (is_numeric($value)) {
            return $value >= $min;
        } elseif (is_string($value)) {
            return mb_strlen($value) >= $min;
        } elseif (is_array($value)) {
            return count($value) >= $min;
        }
        return false;
    }
    
    /**
     * Validate that a value is at most a certain length
     */
    public static function max($value, $max) {
        if (is_numeric($value)) {
            return $value <= $max;
        } elseif (is_string($value)) {
            return mb_strlen($value) <= $max;
        } elseif (is_array($value)) {
            return count($value) <= $max;
        }
        return false;
    }
    
    /**
     * Validate that a value is between min and max (inclusive)
     */
    public static function between($value, $min, $max) {
        return self::min($value, $min) && self::max($value, $max);
    }
    
    /**
     * Validate that a value is in a list of allowed values
     */
    public static function in($value, $allowed) {
        if (is_string($allowed)) {
            $allowed = explode(',', $allowed);
            $allowed = array_map('trim', $allowed);
        }
        return in_array($value, $allowed, true);
    }
    
    /**
     * Validate that a value is not in a list of disallowed values
     */
    public static function notIn($value, $disallowed) {
        return !self::in($value, $disallowed);
    }
    
    /**
     * Validate that a value matches a pattern
     */
    public static function regex($value, $pattern) {
        return (bool) preg_match($pattern, $value);
    }
    
    /**
     * Validate that a value is a valid IP address
     */
    public static function ip($value) {
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
    }
    
    /**
     * Validate that a value is a valid IPv4 address
     */
    public static function ipv4($value) {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
    }
    
    /**
     * Validate that a value is a valid IPv6 address
     */
    public static function ipv6($value) {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
    }
    
    /**
     * Validate that a value is a valid MAC address
     */
    public static function mac($value) {
        return filter_var($value, FILTER_VALIDATE_MAC) !== false;
    }
    
    /**
     * Validate that a value is a valid domain
     */
    public static function domain($value) {
        return filter_var($value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false;
    }
}

// Initialize with default rules
InputValidator::init();
