<?php

namespace AIPT\Api\Chatbot;

use AIPT\Api\Chatbot\Diag\DiagController;
use AIPT\Api\Chatbot\Exceptions\AsyncUnavailableException;
use AIPT\Api\Chatbot\Jobs\ChatbotJobRepository;
use AIPT\Api\Chatbot\Jobs\EnqueueService;
use AIPT\Api\Constants\ChatbotConstants;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * REST API controller for chatbot endpoints.
 *
 * Handles all chatbot-related REST API requests including
 * chat, settings, and message queue management.
 *
 * @package AIPT\Api\Chatbot
 * @since 2.5.0
 */
class ChatbotController {

    /**
     * API namespace.
     */
    private const NAMESPACE = 'aipt/v1/chatbot';

    /**
     * Minimum message length (characters).
     */
    private const MIN_MESSAGE_LENGTH = 1;

    /**
     * Chatbot service instance.
     *
     * @var ChatbotService
     */
    private $service;

    /**
     * Settings instance.
     *
     * @var ChatbotSettings
     */
    private $settings;

    /**
     * Message queue instance.
     *
     * @var MessageQueue
     */
    private $message_queue;

    /**
     * Rate limiter instance.
     *
     * @var RateLimiter
     */
    private $rate_limiter;

    /**
     * Provider cooldown instance.
     *
     * @var ProviderCooldown
     */
    private $provider_cooldown;

    /**
     * Enqueue service instance.
     *
     * @var EnqueueService
     */
    private EnqueueService $enqueue_service;

    /**
     * Job repository instance.
     *
     * @var ChatbotJobRepository
     */
    private ChatbotJobRepository $job_repository;

    /**
     * Constructor.
     *
     * @param ChatbotService|null       $service           Chatbot service instance.
     * @param ChatbotSettings|null      $settings          Settings instance.
     * @param MessageQueue|null         $message_queue     Message queue instance.
     * @param RateLimiter|null          $rate_limiter      Rate limiter instance.
     * @param ProviderCooldown|null     $provider_cooldown Provider cooldown instance.
     * @param EnqueueService|null       $enqueue_service   Async job enqueue service.
     * @param ChatbotJobRepository|null $job_repository    Async job repository.
     */
    public function __construct(
        ?ChatbotService $service = null,
        ?ChatbotSettings $settings = null,
        ?MessageQueue $message_queue = null,
        ?RateLimiter $rate_limiter = null,
        ?ProviderCooldown $provider_cooldown = null,
        ?EnqueueService $enqueue_service = null,
        ?ChatbotJobRepository $job_repository = null
    ) {
        $this->settings = $settings ?? new ChatbotSettings();
        $this->message_queue = $message_queue ?? new MessageQueue();
        $this->rate_limiter = $rate_limiter ?? new RateLimiter( $this->settings );
        $this->provider_cooldown = $provider_cooldown ?? new ProviderCooldown( $this->settings );
        $this->service = $service ?? new ChatbotService($this->settings, null, null, null, null, $this->message_queue);
        $this->enqueue_service = $enqueue_service ?? new EnqueueService();
        $this->job_repository = $job_repository ?? new ChatbotJobRepository();
    }

    /**
     * Get string length with proper UTF-8 support.
     *
     * Uses mb_strlen when available, falls back to iconv_strlen,
     * and finally to a regex-based byte count as last resort.
     *
     * @param string $string The string to measure.
     * @return int Character count.
     */
    private static function getStringLength( string $string ): int {
        if ( function_exists( 'mb_strlen' ) ) {
            return mb_strlen( $string, 'UTF-8' );
        }

        if ( function_exists( 'iconv_strlen' ) ) {
            $length = iconv_strlen( $string, 'UTF-8' );
            if ( false !== $length ) {
                return $length;
            }
        }

        $count = preg_match_all( '/./us', $string, $matches );
        return false === $count ? 0 : $count;
    }

