<?php
/**
 * WP User List AJAX Handlers
 * * Handles AJAX requests for the WP User List widget, including filtering, searching, 
 * and pagination with enhanced meta field handling.
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

/**
 * WP User List AJAX Handler Class
 */
class WP_User_List_Ajax {
    /**
     * Default widget settings
     */
    private static $default_settings = [
        'um_elementor_show_user_image' => 1,
        'um_elementor_show_member_name' => 1,
        'um_elementor_show_user_meta' => 1,
        'um_elementor_user_meta_fields' => [],
        'title_alignment' => 'text-center',
        'user_layout' => 1,
    ];

    /**
     * Initialize AJAX handlers
     */
    public static function init() {
        add_action('wp_ajax_wp_filter_users', [__CLASS__, 'handle_filter_users']);
        add_action('wp_ajax_nopriv_wp_filter_users', [__CLASS__, 'handle_filter_users']);
    }

    /**
     * Get widget settings from AJAX payload
     * * @return array
     */
    private static function get_widget_settings() {
        if ( class_exists( 'UM_Elementor_Ajax_Widget_Settings', false ) ) {
            return UM_Elementor_Ajax_Widget_Settings::from_post_wp_list( self::$default_settings );
        }

        return self::$default_settings;
    }

    /**
     * Sanitize meta value based on type
     * * @param mixed $value
     * @param string $type
     * @return mixed
     */
    private static function sanitize_meta_value($value, $type = 'text') {
        switch ($type) {
            case 'email':
                return sanitize_email($value);
            case 'url':
                return esc_url_raw($value);
            case 'number':
                return is_numeric($value) ? $value : 0;
            case 'boolean':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
            case 'array':
                return is_array($value) ? array_map('sanitize_text_field', $value) : [];
            default:
                return sanitize_text_field($value);
        }
    }

    /**
     * @param array<string, mixed> $filters
     * @param string[]             $allowed_keys Sanitized meta keys permitted in the query (from widget settings).
     * @return array
     */
    private static function build_meta_query($filters, array $allowed_keys) {
        $meta_query = [];
        $meta_types = [
            'user_email' => 'email',
            'user_url' => 'url',
            'age' => 'number',
            'active' => 'boolean',
            // Add more meta field types as needed
        ];

        foreach ($filters as $key => $values) {
            if (empty($values)) {
                continue;
            }

            $key = sanitize_key($key);
            if ( $key === '' || ! in_array( $key, $allowed_keys, true ) ) {
                continue;
            }
            $type = $meta_types[$key] ?? 'text';
            $or_conditions = [];

            if (is_array($values)) {
                $sanitized_values = array_map(function($value) use ($type) {
                    return self::sanitize_meta_value($value, $type);
                }, $values);

                $or_conditions[] = [
                    'key' => $key,
                    'value' => $sanitized_values,
                    'compare' => 'IN'
                ];

                // Handle serialized array data (match quoted scalar inside serialized strings).
                foreach ($sanitized_values as $value) {
                    $or_conditions[] = [
                        'key' => $key,
                        'value' => self::serialized_meta_like_value($value),
                        'compare' => 'LIKE'
                    ];
                }
            } else {
                $value = self::sanitize_meta_value($values, $type);
                $or_conditions[] = [
                    'key' => $key,
                    'value' => $value,
                    'compare' => '='
                ];
                $or_conditions[] = [
                    'key' => $key,
                    'value' => self::serialized_meta_like_value($value),
                    'compare' => 'LIKE'
                ];
            }

            $meta_query[] = ['relation' => 'OR'] + $or_conditions;
        }

        return $meta_query;
    }

    /**
     * LIKE pattern matching a value inside PHP-serialized usermeta strings.
     *
     * @param mixed $value
     * @return string
     */
    private static function serialized_meta_like_value($value) {
        global $wpdb;

        $fragment = '"' . (string) $value . '"';

        return '%' . $wpdb->esc_like($fragment) . '%';
    }

