<?php

declare(strict_types=1);

/**
 * HVAC Security Helpers
 *
 * Centralized security functions for the HVAC Community Events plugin
 *
 * @package HVAC_Community_Events
 * @since 2.1.0
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Superglobal types constants for enhanced security
 */
final class SuperglobalType
{
    public const GET = 'GET';
    public const POST = 'POST';
    public const REQUEST = 'REQUEST';
    public const COOKIE = 'COOKIE';
    public const SERVER = 'SERVER';
}

/**
 * Sanitization methods constants
 */
final class SanitizationMethod
{
    public const ABSINT = 'absint';
    public const INTVAL = 'intval';
    public const SANITIZE_EMAIL = 'sanitize_email';
    public const SANITIZE_URL = 'sanitize_url';
    public const ESC_URL_RAW = 'esc_url_raw';
    public const SANITIZE_TEXT_FIELD = 'sanitize_text_field';
    public const SANITIZE_TEXTAREA_FIELD = 'sanitize_textarea_field';
    public const WP_KSES_POST = 'wp_kses_post';
    public const NONE = 'none';
}

/**
 * Escape contexts constants
 */
final class EscapeContext
{
    public const HTML = 'html';
    public const ATTR = 'attr';
    public const URL = 'url';
    public const JS = 'js';
    public const TEXTAREA = 'textarea';
}

/**
 * Security event types constants
 */
final class SecurityEventType
{
    public const AUTHENTICATION_FAILURE = 'auth_failure';
    public const RATE_LIMIT_EXCEEDED = 'rate_limit_exceeded';
    public const INVALID_NONCE = 'invalid_nonce';
    public const UNAUTHORIZED_ACCESS = 'unauthorized_access';
    public const FILE_UPLOAD_ATTEMPT = 'file_upload_attempt';
    public const SUSPICIOUS_REQUEST = 'suspicious_request';
}

/**
 * HVAC_Security_Helpers class
 * 
 * Modernized security helpers with PHP 8+ features for enhanced type safety and security
 */
final class HVAC_Security_Helpers
{
    private const DEFAULT_MAX_FILE_SIZE = 5242880; // 5MB
    private const DEFAULT_RATE_LIMIT_WINDOW = 60; // seconds
    private const DEFAULT_MAX_ATTEMPTS = 5;
    
    private const IP_HEADER_PRIORITY = [
        'HTTP_CF_CONNECTING_IP',
        'HTTP_CLIENT_IP', 
        'HTTP_X_FORWARDED_FOR',
        'REMOTE_ADDR'
    ];
    
    /**
     * Check if user has HVAC trainer role
     *
     * @param int|null $user_id User ID (null for current user)
     * @return bool True if user has trainer access
     */
    public static function is_hvac_trainer(?int $user_id = null): bool
    {
        $user = $user_id !== null ? get_user_by('id', $user_id) : wp_get_current_user();
        
        if (!$user instanceof \WP_User) {
            return false;
        }
        
        $trainer_roles = ['hvac_trainer', 'hvac_master_trainer'];
        $has_trainer_role = !empty(array_intersect($trainer_roles, $user->roles));
        $is_admin = user_can($user, 'manage_options');
        
        return $has_trainer_role || $is_admin;
    }
    
    /**
     * Check if user has HVAC master trainer role
     *
     * @param int|null $user_id User ID (null for current user)  
     * @return bool True if user has master trainer access
     */
    public static function is_hvac_master_trainer(?int $user_id = null): bool
    {
        $user = $user_id !== null ? get_user_by('id', $user_id) : wp_get_current_user();
        
        if (!$user instanceof \WP_User) {
            return false;
        }
        
        $has_master_role = in_array('hvac_master_trainer', $user->roles, true);
        $is_admin = user_can($user, 'manage_options');
        
        return $has_master_role || $is_admin;
    }
    
    /**
     * Sanitize and validate superglobal input
     *
     * @param string $type Superglobal type (use SuperglobalType constants)
     * @param string $key The key to retrieve
     * @param SanitizationMethod|string $sanitize Sanitization method to use
     * @param mixed $default Default value if not set
     * @return mixed Sanitized value
     */
    public static function get_input(
        string $type, 
        string $key, 
        string $sanitize = SanitizationMethod::SANITIZE_TEXT_FIELD, 
        $default = ''
    ) {
        $type_upper = strtoupper($type);
        
        // Validate type
        $valid_types = [SuperglobalType::GET, SuperglobalType::POST, SuperglobalType::REQUEST, SuperglobalType::COOKIE, SuperglobalType::SERVER];
        if (!in_array($type_upper, $valid_types, true)) {
            return $default;
        }
        
        $superglobal = match ($type_upper) {
            SuperglobalType::GET => $_GET,
            SuperglobalType::POST => $_POST,
            SuperglobalType::REQUEST => $_REQUEST,
            SuperglobalType::COOKIE => $_COOKIE,
            SuperglobalType::SERVER => $_SERVER,
        };
        
        if (!isset($superglobal[$key])) {
            return $default;
        }
        
        $value = $superglobal[$key];
        
        // Handle arrays with proper sanitization
        if (is_array($value)) {
            return self::sanitize_array($value, $sanitize);
        }
        
        return self::apply_sanitization($value, $sanitize);
    }
    
