<?php

namespace AIPT\Api\Chatbot;

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';

    /**
     * Maximum message length (characters).
     */
    private const MAX_MESSAGE_LENGTH = 2000;

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

    /**
     * Maximum admin response length (characters).
     */
    private const MAX_RESPONSE_LENGTH = 5000;

    /**
     * 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;

    /**
     * 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.
     */
    public function __construct(
        ?ChatbotService $service = null,
        ?ChatbotSettings $settings = null,
        ?MessageQueue $message_queue = null,
        ?RateLimiter $rate_limiter = null,
        ?ProviderCooldown $provider_cooldown = 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);
    }

    /**
     * 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 {

        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 <= self::MAX_MESSAGE_LENGTH;
                    },
                ],
                '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);
                    },
                ],
                '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) > 10) {
                            return false;
                        }

                        $max_content_length = 2000;

                        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, 10)); 
                    },
                ],
                '_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') <= 100;
                    },
                    '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 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, '/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, 'admin_permission_check'],
        ]);

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

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

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)/respond', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'respond_to_message'],
            'permission_callback' => [$this, 'admin_permission_check'],
            '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 = strlen(trim($value));
                        return $length >= self::MIN_MESSAGE_LENGTH && $length <= self::MAX_RESPONSE_LENGTH;
                    },
                ],
                'product_ids' => [
                    'type' => 'array',
                    'items' => ['type' => 'integer'],
                    'default' => [],
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/(?P<id>\d+)', [
            'methods' => WP_REST_Server::DELETABLE,
            'callback' => [$this, 'delete_message'],
            'permission_callback' => [$this, 'admin_permission_check'],
            '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, 'admin_permission_check'],
        ]);

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

        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, 'admin_permission_check'],
            'args' => [
                'ids' => [
                    'required' => true,
                    'type' => 'array',
                    'items' => ['type' => 'integer'],
                    'validate_callback' => function ($value) {
                        return is_array($value) && !empty($value) && count($value) <= 100;
                    },
                ],
            ],
        ]);

        register_rest_route(self::NAMESPACE, '/messages/search', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'search_messages'],
            'permission_callback' => [$this, 'admin_permission_check'],
            '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'],
                ],
                'page' => [
                    'type' => 'integer',
                    'default' => 1,
                    'minimum' => 1,
                ],
                'per_page' => [
                    'type' => 'integer',
                    'default' => 20,
                    'minimum' => 1,
                    'maximum' => 100,
                ],
            ],
        ]);

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

    /**
     * Admin permission check with REST nonce verification.
     *
     * Ensures both capability and REST nonce are valid to prevent CSRF.
     *
     * @param WP_REST_Request $request Request instance.
     * @return bool|WP_Error True when allowed, WP_Error when forbidden.
     */
    public function admin_permission_check(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 {

        $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);
        }

        $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'] ) {
            $response = new WP_REST_Response([
                'success' => false,
                'code' => 'rate_limited',
                'message' => __('Too many requests. Please wait a moment before trying again.', 'ai-product-tools'),
                'data' => [
                    'retry_after' => $rate_limit_check['retry_after'],
                ],
            ], 429);
            $response->header( 'Retry-After', $rate_limit_check['retry_after'] );
            return $response;
        }

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

        $message = $request->get_param('message');
        if (is_string($message) && self::getStringLength($message) > 2000) {
            return new WP_REST_Response([
                'success' => false,
                'code' => 'message_too_long',
                'message' => __('Message exceeds maximum length of 2000 characters.', 'ai-product-tools'),
            ], 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);
        }
    }

    /**
     * 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 = 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 Response object.
     */
    public function update_settings(WP_REST_Request $request): WP_REST_Response {
        $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);

            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');

        $result = $this->message_queue->getQueued($status, $page, $per_page);

        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');

        $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);
    }

    /**
     * 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(
                    /* translators: %d: number of deleted messages */
                    _n(
                        '%d message deleted successfully.',
                        '%d messages deleted successfully.',
                        $deleted_count,
                        'ai-product-tools'
                    ),
                    $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));
    }

}