    /**
     * Process user meta data for privileged users
     * * @param int $user_id
     * @param array $meta_fields
     * @return array
     */
    private static function process_privileged_user_meta($user_id, $meta_fields) {
        $user_meta = [];
        $meta_types = [
            'user_email' => 'email',
            'user_url' => 'url',
            'age' => 'number',
            'active' => 'boolean',
        ];

        foreach ($meta_fields as $meta_key) {
            $meta_value = get_user_meta($user_id, $meta_key, true);
            
            // Skip empty meta, but allow 0
            if (empty($meta_value) && $meta_value !== 0) {
                continue;
            }

            // Handle different meta value types
            if (is_serialized($meta_value)) {
                $meta_value = maybe_unserialize($meta_value);
            }

            $type = $meta_types[$meta_key] ?? 'text';
            
            if (is_array($meta_value)) {
                $meta_value = implode(', ', array_filter($meta_value, 'is_string'));
            } else {
                $meta_value = self::sanitize_meta_value($meta_value, $type);
            }

            $user_meta[$meta_key] = $meta_value;
        }

        return $user_meta;
    }

    /**
     * Process user meta data for public users (WHITELISTED)
     * * @param int $user_id
     * @param array $requested_meta_fields
     * @return array
     */
    private static function process_public_user_meta($user_id, $requested_meta_fields, array $visibility_map = []) {
        // *** CRITICAL SECURITY ***
        // Define a whitelist of meta fields that are *always* safe to show publicly.
        // NEVER add 'user_email', 'user_login', 'first_name', or 'last_name' here.
        $public_safe_whitelist = [
            'description',
            'user_url',
            'nickname',
            'country',
            'city',
            'gender',
            'birth_date',
            'age',
            // Add any other public-safe meta keys here
        ];

        $safe_meta = [];
        foreach ($requested_meta_fields as $field_key) {
            if ( class_exists( 'UM_Elementor_Field_Visibility' ) && ! empty( $visibility_map ) ) {
                if ( ! UM_Elementor_Field_Visibility::can_view_key( (string) $field_key, $visibility_map, (int) $user_id ) ) {
                    continue;
                }
            }

            // Only add the meta field if it's in the requested list AND our safe whitelist
            if (in_array($field_key, $public_safe_whitelist, true)) {
                $meta_value = get_user_meta($user_id, $field_key, true);
                
                if (is_serialized($meta_value)) {
                   $meta_value = maybe_unserialize($meta_value);
                }
                
                if (is_array($meta_value)) {
                    $meta_value = implode(', ', array_filter($meta_value, 'is_string'));
                }

                if (!empty($meta_value) || $meta_value === 0) {
                     $safe_meta[$field_key] = self::sanitize_meta_value($meta_value, 'text');
                }
            }
        }
        return $safe_meta;
    }

    /**
     * All non-internal usermeta for a user (UM User List privileged AJAX responses).
     *
     * @param int $user_id
     * @return array<string, string>
     */
    private static function collect_privileged_user_meta_all_keys($user_id) {
        $user_meta = [];
        $meta_keys = get_user_meta($user_id);

        if (!is_array($meta_keys)) {
            return $user_meta;
        }

        foreach ($meta_keys as $meta_key => $meta_values) {
            if (substr($meta_key, 0, 1) === '_') {
                continue;
            }
            $meta_value = $meta_values[0] ?? '';
            if (is_serialized($meta_value)) {
                $unserialized = maybe_unserialize($meta_value);
                $meta_value = is_array($unserialized) ? implode(', ', array_filter($unserialized, 'is_string')) : $unserialized;
            }
            if (is_array($meta_value)) {
                $meta_value = implode(', ', array_filter($meta_value, 'is_string'));
            } elseif (!is_string($meta_value)) {
                $meta_value = (string) $meta_value;
            }
            $user_meta[$meta_key] = $meta_value;
        }

        return $user_meta;
    }