    /**
     * Sanitize array values recursively
     *
     * @param array $array Array to sanitize
     * @param SanitizationMethod|string $sanitize Sanitization method
     * @return array Sanitized array
     */
    private static function sanitize_array(array $array, string $sanitize): array
    {
        $sanitized = [];
        
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $sanitized[$key] = self::sanitize_array($value, $sanitize);
            } else {
                $sanitized[$key] = self::apply_sanitization($value, $sanitize);
            }
        }
        
        return $sanitized;
    }
    
    /**
     * Apply sanitization to a single value
     *
     * @param mixed $value Value to sanitize
     * @param SanitizationMethod|string $sanitize Sanitization method
     * @return mixed Sanitized value
     */
    private static function apply_sanitization($value, string $sanitize)
    {
        return match ($sanitize) {
            SanitizationMethod::ABSINT => absint($value),
            SanitizationMethod::INTVAL => intval($value),
            SanitizationMethod::SANITIZE_EMAIL => sanitize_email($value),
            SanitizationMethod::SANITIZE_URL, SanitizationMethod::ESC_URL_RAW => esc_url_raw($value),
            SanitizationMethod::SANITIZE_TEXT_FIELD => sanitize_text_field($value),
            SanitizationMethod::SANITIZE_TEXTAREA_FIELD => sanitize_textarea_field($value),
            SanitizationMethod::WP_KSES_POST => wp_kses_post($value),
            SanitizationMethod::NONE => $value, // Use with extreme caution
            default => sanitize_text_field($value), // Safe fallback
        };
    }
    
    /**
     * Validate file upload with enhanced security checks
     *
     * @param array $file $_FILES array element
     * @param array<string> $allowed_types Allowed MIME types
     * @param int $max_size Maximum file size in bytes
     * @return bool|\WP_Error True if valid, WP_Error on failure
     */
    public static function validate_file_upload(
        array $file, 
        array $allowed_types = [], 
        int $max_size = self::DEFAULT_MAX_FILE_SIZE
    ): bool|\WP_Error {
        // Validate file structure
        $required_keys = ['error', 'tmp_name', 'size', 'name', 'type'];
        foreach ($required_keys as $key) {
            if (!array_key_exists($key, $file)) {
                return new \WP_Error('invalid_file_structure', 'Invalid file upload structure');
            }
        }
        
        // Check upload error
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $error_message = match ($file['error']) {
                UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'File too large',
                UPLOAD_ERR_PARTIAL => 'File upload incomplete',
                UPLOAD_ERR_NO_FILE => 'No file uploaded',
                UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary directory',
                UPLOAD_ERR_CANT_WRITE => 'Cannot write file to disk',
                UPLOAD_ERR_EXTENSION => 'File upload blocked by extension',
                default => 'File upload failed'
            };
            return new \WP_Error('upload_error', $error_message);
        }
        
        // Security: Verify file was actually uploaded via HTTP POST
        if (!is_uploaded_file($file['tmp_name'])) {
            self::log_security_event(SecurityEventType::SUSPICIOUS_REQUEST, [
                'reason' => 'Non-uploaded file in upload validation',
                'file_name' => $file['name'] ?? 'unknown'
            ]);
            return new \WP_Error('security_error', 'Invalid file upload');
        }
        
        // Validate file size
        if ($file['size'] > $max_size) {
            return new \WP_Error(
                'size_error', 
                sprintf('File too large. Maximum size is %s', size_format($max_size))
            );
        }
        
        // Empty file check
        if ($file['size'] === 0) {
            return new \WP_Error('empty_file', 'Empty file not allowed');
        }
        
        // File type validation
        if (!empty($allowed_types)) {
            $file_type = wp_check_filetype($file['name']);
            
            if (empty($file_type['type']) || !in_array($file_type['type'], $allowed_types, true)) {
                return new \WP_Error(
                    'type_error', 
                    sprintf(
                        'Invalid file type. Allowed types: %s', 
                        implode(', ', $allowed_types)
                    )
                );
            }
        }
        
        // Additional security: Check for executable file extensions
        $dangerous_extensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'pl', 'py', 'jsp', 'asp', 'sh', 'cgi'];
        $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        
        if (in_array($file_extension, $dangerous_extensions, true)) {
            self::log_security_event(SecurityEventType::SUSPICIOUS_REQUEST, [
                'reason' => 'Dangerous file extension upload attempt',
                'file_name' => $file['name'],
                'extension' => $file_extension
            ]);
            return new \WP_Error('dangerous_file', 'Executable file types not allowed');
        }
        
        self::log_security_event(SecurityEventType::FILE_UPLOAD_ATTEMPT, [
            'file_name' => $file['name'],
            'file_size' => $file['size'],
            'mime_type' => $file['type']
        ]);
        
        return true;
    }
    
    /**
     * Generate secure nonce field
     *
     * @param string $action Nonce action
     * @param string $name Nonce field name
     * @return string HTML nonce field
     */
    public static function nonce_field(string $action, string $name = '_wpnonce'): string
    {
        return wp_nonce_field($action, $name, true, false);
    }
    
    /**
     * Verify nonce from request with enhanced security logging
     *
     * @param string $action Nonce action
     * @param string $name Nonce field name
     * @param string $type Request type (use SuperglobalType constants)
     * @return bool True if nonce is valid
     */
    public static function verify_nonce(
        string $action, 
        string $name = '_wpnonce', 
        string $type = SuperglobalType::POST
    ): bool {
        $nonce = self::get_input($type, $name, SanitizationMethod::SANITIZE_TEXT_FIELD, '');
        
        if (empty($nonce)) {
            self::log_security_event(SecurityEventType::INVALID_NONCE, [
                'action' => $action,
                'reason' => 'Empty nonce value'
            ]);
            return false;
        }
        
        $result = wp_verify_nonce($nonce, $action);
        
        if (!$result) {
            self::log_security_event(SecurityEventType::INVALID_NONCE, [
                'action' => $action,
                'nonce' => substr($nonce, 0, 10) . '...' // Log partial nonce for debugging
            ]);
            return false;
        }
        
        return true;
    }
    
    /**
     * Check AJAX referer with enhanced error handling and logging
     *
     * @param string $action Nonce action
     * @param string $query_arg Nonce query argument
     * @param bool $send_error Whether to send JSON error response
     * @return bool True if nonce is valid
     */
    public static function check_ajax_nonce(
        string $action, 
        string $query_arg = 'nonce', 
        bool $send_error = true
    ): bool {
        $result = check_ajax_referer($action, $query_arg, false);
        
        if (!$result) {
            self::log_security_event(SecurityEventType::INVALID_NONCE, [
                'action' => $action,
                'context' => 'AJAX request',
                'referer' => $_SERVER['HTTP_REFERER'] ?? 'unknown'
            ]);
            
            if ($send_error) {
                wp_send_json_error([
                    'message' => 'Security check failed',
                    'code' => 'invalid_nonce'
                ]);
            }
            
            return false;
        }
        
        return true;
    }
    
    /**
     * Escape output based on context with type safety
     *
     * @param mixed $data Data to escape
     * @param EscapeContext|string $context Escape context
     * @return string Escaped data
     */
    public static function escape($data, string $context = EscapeContext::HTML): string
    {
        // Convert complex data types to empty string for security
        if (is_array($data) || is_object($data)) {
            return '';
        }
        
        // Convert to string if not already
        $data = (string) $data;
        
        return match ($context) {
            EscapeContext::HTML => esc_html($data),
            EscapeContext::ATTR => esc_attr($data),
            EscapeContext::URL => esc_url($data),
            EscapeContext::JS => esc_js($data),
            EscapeContext::TEXTAREA => esc_textarea($data),
            default => esc_html($data), // Safe fallback
        };
    }
    
    /**
     * Validate and sanitize email with enhanced validation
     *
     * @param string $email Email address to validate
     * @return string|false Sanitized email or false if invalid
     */
    public static function validate_email(string $email)
    {
        // Basic sanitization
        $email = trim($email);
        $email = sanitize_email($email);
        
        // WordPress validation
        if (!is_email($email)) {
            return false;
        }
        
        // Additional length check (RFC 5321 limit)
        if (strlen($email) > 254) {
            return false;
        }
        
        // Check for disposable email domains (basic list)
        $disposable_domains = [
            '10minutemail.com', 'tempmail.org', 'guerrillamail.com', 
            'mailinator.com', 'trashmail.com'
        ];
        
        $domain = substr(strrchr($email, '@'), 1);
        if (in_array(strtolower($domain), $disposable_domains, true)) {
            self::log_security_event(SecurityEventType::SUSPICIOUS_REQUEST, [
                'reason' => 'Disposable email domain detected',
                'domain' => $domain
            ]);
            return false;
        }
        
        return $email;
    }
    
    /**
     * Add comprehensive security headers
     *
     * @param bool $strict Whether to use strict CSP policy
     * @return void
     */
    public static function add_security_headers(bool $strict = false): void
    {
        // Prevent output if headers already sent
        if (headers_sent()) {
            return;
        }
        
        $csp_policy = $strict 
            ? "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'"
            : "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'";
            
        $headers = [
            'Content-Security-Policy' => $csp_policy,
            'X-Frame-Options' => 'SAMEORIGIN',
            'X-Content-Type-Options' => 'nosniff',
            'X-XSS-Protection' => '1; mode=block',
            'Referrer-Policy' => 'strict-origin-when-cross-origin',
            'X-Permitted-Cross-Domain-Policies' => 'none',
            'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()'
        ];
        
        foreach ($headers as $name => $value) {
            header(sprintf('%s: %s', $name, $value));
        }
    }
    
    /**
     * Enhanced rate limiting with exponential backoff
     *
     * @param string $action Action identifier
     * @param int $max_attempts Maximum attempts allowed
     * @param int $window Time window in seconds
     * @return bool True if allowed, false if rate limited
     */
    public static function check_rate_limit(
        string $action, 
        int $max_attempts = self::DEFAULT_MAX_ATTEMPTS, 
        int $window = self::DEFAULT_RATE_LIMIT_WINDOW
    ): bool {
        $user_id = get_current_user_id();
        $ip = self::get_client_ip();
        $key = sprintf('hvac_rate_limit_%s', hash('sha256', $action . '_' . $user_id . '_' . $ip));
        
        $attempts = get_transient($key);
        
        if ($attempts === false) {
            set_transient($key, 1, $window);
            return true;
        }
        
        if ($attempts >= $max_attempts) {
            // Exponential backoff: extend the window for repeat offenders
            $extended_window = $window * min(8, pow(2, $attempts - $max_attempts));
            set_transient($key, $attempts + 1, $extended_window);
            
            self::log_security_event(SecurityEventType::RATE_LIMIT_EXCEEDED, [
                'action' => $action,
                'attempts' => $attempts,
                'window' => $window,
                'extended_window' => $extended_window
            ]);
            
            return false;
        }
        
        set_transient($key, $attempts + 1, $window);
        return true;
    }
    
    /**
     * Get client IP address with enhanced detection and validation
     *
     * @return string Client IP address
     */
    public static function get_client_ip(): string
    {
        foreach (self::IP_HEADER_PRIORITY as $header) {
            if (!array_key_exists($header, $_SERVER)) {
                continue;
            }
            
            $ip_list = $_SERVER[$header];
            $ips = array_map('trim', explode(',', $ip_list));
            
            foreach ($ips as $ip) {
                // Validate IP and exclude private/reserved ranges for public IPs
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
                
                // For local development, accept private ranges
                if (defined('WP_DEBUG') && WP_DEBUG && filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }
    
    /**
     * Enhanced security event logging with structured data
     *
     * @param SecurityEventType|string $event Event type
     * @param array<string, mixed> $data Additional event data
     * @return void
     */
    public static function log_security_event(string $event, array $data = []): void
    {
        $event_type = $event;
        
        $log_data = [
            'event' => $event_type,
            'timestamp' => current_time('mysql', true), // UTC timestamp
            'user_id' => get_current_user_id(),
            'ip' => self::get_client_ip(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
            'request_method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
            'data' => $data
        ];
        
        // Always log to error log with structured format
        error_log(sprintf(
            '[HVAC Security] %s - %s',
            strtoupper($event_type),
            wp_json_encode($log_data, JSON_UNESCAPED_SLASHES)
        ));
        
        // Optional database logging for persistent storage
        if (defined('HVAC_SECURITY_LOG_TO_DB') && HVAC_SECURITY_LOG_TO_DB) {
            self::log_to_database($event_type, $log_data);
        }
        
        // Trigger action hook for extensibility
        do_action('hvac_security_event_logged', $event_type, $log_data);
    }
    
    /**
     * Log security event to database with error handling
     *
     * @param string $event_type Event type
     * @param array<string, mixed> $log_data Log data
     * @return void
     */
    private static function log_to_database(string $event_type, array $log_data): void
    {
        global $wpdb;
        
        $table = $wpdb->prefix . 'hvac_security_log';
        
        // Check if table exists before attempting insert
        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== $table) {
            return;
        }
        
        $insert_data = [
            'event_type' => $event_type,
            'event_data' => wp_json_encode($log_data['data']),
            'user_id' => $log_data['user_id'],
            'ip_address' => $log_data['ip'],
            'user_agent' => $log_data['user_agent'],
            'request_uri' => $log_data['request_uri'],
            'created_at' => $log_data['timestamp']
        ];
        
        $result = $wpdb->insert($table, $insert_data);
        
        if ($result === false) {
            error_log(sprintf(
                '[HVAC Security] Failed to log to database: %s',
                $wpdb->last_error
            ));
        }
    }
}