<?php
/**
 * GGM Commerce Reviews Class
 * Handles all review operations including CRUD and rating aggregation
 *
 * @package GGM Commerce
 * @subpackage Reviews
 * @version 1.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

class GGM_Commerce_Reviews {

    /**
     * Review statuses
     */
    const STATUS_PENDING = 'pending';
    const STATUS_APPROVED = 'approved';
    const STATUS_SPAM = 'spam';
    const STATUS_TRASH = 'trash';

    /**
     * Initialize review functionality
     */
    public static function init() {
        // Register AJAX handlers for frontend review submission
        add_action('wp_ajax_ggmc_submit_review', [__CLASS__, 'ajax_submit_review']);
        add_action('wp_ajax_nopriv_ggmc_submit_review', [__CLASS__, 'ajax_submit_review']);

        // Register AJAX handler for helpful votes
        add_action('wp_ajax_ggmc_vote_review_helpful', [__CLASS__, 'ajax_vote_helpful']);
        add_action('wp_ajax_nopriv_ggmc_vote_review_helpful', [__CLASS__, 'ajax_vote_helpful']);

        // Register AJAX handler for loading more reviews
        add_action('wp_ajax_ggmc_load_more_reviews', [__CLASS__, 'ajax_load_more_reviews']);
        add_action('wp_ajax_nopriv_ggmc_load_more_reviews', [__CLASS__, 'ajax_load_more_reviews']);
    }

    /**
     * Get a single review by ID
     *
     * @param int $review_id Review ID
     * @return object|null Review object or null
     */
    public static function get_review($review_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_product_reviews WHERE id = %d",
            $review_id
        ));
    }

    /**
     * Get reviews for a product
     *
     * @param int $product_id Product ID
     * @param array $args Query arguments
     * @return array Array of review objects
     */
    public static function get_product_reviews($product_id, $args = []) {
        global $wpdb;

        $defaults = [
            'status' => self::STATUS_APPROVED,
            'orderby' => 'created_at',
            'order' => 'DESC',
            'limit' => 10,
            'offset' => 0,
        ];

        $args = wp_parse_args($args, $defaults);

        $where = "product_id = %d";
        $params = [$product_id];

        if ($args['status'] !== 'all') {
            $where .= " AND status = %s";
            $params[] = $args['status'];
        }

        $orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']);
        if (!$orderby) {
            $orderby = 'created_at DESC';
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built safely above with sanitized values, $orderby is sanitized
        $sql = $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ggmc_product_reviews
             WHERE {$where}
             ORDER BY {$orderby}
             LIMIT %d OFFSET %d",
            array_merge($params, [$args['limit'], $args['offset']])
        );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- $sql is prepared above
        return $wpdb->get_results($sql);
    }

    /**
     * Get total review count for a product
     *
     * @param int $product_id Product ID
     * @param string $status Review status (or 'all')
     * @return int Review count
     */
    public static function get_product_review_count($product_id, $status = 'approved') {
        global $wpdb;

        if ($status === 'all') {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            return (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_reviews WHERE product_id = %d",
                $product_id
            ));
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_reviews WHERE product_id = %d AND status = %s",
            $product_id,
            $status
        ));
    }

    /**
     * Get rating distribution for a product
     *
     * @param int $product_id Product ID
     * @return array Array with keys 1-5 containing count for each rating
     */
    public static function get_rating_distribution($product_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $results = $wpdb->get_results($wpdb->prepare(
            "SELECT rating, COUNT(*) as count
             FROM {$wpdb->prefix}ggmc_product_reviews
             WHERE product_id = %d AND status = %s
             GROUP BY rating",
            $product_id,
            self::STATUS_APPROVED
        ), OBJECT_K);

        $distribution = [];
        for ($i = 1; $i <= 5; $i++) {
            $distribution[$i] = isset($results[$i]) ? (int) $results[$i]->count : 0;
        }

        return $distribution;
    }

    /**
     * Create a new review
     *
     * @param array $data Review data
     * @return int|false Review ID on success, false on failure
     */
    public static function create_review($data) {
        global $wpdb;

        $defaults = [
            'product_id' => 0,
            'customer_id' => null,
            'author_name' => '',
            'author_email' => '',
            'rating' => 5,
            'title' => '',
            'content' => '',
            'status' => self::STATUS_PENDING,
            'verified_purchase' => 0,
            'ip_address' => self::get_client_ip(),
        ];

        $data = wp_parse_args($data, $defaults);

        // Validate required fields
        if (empty($data['product_id']) || empty($data['author_name']) || empty($data['author_email'])) {
            return false;
        }

        // Validate rating
        $data['rating'] = max(1, min(5, (int) $data['rating']));

        // Sanitize data
        $insert_data = [
            'product_id' => absint($data['product_id']),
            'customer_id' => $data['customer_id'] ? absint($data['customer_id']) : null,
            'author_name' => sanitize_text_field($data['author_name']),
            'author_email' => sanitize_email($data['author_email']),
            'rating' => $data['rating'],
            'title' => sanitize_text_field($data['title']),
            'content' => sanitize_textarea_field($data['content']),
            'status' => $data['status'],
            'verified_purchase' => (int) $data['verified_purchase'],
            'ip_address' => $data['ip_address'],
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert(
            "{$wpdb->prefix}ggmc_product_reviews",
            $insert_data,
            ['%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%d', '%s']
        );

        if ($result) {
            $review_id = $wpdb->insert_id;

            // If auto-approve is enabled or review is approved, update product ratings
            if ($data['status'] === self::STATUS_APPROVED) {
                self::recalculate_product_rating($data['product_id']);
            }

            return $review_id;
        }

        return false;
    }

    /**
     * Update a review
     *
     * @param int $review_id Review ID
     * @param array $data Review data to update
     * @return bool Success status
     */
    public static function update_review($review_id, $data) {
        global $wpdb;

        $review = self::get_review($review_id);
        if (!$review) {
            return false;
        }

        $update_data = [];
        $format = [];

        if (isset($data['author_name'])) {
            $update_data['author_name'] = sanitize_text_field($data['author_name']);
            $format[] = '%s';
        }

        if (isset($data['author_email'])) {
            $update_data['author_email'] = sanitize_email($data['author_email']);
            $format[] = '%s';
        }

        if (isset($data['rating'])) {
            $update_data['rating'] = max(1, min(5, (int) $data['rating']));
            $format[] = '%d';
        }

        if (isset($data['title'])) {
            $update_data['title'] = sanitize_text_field($data['title']);
            $format[] = '%s';
        }

        if (isset($data['content'])) {
            $update_data['content'] = sanitize_textarea_field($data['content']);
            $format[] = '%s';
        }

        if (isset($data['status'])) {
            $update_data['status'] = $data['status'];
            $format[] = '%s';
        }

        if (isset($data['verified_purchase'])) {
            $update_data['verified_purchase'] = (int) $data['verified_purchase'];
            $format[] = '%d';
        }

        if (empty($update_data)) {
            return true; // Nothing to update
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update(
            "{$wpdb->prefix}ggmc_product_reviews",
            $update_data,
            ['id' => $review_id],
            $format,
            ['%d']
        );

        // Recalculate product rating if status or rating changed
        if (isset($data['status']) || isset($data['rating'])) {
            self::recalculate_product_rating($review->product_id);
        }

        return $result !== false;
    }

    /**
     * Delete a review
     *
     * @param int $review_id Review ID
     * @return bool Success status
     */
    public static function delete_review($review_id) {
        global $wpdb;

        $review = self::get_review($review_id);
        if (!$review) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->delete(
            "{$wpdb->prefix}ggmc_product_reviews",
            ['id' => $review_id],
            ['%d']
        );

        if ($result) {
            // Recalculate product rating
            self::recalculate_product_rating($review->product_id);
        }

        return $result !== false;
    }

    /**
     * Update review status
     *
     * @param int $review_id Review ID
     * @param string $status New status
     * @return bool Success status
     */
    public static function update_status($review_id, $status) {
        return self::update_review($review_id, ['status' => $status]);
    }

    /**
     * Approve a review
     *
     * @param int $review_id Review ID
     * @return bool Success status
     */
    public static function approve_review($review_id) {
        return self::update_status($review_id, self::STATUS_APPROVED);
    }

    /**
     * Mark review as spam
     *
     * @param int $review_id Review ID
     * @return bool Success status
     */
    public static function spam_review($review_id) {
        return self::update_status($review_id, self::STATUS_SPAM);
    }

    /**
     * Trash a review
     *
     * @param int $review_id Review ID
     * @return bool Success status
     */
    public static function trash_review($review_id) {
        return self::update_status($review_id, self::STATUS_TRASH);
    }

    /**
     * Recalculate and update product rating aggregates
     *
     * @param int $product_id Product ID
     * @return bool Success status
     */
    public static function recalculate_product_rating($product_id) {
        global $wpdb;

        // Calculate average rating and count from approved reviews
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $stats = $wpdb->get_row($wpdb->prepare(
            "SELECT AVG(rating) as average_rating, COUNT(*) as rating_count
             FROM {$wpdb->prefix}ggmc_product_reviews
             WHERE product_id = %d AND status = %s",
            $product_id,
            self::STATUS_APPROVED
        ));

        $average_rating = $stats && $stats->average_rating ? round($stats->average_rating, 2) : 0.00;
        $rating_count = $stats ? (int) $stats->rating_count : 0;

        // Update product table
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update(
            "{$wpdb->prefix}ggmc_products",
            [
                'average_rating' => $average_rating,
                'rating_count' => $rating_count,
            ],
            ['id' => $product_id],
            ['%f', '%d'],
            ['%d']
        );

        return $result !== false;
    }

    /**
     * Check if a customer has already reviewed a product
     *
     * @param int $product_id Product ID
     * @param string $email Customer email
     * @return bool True if already reviewed
     */
    public static function has_reviewed($product_id, $email) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_reviews
             WHERE product_id = %d AND author_email = %s AND status != %s",
            $product_id,
            $email,
            self::STATUS_TRASH
        ));

        return $count > 0;
    }

    /**
     * Check if customer has purchased the product
     *
     * @param int $product_id Product ID
     * @param string $email Customer email
     * @return bool True if purchased
     */
    public static function has_purchased($product_id, $email) {
        global $wpdb;

        // Check orders for this customer containing this product
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_orders o
             INNER JOIN {$wpdb->prefix}ggmc_order_items oi ON o.id = oi.order_id
             WHERE o.customer_email = %s
             AND oi.product_id = %d
             AND o.status IN ('completed', 'processing')",
            $email,
            $product_id
        ));

        return $count > 0;
    }

    /**
     * Increment helpful vote count
     *
     * @param int $review_id Review ID
     * @return bool Success status
     */
    public static function vote_helpful($review_id) {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->query($wpdb->prepare(
            "UPDATE {$wpdb->prefix}ggmc_product_reviews
             SET helpful_votes = helpful_votes + 1
             WHERE id = %d",
            $review_id
        ));

        return $result !== false;
    }

    /**
     * Get all reviews for admin (with pagination and filtering)
     *
     * @param array $args Query arguments
     * @return array Array with 'reviews' and 'total' keys
     */
    public static function get_all_reviews($args = []) {
        global $wpdb;

        $defaults = [
            'status' => 'all',
            'product_id' => 0,
            'search' => '',
            'orderby' => 'created_at',
            'order' => 'DESC',
            'limit' => 20,
            'offset' => 0,
        ];

        $args = wp_parse_args($args, $defaults);

        $where = "1=1";
        $params = [];

        if ($args['status'] !== 'all') {
            $where .= " AND r.status = %s";
            $params[] = $args['status'];
        }

        if ($args['product_id']) {
            $where .= " AND r.product_id = %d";
            $params[] = $args['product_id'];
        }

        if (!empty($args['search'])) {
            $search = '%' . $wpdb->esc_like($args['search']) . '%';
            $where .= " AND (r.author_name LIKE %s OR r.author_email LIKE %s OR r.content LIKE %s)";
            $params[] = $search;
            $params[] = $search;
            $params[] = $search;
        }

        $orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']);
        if (!$orderby) {
            $orderby = 'r.created_at DESC';
        }

        // Get total count
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built safely above with sanitized values
        $count_sql = "SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_reviews r WHERE {$where}";
        if (!empty($params)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $count_sql uses safe interpolation, $params are sanitized
            $count_sql = $wpdb->prepare($count_sql, $params);
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- $count_sql prepared above
        $total = (int) $wpdb->get_var($count_sql);

        // Get reviews with product name
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built safely above, $orderby is sanitized via sanitize_sql_orderby
        $sql = "SELECT r.*, p.name as product_name
                FROM {$wpdb->prefix}ggmc_product_reviews r
                LEFT JOIN {$wpdb->prefix}ggmc_products p ON r.product_id = p.id
                WHERE {$where}
                ORDER BY {$orderby}
                LIMIT %d OFFSET %d";

        $params[] = $args['limit'];
        $params[] = $args['offset'];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- $sql uses safe interpolation, $params are sanitized
        $reviews = $wpdb->get_results($wpdb->prepare($sql, $params));

        return [
            'reviews' => $reviews,
            'total' => $total,
        ];
    }

    /**
     * Get review counts by status
     *
     * @return array Array with status counts
     */
    public static function get_status_counts() {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $results = $wpdb->get_results(
            "SELECT status, COUNT(*) as count
             FROM {$wpdb->prefix}ggmc_product_reviews
             GROUP BY status",
            OBJECT_K
        );

        return [
            'all' => array_sum(array_column((array) $results, 'count')),
            'pending' => isset($results['pending']) ? (int) $results['pending']->count : 0,
            'approved' => isset($results['approved']) ? (int) $results['approved']->count : 0,
            'spam' => isset($results['spam']) ? (int) $results['spam']->count : 0,
            'trash' => isset($results['trash']) ? (int) $results['trash']->count : 0,
        ];
    }

    /**
     * Check if product reviews feature is activated
     *
     * @return bool Whether the feature is activated
     */
    public static function is_reviews_activated() {
        $license = ggm_commerce_license();
        return $license->is_widget_activated('product-reviews');
    }

    /**
     * AJAX handler for submitting a review
     */
    public static function ajax_submit_review() {
        // Check if reviews feature is activated
        if (!self::is_reviews_activated()) {
            wp_send_json_error(['message' => 'Product reviews feature is not activated.']);
        }

        // Verify nonce
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce is verified, not used as data
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'ggmc_submit_review')) {
            wp_send_json_error(['message' => 'Security check failed.']);
        }

        $product_id = absint($_POST['product_id'] ?? 0);
        $rating = absint($_POST['rating'] ?? 0);
        $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
        $content = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : '';
        $author_name = isset($_POST['author_name']) ? sanitize_text_field(wp_unslash($_POST['author_name'])) : '';
        $author_email = isset($_POST['author_email']) ? sanitize_email(wp_unslash($_POST['author_email'])) : '';

        // Validate
        if (!$product_id) {
            wp_send_json_error(['message' => 'Invalid product.']);
        }

        if ($rating < 1 || $rating > 5) {
            wp_send_json_error(['message' => 'Please select a rating between 1 and 5.']);
        }

        if (empty($author_name)) {
            wp_send_json_error(['message' => 'Please enter your name.']);
        }

        if (!is_email($author_email)) {
            wp_send_json_error(['message' => 'Please enter a valid email address.']);
        }

        // Check for duplicate review
        if (self::has_reviewed($product_id, $author_email)) {
            wp_send_json_error(['message' => 'You have already reviewed this product.']);
        }

        // Check if verified purchase
        $verified_purchase = self::has_purchased($product_id, $author_email) ? 1 : 0;

        // Get customer ID if logged in
        $customer_id = null;
        if (is_user_logged_in()) {
            global $wpdb;
            $user = wp_get_current_user();
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $customer_id = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}ggmc_customers WHERE email = %s",
                $user->user_email
            ));
        }

        // Determine auto-approve setting
        $auto_approve = get_option('ggm_commerce_reviews_auto_approve', false);
        $status = $auto_approve ? self::STATUS_APPROVED : self::STATUS_PENDING;

        // Create review
        $review_id = self::create_review([
            'product_id' => $product_id,
            'customer_id' => $customer_id,
            'author_name' => $author_name,
            'author_email' => $author_email,
            'rating' => $rating,
            'title' => $title,
            'content' => $content,
            'status' => $status,
            'verified_purchase' => $verified_purchase,
        ]);

        if ($review_id) {
            $message = $auto_approve
                ? 'Thank you for your review!'
                : 'Thank you for your review! It will be published after moderation.';
            wp_send_json_success(['message' => $message, 'review_id' => $review_id]);
        } else {
            wp_send_json_error(['message' => 'Failed to submit review. Please try again.']);
        }
    }

    /**
     * AJAX handler for voting a review helpful
     */
    public static function ajax_vote_helpful() {
        // Check if reviews feature is activated
        if (!self::is_reviews_activated()) {
            wp_send_json_error(['message' => 'Product reviews feature is not activated.']);
        }

        // Verify nonce
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce is verified, not used as data
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'ggmc_vote_helpful')) {
            wp_send_json_error(['message' => 'Security check failed.']);
        }

        $review_id = absint($_POST['review_id'] ?? 0);

        if (!$review_id) {
            wp_send_json_error(['message' => 'Invalid review.']);
        }

        // Check if user has already voted (using transient with IP)
        $ip = self::get_client_ip();
        $vote_key = 'ggmc_vote_' . $review_id . '_' . md5($ip);

        if (get_transient($vote_key)) {
            wp_send_json_error(['message' => 'You have already voted on this review.']);
        }

        if (self::vote_helpful($review_id)) {
            // Set transient to prevent duplicate votes (30 days)
            set_transient($vote_key, 1, 30 * DAY_IN_SECONDS);

            $review = self::get_review($review_id);
            wp_send_json_success([
                'message' => 'Thank you for your feedback!',
                'helpful_votes' => $review ? $review->helpful_votes : 0,
            ]);
        } else {
            wp_send_json_error(['message' => 'Failed to record vote.']);
        }
    }

    /**
     * AJAX handler for loading more reviews
     */
    public static function ajax_load_more_reviews() {
        // Check if reviews feature is activated
        if (!self::is_reviews_activated()) {
            wp_send_json_error(['message' => 'Product reviews feature is not activated.']);
        }

        // Verify nonce
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce is verified, not used as data
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'ggmc_load_reviews')) {
            wp_send_json_error(['message' => 'Security check failed.']);
        }

        $product_id = absint($_POST['product_id'] ?? 0);
        $offset = absint($_POST['offset'] ?? 0);
        $limit = absint($_POST['limit'] ?? 5);

        if (!$product_id) {
            wp_send_json_error(['message' => 'Invalid product.']);
        }

        // Fetch reviews
        $reviews = self::get_product_reviews($product_id, [
            'limit' => $limit,
            'offset' => $offset,
            'status' => self::STATUS_APPROVED,
        ]);

        // Get total count
        $total = self::get_product_review_count($product_id);
        $has_more = ($offset + count($reviews)) < $total;

        // Render review cards HTML
        ob_start();
        foreach ($reviews as $review) {
            self::render_review_card_html($review);
        }
        $html = ob_get_clean();

        wp_send_json_success([
            'html' => $html,
            'count' => count($reviews),
            'has_more' => $has_more,
        ]);
    }

    /**
     * Render a single review card HTML (for AJAX loading)
     */
    private static function render_review_card_html($review) {
        ?>
        <div class="ggmc-review-card">
            <div class="ggmc-review-header">
                <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- render_stars() returns pre-escaped HTML ?>
                <div class="ggmc-review-stars"><?php echo self::render_stars($review->rating, false); ?></div>
                <?php if ($review->verified_purchase): ?>
                    <span class="ggmc-verified-badge">Verified Purchase</span>
                <?php endif; ?>
            </div>

            <?php if (!empty($review->title)): ?>
                <h4 class="ggmc-review-title"><?php echo esc_html($review->title); ?></h4>
            <?php endif; ?>

            <div class="ggmc-review-content"><?php echo esc_html($review->content); ?></div>

            <div class="ggmc-review-footer">
                <div class="ggmc-reviewer-info">
                    <span class="ggmc-reviewer-name"><?php echo esc_html($review->author_name); ?></span>
                    <span class="ggmc-review-date"><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($review->created_at))); ?></span>
                </div>
                <div class="ggmc-helpful-section">
                    <button class="ggmc-helpful-btn" data-review-id="<?php echo esc_attr($review->id); ?>">
                        <span class="ggmc-helpful-icon">&#128077;</span>
                        <span class="ggmc-helpful-text">Helpful</span>
                        <?php if ($review->helpful_votes > 0): ?>
                            <span class="ggmc-helpful-count">(<?php echo esc_html($review->helpful_votes); ?>)</span>
                        <?php endif; ?>
                    </button>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Get client IP address
     *
     * @return string IP address
     */
    private static function get_client_ip() {
        $ip = '';

        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP']));
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR']));
        } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
            $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
        }

        return $ip;
    }

    /**
     * Format rating as stars HTML
     *
     * @param float $rating Rating value (0-5)
     * @param bool $show_empty Show empty stars
     * @return string HTML stars
     */
    public static function render_stars($rating, $show_empty = true) {
        $rating = max(0, min(5, (float) $rating));
        $full_stars = floor($rating);
        $half_star = ($rating - $full_stars) >= 0.5;
        $empty_stars = 5 - $full_stars - ($half_star ? 1 : 0);

        $html = '<span class="ggmc-stars">';

        // Full stars
        for ($i = 0; $i < $full_stars; $i++) {
            $html .= '<span class="ggmc-star ggmc-star-full">&#9733;</span>';
        }

        // Half star
        if ($half_star) {
            $html .= '<span class="ggmc-star ggmc-star-half">&#9733;</span>';
        }

        // Empty stars
        if ($show_empty) {
            for ($i = 0; $i < $empty_stars; $i++) {
                $html .= '<span class="ggmc-star ggmc-star-empty">&#9734;</span>';
            }
        }

        $html .= '</span>';

        return $html;
    }
}

// Initialize Reviews class
GGM_Commerce_Reviews::init();