    /**
     * Build one user row for JSON list responses.
     *
     * @param \WP_User $user
     * @param bool     $is_privileged
     * @param array    $requested_meta_fields
     * @param bool     $privileged_include_all_user_meta When true, privileged users receive all non-internal meta (UM list); when false, only requested keys (WP list).
     * @return array<string, mixed>
     */
    public static function format_user_array($user, $is_privileged, array $requested_meta_fields, $privileged_include_all_user_meta = false, array $visibility_map = []) {
        $base = [
            'ID' => $user->ID,
            'display_name' => $user->display_name,
            'user_avatar' => get_avatar_url($user->ID, ['size' => 96]),
            'profile_url' => UM_Elementor\Helpers\UM_Elementor_Utils::getUserProfileUrl($user->ID),
        ];

        if ($is_privileged) {
            $user_meta = $privileged_include_all_user_meta
                ? self::collect_privileged_user_meta_all_keys($user->ID)
                : self::process_privileged_user_meta($user->ID, $requested_meta_fields);

            return array_merge($base, [
                'user_login' => $user->user_login,
                'user_email' => $user->user_email,
                'first_name' => $user->first_name,
                'last_name' => $user->last_name,
                'user_registered' => $user->user_registered,
                'user_url' => $user->user_url,
                'user_nicename' => $user->user_nicename,
                'user_meta' => $user_meta,
            ]);
        }

        return array_merge($base, [
            'user_meta' => self::process_public_user_meta($user->ID, $requested_meta_fields, $visibility_map),
        ]);
    }

    /**
     * @param array<int, mixed> $users
     * @return array<int, array<string, mixed>>
     */
    public static function build_users_response_array(array $users, $is_privileged, array $requested_meta_fields, $privileged_include_all_user_meta = false, array $visibility_map = []) {
        $users_array = [];

        foreach ($users as $user_item) {
            $user = is_numeric($user_item) ? get_user_by('ID', $user_item) : $user_item;

            if (!is_object($user) || !isset($user->ID)) {
                continue;
            }

            $users_array[] = self::format_user_array($user, $is_privileged, $requested_meta_fields, $privileged_include_all_user_meta, $visibility_map);
        }

        return $users_array;
    }