    /**
     * Register REST API routes.
     *
     * @return void
     */
    public function register_routes(): void {
        ( new DiagController() )->register_routes();

        register_rest_route(self::NAMESPACE, '/chat', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'handle_chat'],
            'permission_callback' => '__return_true',
            'args' => [
                'message' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field',
                    'validate_callback' => function ($value) {
                        $length = self::getStringLength( trim( $value ) );
                        return $length >= self::MIN_MESSAGE_LENGTH && $length <= ChatbotConstants::maxMessageLength();
                    },
                ],
                'session_id' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value) {
                        return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
                    },
                ],
                'client_request_id' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value) {
                        return is_string($value) && '' !== trim($value) && strlen($value) <= 64;
                    },
                ],
                'history' => [
                    'required' => false,
                    'type' => 'array',
                    'default' => [],
                    'description' => __('Conversation history for context.', 'ai-product-tools'),
                    'validate_callback' => function ($value) {
                        if (!is_array($value)) {
                            return false;
                        }
                        if (count($value) > ChatbotConstants::maxHistoryMessages()) {
                            return false;
                        }
                        $max_content_length = ChatbotConstants::maxMessageLength();

                        foreach ($value as $msg) {
                            if (!is_array($msg)) {
                                return false;
                            }
                            if (!isset($msg['role']) || !isset($msg['content'])) {
                                return false;
                            }
                            if (!in_array($msg['role'], ['user', 'assistant'], true)) {
                                return false;
                            }
                            if (!is_string($msg['content'])) {
                                return false;
                            }

                            if (mb_strlen($msg['content'], 'UTF-8') > $max_content_length) {
                                return false;
                            }
                        }
                        return true;
                    },
                    'sanitize_callback' => function ($value) {
                        if (!is_array($value)) {
                            return [];
                        }
                        return array_map(function ($msg) {
                            return [
                                'role' => sanitize_text_field($msg['role'] ?? 'user'),
                                'content' => sanitize_textarea_field($msg['content'] ?? ''),
                            ];
                        }, array_slice($value, 0, ChatbotConstants::maxHistoryMessages()));
                    },
                ],
                '_wpnonce' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'description' => __('Optional nonce for enhanced CSRF protection.', 'ai-product-tools'),
                ],
                'user_name' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value) {
                        if (empty($value)) {
                            return true;
                        }
                        return mb_strlen($value, 'UTF-8') <= ChatbotConstants::maxUserNameLength();
                    },
                    'description' => __('User name from pre-chat form.', 'ai-product-tools'),
                ],
                'user_email' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_email',
                    'validate_callback' => function ($value) {
                        if (empty($value)) {
                            return true;
                        }
                        return (bool) \is_email($value);
                    },
                    'description' => __('User email from pre-chat form.', 'ai-product-tools'),
                ],
                'consent_given' => [
                    'required' => false,
                    'type' => 'boolean',
                    'default' => false,
                    'description' => __('Whether user gave consent for email storage.', 'ai-product-tools'),
                ],
                'consent_timestamp' => [
                    'required' => false,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint',
                    'validate_callback' => function ($value) {

                        if (empty($value)) {
                            return true;
                        }
                        $min_timestamp = strtotime('2020-01-01');
                        $max_timestamp = time() + DAY_IN_SECONDS; 
                        return $value >= $min_timestamp && $value <= $max_timestamp;
                    },
                    'description' => __('Unix timestamp when consent was given.', 'ai-product-tools'),
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/enqueue', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'handle_enqueue'],
            'permission_callback' => '__return_true',
            'args' => [
                'message' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field',
                    'validate_callback' => function ($value) {
                        $length = self::getStringLength( trim( (string) $value ) );
                        return $length >= self::MIN_MESSAGE_LENGTH && $length <= ChatbotConstants::maxMessageLength();
                    },
                ],
                'session_id' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value) {
                        return is_string($value) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
                    },
                ],
                'client_request_id' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',

                    'validate_callback' => function ($value) {
                        return is_string($value)
                            && strlen($value) <= 64
                            && preg_match('/^[A-Za-z0-9_\-]+$/', $value) === 1;
                    },
                ],

                'history' => [
                    'required' => false,
                    'type' => 'array',
                    'default' => [],
                    'validate_callback' => function ( $value ) {
                        if ( ! is_array( $value ) ) {
                            return false;
                        }
                        if ( count( $value ) > ChatbotConstants::maxHistoryMessages() ) {
                            return false;
                        }
                        $cap = ChatbotConstants::maxMessageLength();
                        foreach ( $value as $item ) {
                            if ( ! is_array( $item ) ) {
                                return false;
                            }
                            $content = isset( $item['content'] ) ? (string) $item['content'] : '';
                            if ( self::getStringLength( $content ) > $cap ) {
                                return false;
                            }
                        }
                        return true;
                    },
                ],
                'user_name' => [
                    'required' => false,
                    'type' => 'string',
                    'default' => '',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ( $value ) {
                        if ( ! is_string( $value ) ) {
                            return false;
                        }
                        return self::getStringLength( trim( $value ) ) <= ChatbotConstants::maxUserNameLength();
                    },
                ],
                'user_email' => [
                    'required' => false,
                    'type' => 'string',
                    'default' => '',
                    'sanitize_callback' => 'sanitize_email',
                    'validate_callback' => function ( $value ) {
                        if ( ! is_string( $value ) ) {
                            return false;
                        }
                        if ( '' === trim( $value ) ) {
                            return true;
                        }
                        return (bool) \is_email( $value );
                    },
                ],
                'consent_given' => [
                    'required' => false,
                    'type' => 'boolean',
                    'default' => false,
                ],
                'consent_timestamp' => [
                    'required' => false,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint',

                    'validate_callback' => function ( $value ) {
                        if ( null === $value || '' === $value ) {
                            return true;
                        }
                        if ( ! is_numeric( $value ) ) {
                            return false;
                        }
                        $value         = (int) $value;
                        $min_timestamp = strtotime( '2020-01-01' );
                        $max_timestamp = time() + DAY_IN_SECONDS;
                        return $value >= $min_timestamp && $value <= $max_timestamp;
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/status/(?P<job_uuid>[a-f0-9-]{36})', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_job_status'],
            'permission_callback' => '__return_true',
            'args' => [
                'job_uuid' => [
                    'required' => true,
                    'type' => 'string',
                    'validate_callback' => function ($value) {
                        return is_string($value) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
                    },
                ],
                'status_token' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/settings', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_public_settings'],
            'permission_callback' => '__return_true',
        ]);

        register_rest_route(self::NAMESPACE, '/settings', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'update_settings'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'enabled' => [
                    'type' => 'boolean',
                    'required' => false,
                ],
                'provider' => [
                    'type' => 'string',
                    'required' => false,
                    'enum' => ['openai', 'gemini', 'openrouter', 'claude'],
                ],
                'model' => [
                    'type' => 'string',
                    'required' => false,
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'theme_primary_color' => [
                    'type' => 'string',
                    'required' => false,
                    'sanitize_callback' => 'sanitize_hex_color',
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/settings/full', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_full_settings'],
            'permission_callback' => [$this, 'check_permissions_write'],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/queued', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_queued_messages'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'status' => [
                    'type' => 'string',
                    'default' => 'queued',
                    'enum' => ['queued', 'responded', 'retried', 'failed'],
                ],
                'page' => [
                    'type' => 'integer',
                    'default' => 1,
                    'minimum' => 1,
                ],
                'per_page' => [
                    'type' => 'integer',
                    'default' => ChatbotConstants::defaultPaginationPerPage(),
                    'minimum' => 1,
                    'maximum' => ChatbotConstants::maxPaginationPerPage(),
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)/respond', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'respond_to_message'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
                'response' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field',
                    'validate_callback' => function ($value) {
                        $length = mb_strlen(trim((string) $value), 'UTF-8');
                        return $length >= self::MIN_MESSAGE_LENGTH && $length <= ChatbotConstants::maxResponseLength();
                    },
                ],
                'product_ids' => [
                    'type' => 'array',
                    'items' => ['type' => 'integer'],
                    'default' => [],
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_message'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)', [
            'methods' => WP_REST_Server::DELETABLE,
            'callback' => [$this, 'delete_message'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/cart/add', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'add_to_cart'],
            'permission_callback' => '__return_true',
            'args' => [
                'product_id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
                'quantity' => [
                    'type' => 'integer',
                    'default' => 1,
                    'minimum' => 1,
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/detect-brand-colors', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'detect_brand_colors'],
            'permission_callback' => [$this, 'check_permissions_write'],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/stats', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_queue_stats'],
            'permission_callback' => [$this, 'check_permissions_write'],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<session_id>[a-f0-9-]{36})/poll', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'poll_responses'],
            'permission_callback' => '__return_true',
            'args' => [
                'session_id' => [
                    'required' => true,
                    'type' => 'string',
                    'validate_callback' => function ($value) {
                        return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)/delivered', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'mark_delivered'],
            'permission_callback' => '__return_true',
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
                'session_id' => [
                    'required' => true,
                    'type' => 'string',
                    'validate_callback' => function ($value) {
                        return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/bulk-delete', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'bulk_delete_messages'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'ids' => [
                    'required' => true,
                    'type' => 'array',
                    'items' => ['type' => 'integer'],
                    'validate_callback' => function ($value) {
                        return is_array($value) && !empty($value) && count($value) <= ChatbotConstants::maxBulkDeleteItems();
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/search', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'search_messages'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'search' => [
                    'type' => 'string',
                    'default' => '',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'status' => [
                    'type' => 'string',
                    'default' => '',
                    'enum' => ['', 'queued', 'responded', 'retried', 'failed', 'delivered', 'info'],
                ],
                'record_type' => [
                    'type' => 'string',
                    'default' => '',
                    'enum' => ['', 'first_contact', 'error', 'conversation'],
                ],
                'page' => [
                    'type' => 'integer',
                    'default' => 1,
                    'minimum' => 1,
                ],
                'per_page' => [
                    'type' => 'integer',
                    'default' => ChatbotConstants::defaultPaginationPerPage(),
                    'minimum' => 1,
                    'maximum' => ChatbotConstants::maxPaginationPerPage(),
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)/contact', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_message_contact'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'validate_callback' => function ($value) {
                        return is_numeric($value) && $value > 0;
                    },
                ],
            ],
        ]);
    }

    /**
     * Permission callback for write (admin) endpoints.
     *
     * Enforces REST nonce verification and manage_woocommerce capability.
     * Used as standard check_permissions_write per project convention.
     *
     * @param WP_REST_Request $request Request instance.
     * @return bool|WP_Error True when allowed, WP_Error when forbidden.
     */
    public function check_permissions_write(WP_REST_Request $request) {
        $nonce = $request->get_header('X-WP-Nonce');
        if (empty($nonce)) {
            $nonce = $request->get_param('_wpnonce');
        }

        $nonce = is_string($nonce) ? sanitize_text_field(wp_unslash($nonce)) : '';

        if (empty($nonce) || !wp_verify_nonce($nonce, 'wp_rest')) {
            return new WP_Error(
                'rest_forbidden_nonce',
                __('Invalid security token. Please refresh and try again.', 'ai-product-tools'),
                ['status' => 403]
            );
        }

        if (!current_user_can('manage_woocommerce')) {
            return new WP_Error(
                'rest_forbidden',
                __('You do not have permission to manage chatbot settings.', 'ai-product-tools'),
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Handle chat request.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function handle_chat(WP_REST_Request $request): WP_REST_Response {

        $ai_gate = \AIPT\Core\Services\AiSupportGate::guard();
        if ( $ai_gate instanceof \WP_Error ) {
            return new \WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => $ai_gate->get_error_code(),
                    'message' => $ai_gate->get_error_message(),
                ),
                503
            );
        }

        $nonce = $request->get_param('_wpnonce');
        if (!empty($nonce) && !wp_verify_nonce($nonce, 'wp_rest')) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'invalid_nonce',
                'message' => __('Security verification failed. Please refresh and try again.', 'ai-product-tools'),
            ], 403);
        }

        if ( apply_filters( ChatbotConstants::ASYNC_ENABLED_FILTER, false ) ) {
            $client_request_id = $request->get_param('client_request_id');

            if (
                ! is_string( $client_request_id )
                || '' === trim( $client_request_id )
                || strlen( $client_request_id ) > 64
                || preg_match( '/^[A-Za-z0-9_\-]+$/', $client_request_id ) !== 1
            ) {
                return new WP_REST_Response([
                    'success' => false,
                    'code' => 'aipt_chatbot_client_request_id_required',
                    'message' => __('Client request ID is required when async chat is enabled.', 'ai-product-tools'),
                ], 400);
            }

            return $this->handle_enqueue( $request );
        }

        $message = $request->get_param('message');
        $session_id = $request->get_param('session_id');
        $history = $request->get_param('history') ?? [];
        $user_name = $request->get_param('user_name') ?? '';
        $user_email = $request->get_param('user_email') ?? '';
        $consent_given = (bool) $request->get_param('consent_given');
        $consent_timestamp = $request->get_param('consent_timestamp') ? (int) $request->get_param('consent_timestamp') : null;

        $rate_limit_check = $this->rate_limiter->check( $session_id );
        if ( ! $rate_limit_check['allowed'] ) {
			return $this->createRateLimitFailureResponse( $rate_limit_check );
        }

        $client_ip = $this->rate_limiter->getClientIp();
        $ip_rate_limit_check = $this->rate_limiter->checkByIp( $client_ip );
        if ( ! $ip_rate_limit_check['allowed'] ) {
			return $this->createRateLimitFailureResponse( $ip_rate_limit_check );
        }

        $message = $request->get_param('message');
        if (is_string($message) && self::getStringLength($message) > ChatbotConstants::maxMessageLength()) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'message_too_long',
                'message' => sprintf(
                    /* translators: %d: maximum allowed message length in characters */
                    __('Message exceeds maximum length of %d characters.', 'ai-product-tools'),
                    ChatbotConstants::maxMessageLength()
                ),
            ], 400);
        }

        $cooldown_check = $this->provider_cooldown->isActive();
        if ( $cooldown_check['active'] ) {
            $response = new WP_REST_Response([
                'success' => false,
                'code' => 'provider_cooldown',
                'message' => __('The AI service is temporarily unavailable. Please try again shortly.', 'ai-product-tools'),
                'data' => [
                    'retry_after' => $cooldown_check['retry_after'],
                ],
            ], 503);
            $response->header( 'Retry-After', $cooldown_check['retry_after'] );
            return $response;
        }

        if (!$this->settings->isEnabled()) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'chatbot_disabled',
                'message' => __('The chatbot is currently disabled.', 'ai-product-tools'),
            ], 503);
        }

        try {
            $result = $this->service->processMessage($message, $session_id, $history, $user_name, $user_email, $consent_given, $consent_timestamp);

            if ($result['success']) {
                return new WP_REST_Response($result, 200);
            }

            $cooldown_errors = [ 'rate_limit', 'service_unavailable' ];
            if ( isset( $result['error'] ) && in_array( $result['error'], $cooldown_errors, true ) ) {

                $this->provider_cooldown->activate();

                if ( $this->provider_cooldown->shouldEnqueueOnFailure() ) {
                    $this->message_queue->enqueue( $message, $session_id, '', $user_name, $user_email, $consent_given, $consent_timestamp );

                    return new WP_REST_Response([
                        'success' => true,
                        'code'    => 'message_queued',
                        'message' => __( 'All support agents are busy. Your message has been queued and will be processed shortly.', 'ai-product-tools' ),
                        'data'    => [ 'queued' => true ],
                    ], 202);
                }
            }

            $status_code = 500;
            if (isset($result['error'])) {
                switch ($result['error']) {
                    case 'chatbot_unavailable':
                    case 'service_unavailable':
                    case 'rate_limit':
                        $status_code = 503;
                        break;
                    case 'invalid_session':
                    case 'message_too_long':
                        $status_code = 400;
                        break;
                }
            }

            return new WP_REST_Response($result, $status_code);

        } catch (\InvalidArgumentException $e) {

            return new WP_REST_Response([
                'success' => false,
                'code' => 'invalid_request',
                'message' => $this->sanitizeErrorMessage($e->getMessage()),
            ], 400);
        } catch (\Exception $e) {

            return new WP_REST_Response([
                'success' => false,
                'code' => 'ai_error',
                'message' => __('An error occurred processing your request.', 'ai-product-tools'),
            ], 500);
        }
    }

    public function handle_enqueue(WP_REST_Request $request): WP_REST_Response {

        if ( ! apply_filters( ChatbotConstants::ASYNC_ENABLED_FILTER, false ) ) {
            return new WP_REST_Response(
                [
                    'success' => false,
                    'code'    => 'aipt_chatbot_async_disabled',
                    'message' => __( 'Async chat is not enabled on this site.', 'ai-product-tools' ),
                ],
                404
            );
        }

        $ai_gate = \AIPT\Core\Services\AiSupportGate::guard();
        if ( $ai_gate instanceof \WP_Error ) {
            return new \WP_REST_Response(
                array(
                    'success' => false,
                    'code'    => $ai_gate->get_error_code(),
                    'message' => $ai_gate->get_error_message(),
                ),
                503
            );
        }

        $message           = $request->get_param('message');
        $session_id        = $request->get_param('session_id');
        $client_request_id = $request->get_param('client_request_id');
        $validation        = $this->validateAsyncChatRequest( $message, $session_id, $client_request_id );

        if ( $validation instanceof WP_REST_Response ) {
            return $validation;
        }

        $session_id        = sanitize_text_field( (string) $session_id );
        $client_request_id = sanitize_text_field( (string) $client_request_id );

        $rate_limit_response = $this->checkAsyncRateLimits( $session_id );
        if ( $rate_limit_response instanceof WP_REST_Response ) {
            return $rate_limit_response;
        }

        if (!$this->settings->isEnabled()) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'chatbot_disabled',
                'message' => __('The chatbot is currently disabled.', 'ai-product-tools'),
            ], 503);
        }

        $payload = $this->buildAsyncPayload( $request, $session_id, $client_request_id );

        try {
            $enqueue = $this->enqueue_service->enqueue( $session_id, $client_request_id, $payload );
        } catch ( AsyncUnavailableException $exception ) {
            return $this->createAsyncUnavailableResponse( $exception );
        } catch ( \Throwable $throwable ) {
            ChatbotLogger::error(
                'enqueue_failed',
                [
                    'session_id' => $session_id,
                    'request_id' => $client_request_id,
                    'error' => $throwable->getMessage(),
                ]
            );

            return new WP_REST_Response([
                'success' => false,
                'code' => 'ai_error',
                'message' => __('An error occurred processing your request.', 'ai-product-tools'),
            ], 500);
        }

        $response = new WP_REST_Response([
            'success' => true,
            'data' => [
                'job_uuid' => $enqueue['job_uuid'],
                'status_token' => $enqueue['status_token'],
                'poll_url' => $enqueue['poll_url'],
                'estimated_wait_ms' => ChatbotConstants::WORKER_TIMEOUT_SECONDS * 1000,
            ],
        ], 202);
        $response->header( 'Retry-After', 1 );

        return $response;
    }

    public function get_job_status(WP_REST_Request $request): WP_REST_Response {
        $job_uuid = $request->get_param('job_uuid');
        $token    = $this->getStatusTokenFromRequest( $request );

		$client_ip          = $this->rate_limiter->getClientIp();
		$ip_rate_limit_check = $this->rate_limiter->checkPollByIp( $client_ip );
        if ( ! ( $ip_rate_limit_check['allowed'] ?? false ) ) {
            return $this->createRateLimitFailureResponse( $ip_rate_limit_check );
        }

        if ( ! is_string( $job_uuid ) || ! $this->isUuid( $job_uuid ) || '' === $token ) {
            return $this->createStatusTokenInvalidResponse();
        }

        $row = $this->job_repository->findByUuidWithToken( $job_uuid, $token );
        if ( null === $row ) {
            return $this->createStatusTokenInvalidResponse();
        }

        $session_id = isset( $row['session_id'] ) ? (string) $row['session_id'] : '';
        if ( '' !== $session_id ) {
            $poll_rate_limit_check = $this->rate_limiter->checkPoll( $session_id );
            if ( ! ( $poll_rate_limit_check['allowed'] ?? false ) ) {
                return $this->createRateLimitFailureResponse( $poll_rate_limit_check );
            }
        }

        $status = (string) ( $row['status'] ?? 'pending' );

        if ( 'completed' === $status ) {
            $response_payload = $this->decodeJobJson( $row['response'] ?? null );
            $data             = [ 'response' => $response_payload ];

            if ( isset( $response_payload['data']['products'] ) && is_array( $response_payload['data']['products'] ) ) {
                $data['products'] = $response_payload['data']['products'];
            }
            if ( isset( $response_payload['data']['actions'] ) && is_array( $response_payload['data']['actions'] ) ) {
                $data['actions'] = $response_payload['data']['actions'];
            }

            return new WP_REST_Response([
                'status' => 'completed',
                'data' => $data,
            ], 200);
        }

        if ( 'failed' === $status ) {
            $error_message = (string) ( $row['error_detail'] ?? '' );
            if ( '' === $error_message ) {
                $error_message = __( 'An error occurred. Please try again.', 'ai-product-tools' );
            }

            return new WP_REST_Response([
                'status' => 'failed',
                'data' => [
                    'error_code' => sanitize_key( (string) ( $row['error_code'] ?? 'worker_unhandled' ) ),
                    'error_message' => $this->sanitizeErrorMessage( $error_message ),
                ],
            ], 200);
        }

        if ( 'requires_user_retry' === $status ) {
            $next_attempt_at = isset( $row['next_attempt_at'] ) ? (string) $row['next_attempt_at'] : '';

            return new WP_REST_Response([
                'status' => 'requires_user_retry',
                'data' => [
                    'error_code' => sanitize_key( (string) ( $row['error_code'] ?? 'provider_cooldown' ) ),
                    'retry_after_seconds' => $this->getRetryAfterSeconds( $next_attempt_at ),
                    'next_attempt_at' => $next_attempt_at,
                ],
            ], 200);
        }

        $public_status = 'processing' === $status ? 'processing' : 'queued';
        $data          = [
            'attempts' => isset( $row['attempts'] ) ? (int) $row['attempts'] : 0,
            'eta_ms' => ChatbotConstants::WORKER_TIMEOUT_SECONDS * 1000,
        ];

        if ( ! empty( $row['next_attempt_at'] ) ) {
            $data['next_attempt_at'] = (string) $row['next_attempt_at'];
        }

        return new WP_REST_Response([
            'status' => $public_status,
            'data' => $data,
        ], 200);
    }

    private function validateAsyncChatRequest( $message, $session_id, $client_request_id ): ?WP_REST_Response {
        if ( ! is_string( $session_id ) || ! $this->isUuid( $session_id ) ) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'invalid_session',
                'message' => __('Invalid session ID format.', 'ai-product-tools'),
            ], 400);
        }

        if (
            ! is_string( $client_request_id )
            || '' === trim( $client_request_id )
            || strlen( $client_request_id ) > 64
            || preg_match( '/^[A-Za-z0-9_\-]+$/', $client_request_id ) !== 1
        ) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'aipt_chatbot_client_request_id_required',
                'message' => __('Client request ID is required when async chat is enabled.', 'ai-product-tools'),
            ], 400);
        }

        if ( ! is_string( $message ) || self::getStringLength( trim( $message ) ) < self::MIN_MESSAGE_LENGTH ) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'invalid_request',
                'message' => __('Message cannot be empty.', 'ai-product-tools'),
            ], 400);
        }

        if ( self::getStringLength( $message ) > ChatbotConstants::maxMessageLength() ) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'message_too_long',
                'message' => sprintf(
                    /* translators: %d: maximum allowed message length in characters */
                    __('Message exceeds maximum length of %d characters.', 'ai-product-tools'),
                    ChatbotConstants::maxMessageLength()
                ),
            ], 400);
        }

        return null;
    }

    private function checkAsyncRateLimits( string $session_id ): ?WP_REST_Response {
        $rate_limit_check = $this->rate_limiter->check( $session_id );
        if ( ! $rate_limit_check['allowed'] ) {
            return $this->createRateLimitFailureResponse( $rate_limit_check );
        }

        $client_ip = $this->rate_limiter->getClientIp();
        $ip_rate_limit_check = $this->rate_limiter->checkByIp( $client_ip );
        if ( ! $ip_rate_limit_check['allowed'] ) {
            return $this->createRateLimitFailureResponse( $ip_rate_limit_check );
        }

        return null;
    }

    private function buildAsyncPayload( WP_REST_Request $request, string $session_id, string $client_request_id ): array {
        return [
            'session_id' => $session_id,
            'client_request_id' => $client_request_id,
            'message' => sanitize_textarea_field( (string) $request->get_param('message') ),
            'history' => $this->sanitizeHistory( $request->get_param('history') ),
            'user_name' => sanitize_text_field( (string) ( $request->get_param('user_name') ?? '' ) ),
            'user_email' => sanitize_email( (string) ( $request->get_param('user_email') ?? '' ) ),
            'consent_given' => (bool) $request->get_param('consent_given'),
            'consent_timestamp' => $request->get_param('consent_timestamp') ? absint( $request->get_param('consent_timestamp') ) : null,
        ];
    }

    private function sanitizeHistory( $history ): array {
        if ( ! is_array( $history ) ) {
            return [];
        }

        $sanitized = [];
        foreach ( array_slice( $history, 0, ChatbotConstants::maxHistoryMessages() ) as $message ) {
            if ( ! is_array( $message ) ) {
                continue;
            }

            $role = sanitize_text_field( (string) ( $message['role'] ?? 'user' ) );
            if ( ! in_array( $role, [ 'user', 'assistant' ], true ) ) {
                $role = 'user';
            }

            $sanitized[] = [
                'role' => $role,
                'content' => sanitize_textarea_field( (string) ( $message['content'] ?? '' ) ),
            ];
        }

        return $sanitized;
    }

    private function getStatusTokenFromRequest( WP_REST_Request $request ): string {
        $authorization = $request->get_header( 'Authorization' );
        if ( is_string( $authorization ) && preg_match( '/^Bearer\s+(.+)$/i', $authorization, $matches ) ) {
            return sanitize_text_field( $matches[1] );
        }

        $query_token = $request->get_param( 'status_token' );

        return is_string( $query_token ) ? sanitize_text_field( $query_token ) : '';
    }

    private function createStatusTokenInvalidResponse(): WP_REST_Response {
        return new WP_REST_Response([
            'success' => false,
            'code' => 'aipt_chatbot_status_token_invalid',
            'message' => __('Invalid status token.', 'ai-product-tools'),
        ], 403);
    }

    private function createAsyncUnavailableResponse( AsyncUnavailableException $exception ): WP_REST_Response {
        return new WP_REST_Response([
            'success' => false,
            'code' => $exception->getErrorCode(),
            'message' => $exception->getUserMessage(),
        ], 503);
    }

    private function decodeJobJson( $value ): array {
        $decoded = is_string( $value ) ? json_decode( $value, true ) : null;

        return is_array( $decoded ) ? $decoded : [];
    }

    private function getRetryAfterSeconds( string $next_attempt_at ): int {
        $timestamp = '' !== $next_attempt_at ? strtotime( $next_attempt_at ) : false;
        if ( false === $timestamp ) {
            return 0;
        }

        return max( 0, $timestamp - time() );
    }

    private function isUuid( string $value ): bool {
        return (bool) preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value );
    }

	private function createRateLimitFailureResponse( array $rate_limit_check ): WP_REST_Response {
		$is_degraded = ! empty( $rate_limit_check['degraded'] );
		$retry_after = isset( $rate_limit_check['retry_after'] ) ? (int) $rate_limit_check['retry_after'] : 60;
		$error_code  = $is_degraded ? 'rate_limit_store_unavailable' : 'rate_limited';
		$status_code = $is_degraded ? 503 : 429;
		$message     = $is_degraded
			? __( 'The chat service is temporarily unavailable. Please try again shortly.', 'ai-product-tools' )
			: __( 'Too many requests. Please wait a moment before trying again.', 'ai-product-tools' );

		if ( isset( $rate_limit_check['error_code'] ) && is_string( $rate_limit_check['error_code'] ) && '' !== $rate_limit_check['error_code'] ) {
			$error_code = sanitize_key( $rate_limit_check['error_code'] );
		}

		$response_data = [
			'success' => false,
			'code'    => $error_code,
			'message' => $message,
			'data'    => [
				'retry_after' => $retry_after,
			],
		];

		if ( $is_degraded ) {
			$response_data['data']['degraded'] = true;
		}

		$response = new WP_REST_Response( $response_data, $status_code );
		$response->header( 'Retry-After', $retry_after );

		if ( $is_degraded ) {
			$response->header( 'X-AIPT-RateLimit-Degraded', '1' );
		}

		return $response;
	}

    /**
     * Sanitize error message for client response.
     *
     * Removes sensitive information like file paths, database details,
     * and API keys from error messages before returning to client.
     *
     * @param string $message Original error message.
     * @return string Sanitized message.
     */
    private function sanitizeErrorMessage(string $message): string {

        $allowed_messages = [
            __('Message cannot be empty.', 'ai-product-tools'),
            __('Message is too long (max 2000 characters).', 'ai-product-tools'),
            __('Invalid session ID format.', 'ai-product-tools'),
            __('The chatbot is currently unavailable.', 'ai-product-tools'),
        ];

        foreach ($allowed_messages as $allowed) {
            if (stripos($message, $allowed) !== false) {
                return $message;
            }
        }

        $sensitive_patterns = [
            '/\/[a-zA-Z0-9_\-\/]+\.php/i',  
            '/line \d+/i',                   
            '/API key/i',                    
            '/password/i',                   
            '/SELECT|INSERT|UPDATE|DELETE/i', 
            '/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', 
            '/sk-[a-zA-Z0-9]+/',             
        ];

        foreach ($sensitive_patterns as $pattern) {
            if (preg_match($pattern, $message)) {
                return __('An error occurred. Please try again.', 'ai-product-tools');
            }
        }

        return wp_strip_all_tags($message);
    }

    /**
     * Get public settings.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_public_settings(WP_REST_Request $request): WP_REST_Response {
        return new WP_REST_Response([
            'success' => true,
            'data' => $this->settings->getPublicSettings(),
        ], 200);
    }

    /**
     * Get full settings (admin).
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_full_settings(WP_REST_Request $request): WP_REST_Response {

        $pages = (array) \get_pages(['post_status' => 'publish']);
        $page_options = array_map(function ($page) {
            return [
                'id' => $page->ID,
                'title' => $page->post_title,
                'type' => 'page',
            ];
        }, $pages);

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'settings' => $this->settings->getAll(),
                'pages' => $page_options,
            ],
        ], 200);
    }

    /**
     * Update settings.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error Response object or persistence drift error.
     */
    public function update_settings(WP_REST_Request $request) {
        $body = $request->get_json_params();

        if (empty($body)) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'empty_body',
                'message' => __('Request body is empty.', 'ai-product-tools'),
            ], 400);
        }

        try {
            $result = $this->settings->update($body);

            if (is_wp_error($result)) {
                return $result;
            }

            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'message' => __('Settings updated successfully.', 'ai-product-tools'),
                    'settings' => $this->settings->getAll(),
                ],
            ], 200);

        } catch (\InvalidArgumentException $e) {
            $raw_message = $e->getMessage();
            $decoded = json_decode( $raw_message, true );

            if ( is_array( $decoded ) ) {
                $errors = $decoded;
            } else {

                $errors = [
                    'general' => [ $raw_message ?: __( 'An unknown validation error occurred.', 'ai-product-tools' ) ],
                ];
            }

            return new WP_REST_Response([
                'success' => false,
                'code' => 'validation_error',
                'message' => __('Validation failed.', 'ai-product-tools'),
                'data' => [
                    'errors' => $errors,
                ],
            ], 400);
        }
    }

    /**
     * Get queued messages.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_queued_messages(WP_REST_Request $request): WP_REST_Response {
        $status = $request->get_param('status');
        $page = $request->get_param('page');
        $per_page = $request->get_param('per_page');

        try {
            $result = $this->message_queue->getQueued($status, $page, $per_page);
        } catch (\Throwable $e) {
            return new WP_REST_Response([
                'success' => false,
                'code'    => 'message_queue_unavailable',
                'message' => __('Could not load queued messages right now. Please try again.', 'ai-product-tools'),
            ], 500);
        }

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'messages' => $result['messages'],
                'pagination' => [
                    'total' => $result['total'],
                    'pages' => $result['pages'],
                    'current' => $page,
                    'per_page' => $per_page,
                ],
            ],
        ], 200);
    }

    /**
     * Respond to queued message.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function respond_to_message(WP_REST_Request $request): WP_REST_Response {
        $id = (int) $request->get_param('id');
        $response = $request->get_param('response');

		$can_reply_default = \AIPT\Utils\PremiumFeatureManager::getInstance()->canUsePremiumFeatures();
		if ( ! apply_filters( 'aipt_chatbot_can_reply_to_queue', $can_reply_default ) ) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'premium_required',
                'message' => __('Replying to chatbot messages requires a premium license.', 'ai-product-tools'),
            ], 403);
        }

        $message = $this->message_queue->get($id);
        if (!$message) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'not_found',
                'message' => __('Message not found.', 'ai-product-tools'),
            ], 404);
        }

        $result = $this->message_queue->markResponded($id, $response);

        if ($result) {
            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'message' => __('Response saved successfully.', 'ai-product-tools'),
                    'queued_message' => $this->message_queue->get($id),
                ],
            ], 200);
        }

        return new WP_REST_Response([
            'success' => false,
            'code' => 'update_failed',
            'message' => __('Failed to save response.', 'ai-product-tools'),
        ], 500);
    }

    /**
     * Get a single queued message by ID.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_message(WP_REST_Request $request): WP_REST_Response {
        $id = (int) $request->get_param('id');

        $message = $this->message_queue->get($id);
        if (!$message) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'not_found',
                'message' => __('Message not found.', 'ai-product-tools'),
            ], 404);
        }

        return new WP_REST_Response([
            'success' => true,
            'data' => $message,
        ], 200);
    }

    /**
     * Delete queued message.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function delete_message(WP_REST_Request $request): WP_REST_Response {
        $id = (int) $request->get_param('id');

        $message = $this->message_queue->get($id);
        if (!$message) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'not_found',
                'message' => __('Message not found.', 'ai-product-tools'),
            ], 404);
        }

        $result = $this->message_queue->delete($id);

        if ($result) {
            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'message' => __('Message deleted successfully.', 'ai-product-tools'),
                ],
            ], 200);
        }

        return new WP_REST_Response([
            'success' => false,
            'code' => 'delete_failed',
            'message' => __('Failed to delete message.', 'ai-product-tools'),
        ], 500);
    }

    /**
     * Add product to cart.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function add_to_cart(WP_REST_Request $request): WP_REST_Response {
        $product_id = (int) $request->get_param('product_id');
        $quantity = (int) $request->get_param('quantity');

        $action = new Actions\AddToCartAction();
        $result = $action->execute($product_id, ['quantity' => $quantity]);

        $status_code = $result['success'] ? 200 : 400;
        return new WP_REST_Response([
            'success' => $result['success'],
            'data' => $result['data'] ?? [],
            'message' => $result['message'] ?? '',
        ], $status_code);
    }

    /**
     * Detect brand colors.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function detect_brand_colors(WP_REST_Request $request): WP_REST_Response {
        $colors = $this->settings->detectBrandColors();

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'colors' => $colors,
            ],
        ], 200);
    }

    /**
     * Get queue stats.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_queue_stats(WP_REST_Request $request): WP_REST_Response {
        $stats = $this->message_queue->getStats();

        return new WP_REST_Response([
            'success' => true,
            'data' => $stats,
        ], 200);
    }

    /**
     * Poll for admin responses (public endpoint for widget).
     *
     * Returns admin responses that are ready to be delivered to the customer.
     * Uses session_id to ensure customers can only see their own responses.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function poll_responses(WP_REST_Request $request): WP_REST_Response {

        $session_id = $request->get_param('session_id');

        $rate_limit_check = $this->rate_limiter->checkPoll( $session_id );
        if ( ! $rate_limit_check['allowed'] ) {
            $response = new WP_REST_Response([
                'success' => false,
                'code' => 'rate_limited',
                'message' => __('Too many requests. Please wait a moment.', 'ai-product-tools'),
                'data' => [
                    'retry_after' => $rate_limit_check['retry_after'],
                ],
            ], 429);
            $response->header( 'Retry-After', $rate_limit_check['retry_after'] );
            return $response;
        }

        $responses = $this->message_queue->getPendingResponses($session_id, 5);

        if (empty($responses)) {
            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'has_response' => false,
                    'responses' => [],
                ],
            ], 200);
        }

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'has_response' => true,
                'responses' => $responses,
            ],
        ], 200);
    }

    /**
     * Mark message as delivered to customer (public endpoint for widget).
     *
     * Verifies that the session_id matches the message to prevent
     * unauthorized marking of other users' messages as delivered.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function mark_delivered(WP_REST_Request $request): WP_REST_Response {

        $id = (int) $request->get_param('id');
        $session_id = $request->get_param('session_id');

        $message = $this->message_queue->get($id);

        if (!$message) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'not_found',
                'message' => __('Message not found.', 'ai-product-tools'),
            ], 404);
        }

        if ($message['session_id'] !== $session_id) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'forbidden',
                'message' => __('Access denied.', 'ai-product-tools'),
            ], 403);
        }

        $result = $this->message_queue->markDelivered($id);

        if ($result) {
            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'message' => __('Message marked as delivered.', 'ai-product-tools'),
                ],
            ], 200);
        }

        return new WP_REST_Response([
            'success' => false,
            'code' => 'update_failed',
            'message' => __('Failed to update message.', 'ai-product-tools'),
        ], 500);
    }

    /**
     * Bulk delete messages (admin endpoint).
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function bulk_delete_messages(WP_REST_Request $request): WP_REST_Response {
        $ids = $request->get_param('ids');

        if (empty($ids)) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'empty_ids',
                'message' => __('No messages selected.', 'ai-product-tools'),
            ], 400);
        }

        $deleted_count = $this->message_queue->bulkDelete($ids);

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'deleted' => $deleted_count,
                'message' => sprintf($this->getBulkDeleteMessageTemplate($deleted_count), $deleted_count),
            ],
        ], 200);
    }

    /**
     * Search messages (admin endpoint).
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function search_messages(WP_REST_Request $request): WP_REST_Response {
        $args = [
            'search' => $request->get_param('search'),
            'status' => $request->get_param('status'),
            'record_type' => $request->get_param('record_type'),
            'page' => $request->get_param('page'),
            'per_page' => $request->get_param('per_page'),
        ];

        $result = $this->message_queue->getAllMessages($args);

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'messages' => $result['messages'],
                'pagination' => [
                    'total' => $result['total'],
                    'pages' => $result['pages'],
                    'current' => (int) $args['page'],
                    'per_page' => (int) $args['per_page'],
                ],
            ],
        ], 200);
    }

    /**
     * Get decrypted contact information for a message (admin endpoint).
     *
     * Returns the decrypted email address for display in the admin modal.
     * This is a separate endpoint to ensure emails are only decrypted when
     * explicitly requested, not in list views (PII security best practice).
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response Response object.
     */
    public function get_message_contact(WP_REST_Request $request): WP_REST_Response {
        $id = (int) $request->get_param('id');

        $message = $this->message_queue->get($id);
        if (!$message) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'not_found',
                'message' => __('Message not found.', 'ai-product-tools'),
            ], 404);
        }

        $decrypted_email = null;
        if (!empty($message['user_email'])) {
            $decrypted_email = $this->message_queue->decryptEmail($message['user_email']);
        }

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'user_name' => $message['user_name'] ?? null,
                'user_email' => $decrypted_email,
                'consent_given' => !empty($message['consent_given']),
                'consent_timestamp' => $message['consent_timestamp'] ?? null,
            ],
        ], 200);
    }

    /**
     * Validate request origin for CSRF protection.
     *
     * Checks that the Origin or Referer header matches the site URL
     * to prevent cross-site request forgery attacks on public endpoints.
     *
     * Supports multiple domains pointing to the same site via:
     * - WordPress site_url() and home_url()
     * - AIPT_ALLOWED_ORIGINS constant (comma-separated domains)
     * - 'aipt_chatbot_allowed_origins' filter hook
     *
     * @return bool True if origin is valid.
     */
    private function validateRequestOrigin(): bool {

        $allowed_hosts = [];

        $site_host = \wp_parse_url(\site_url(), PHP_URL_HOST);
        if ($site_host) {
            $allowed_hosts[] = $this->normalize_host($site_host);
        }

        $home_host = \wp_parse_url(\home_url(), PHP_URL_HOST);
        if ($home_host) {
            $allowed_hosts[] = $this->normalize_host($home_host);
        }

        if (isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST'])) {
            $current_host = sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST']));

            $current_host = preg_replace('/:\d+$/', '', $current_host);
            if (!empty($current_host)) {
                $allowed_hosts[] = $this->normalize_host($current_host);
            }
        }

        if (defined('AIPT_ALLOWED_ORIGINS') && \AIPT_ALLOWED_ORIGINS) {
            $extra_domains = array_map('trim', explode(',', \AIPT_ALLOWED_ORIGINS));
            foreach ($extra_domains as $domain) {
                if (!empty($domain)) {
                    $allowed_hosts[] = $this->normalize_host($domain);
                }
            }
        }

        /**
         * Filter the allowed origin hosts for chatbot CSRF protection.
         *
         * Allows adding additional domains that should be permitted to make
         * chatbot API requests. Useful for sites with multiple domains
         * pointing to the same WordPress installation.
         *
         * @since 2.3.2
         * @param array $allowed_hosts Array of normalized hostnames (without www.).
         */
        $allowed_hosts = apply_filters('aipt_chatbot_allowed_origins', $allowed_hosts);

        $allowed_hosts = array_unique($allowed_hosts);

        $origin = isset($_SERVER['HTTP_ORIGIN']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_ORIGIN'])) : '';
        if (!empty($origin)) {
            $origin_host = \wp_parse_url($origin, PHP_URL_HOST);
            if ($origin_host && in_array($this->normalize_host($origin_host), $allowed_hosts, true)) {
                return true;
            }

            return false;
        }

        $referer = isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : '';
        if (!empty($referer)) {
            $referer_host = \wp_parse_url($referer, PHP_URL_HOST);
            if ($referer_host && in_array($this->normalize_host($referer_host), $allowed_hosts, true)) {
                return true;
            }

            return false;
        }

        return true;
    }

    /**
     * Normalize host by removing www. prefix and converting to lowercase.
     *
     * @param string $host Hostname.
     * @return string Normalized hostname.
     */
    private function normalize_host(string $host): string {
        return preg_replace('/^www\./i', '', strtolower($host));
    }

    private function getBulkDeleteMessageTemplate(int $deleted_count): string {
        /* translators: %d: number of chatbot messages successfully deleted. */
        return (string) \_n(
            '%d message deleted successfully.',
            '%d messages deleted successfully.',
            $deleted_count,
            'ai-product-tools'
        );
    }

}