    /**
     * Handle AJAX user filtering
     */
    public static function handle_filter_users() {
        // Verify nonce first, before any file I/O or class checks.
        if (!wp_verify_nonce($_POST['nonce'] ?? '', 'wp_filter_nonce')) {
            wp_send_json_error(['message' => 'Security check failed']);
            return;
        }

        // Load autoloader
        $autoloader_path = UM_USER_ELEMENTOR_PATH . 'modules/autoload.php';
        if (!file_exists($autoloader_path)) {
            wp_send_json_error(['message' => 'Autoloader not found']);
            return;
        }
        require_once $autoloader_path;

        // Verify class existence
        if (!class_exists('UM_Elementor\Helpers\UM_Elementor_Utils')) {
            wp_send_json_error(['message' => 'Required class not found']);
            return;
        }

        // *** START SECURITY CHECK ***
        // Check if the current user is privileged.
        $is_privileged_user = current_user_can('list_users');
        // *** END SECURITY CHECK ***

        // Input validation
        $page = max(1, intval($_POST['page'] ?? 1));
        $per_page = max(1, min(100, intval($_POST['per_page'] ?? 12)));
        $search = sanitize_text_field(wp_unslash($_POST['search'] ?? ''));
        $filters = isset($_POST['filters']) && is_array($_POST['filters']) ? wp_unslash($_POST['filters']) : [];

        $widget_id      = sanitize_text_field(wp_unslash($_POST['widget_id'] ?? ''));
        $post_id        = absint(wp_unslash($_POST['post_id'] ?? 0));
        $visibility_map = function_exists('um_ajax_get_visibility_map_from_elementor')
            ? um_ajax_get_visibility_map_from_elementor($post_id, $widget_id)
            : [];

        $widget_settings = self::get_widget_settings();
        $allowed_meta = array_values(
            array_unique(
                array_filter(
                    array_map( 'sanitize_key', (array) ( $widget_settings['um_elementor_user_meta_fields'] ?? [] ) )
                )
            )
        );

        // Build query settings for UM_User_Query_Engine
        $settings = array_merge(
            $widget_settings,
            [
                'users_per_page' => $per_page,
                'paged' => $page,
                'search' => $search,
            ]
        );

        // Add meta query — only keys exposed by the widget configuration
        if (!empty($filters) && !empty($allowed_meta)) {
            $meta_query = self::build_meta_query($filters, $allowed_meta);
            if (!empty($meta_query)) {
                $settings['meta_query'] = ['relation' => 'AND'] + $meta_query;
            }
        }

        try {
            if (um_use_new_query_engine()) {
                $result = \UM_User_Query_Engine::get_users($settings);
                $users = $result['users'];
                $total_users = $result['total'];
            } else {
                $args = \UM_Elementor\Helpers\WP_User_Query_Legacy::build($settings);
                $args['paged'] = $settings['paged'] ?? 1;
                
                if (!empty($settings['meta_query'])) {
                    $args['meta_query'] = $settings['meta_query'];
                }
                
                if (!empty($settings['search'])) {
                    $args['search'] = '*' . $settings['search'] . '*';
                }
                
                $members_query = new \WP_User_Query($args);
                $users_raw = $members_query->get_results();
                $total_users = \UM_Elementor\Helpers\WP_User_Query_Legacy::getTotalUsers($args);
                $users = \UM_Elementor\Helpers\WP_User_Query_Legacy::filterDisplayableUsers($users_raw);
            }
            
            $requested_meta_fields = $widget_settings['um_elementor_user_meta_fields'] ?? [];

            $users_array = self::build_users_response_array($users, $is_privileged_user, $requested_meta_fields, false, $visibility_map);

            // Handle pagination
            $total_pages = ceil($total_users / $per_page);
            if ($page > $total_pages && $total_pages > 0) {
                $settings['paged'] = $total_pages;
                
                if (um_use_new_query_engine()) {
                    $result = \UM_User_Query_Engine::get_users($settings);
                    $users = $result['users'];
                } else {
                    $args = \UM_Elementor\Helpers\WP_User_Query_Legacy::build($settings);
                    $args['paged'] = $total_pages;
                    
                    if (!empty($settings['meta_query'])) {
                        $args['meta_query'] = $settings['meta_query'];
                    }
                    
                    if (!empty($settings['search'])) {
                        $args['search'] = '*' . $settings['search'] . '*';
                    }
                    
                    $members_query = new \WP_User_Query($args);
                    $users_raw = $members_query->get_results();
                    $users = \UM_Elementor\Helpers\WP_User_Query_Legacy::filterDisplayableUsers($users_raw);
                }
                
                $users_array = self::build_users_response_array($users, $is_privileged_user, $requested_meta_fields, false, $visibility_map);
                $page = $total_pages;
            }

            wp_send_json_success([
                'users' => $users_array,
                'total_users' => $total_users,
                'current_page' => $page,
                'total_pages' => $total_pages,
                'per_page' => $per_page,
                'widget_settings' => $widget_settings,
            ]);

        } catch (Exception $e) {
            $debug_info = (defined('WP_DEBUG') && WP_DEBUG) ? [
                'exception_message' => $e->getMessage(),
                'exception_file' => $e->getFile(),
                'exception_line' => $e->getLine(),
                'exception_trace' => $e->getTraceAsString(),
            ] : [];

            wp_send_json_error([
                'message' => __('Error fetching users.', 'ultimate-member-widgets-for-elementor'),
                'debug_info' => $debug_info,
            ]);
        }
    }
}
