<?php

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

if (!function_exists('vmo_get_platform_faq_grounding_text')) {
    $vmo_platform_faq_path = __DIR__ . '/vmo-platform-faq.php';
    if (file_exists($vmo_platform_faq_path)) {
        require_once $vmo_platform_faq_path;
    }
}

/**
 * VMO Real-time Call Management
 * WordPress REST API endpoints for Node.js voice service integration
 */
class VMO_Realtime_Call_Manager {
    private static $instance = null;
    
    private $active_calls = array();
    private $websocket_connections = array();

    public static function get_instance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }

        return self::$instance;
    }
    
    public function __construct() {
        add_action('rest_api_init', array($this, 'register_rest_routes'));
        add_action('rest_api_init', array($this, 'setup_websocket_handler'));
        
        // Schedule cleanup of expired calls
        add_action('vmo_cleanup_expired_calls', array($this, 'cleanup_expired_calls'));
        
        if (!wp_next_scheduled('vmo_cleanup_expired_calls')) {
            wp_schedule_event(time(), 'hourly', 'vmo_cleanup_expired_calls');
        }
    }

    private function table_exists($table) {
        global $wpdb;
        $like = $wpdb->esc_like($table);
        return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $like)) === $table;
    }

    private function resolve_phone_numbers_table() {
        global $wpdb;

        foreach (array(
            $wpdb->prefix . 'vmo_phone_numbers',
            $wpdb->prefix . 'vmo_business_phones',
            $wpdb->prefix . 'vmo_user_phone_numbers',
        ) as $table) {
            if ($this->table_exists($table)) {
                return $table;
            }
        }

        return '';
    }

    private function resolve_owner_id_by_number($phone_number) {
        global $wpdb;

        $phone_number = trim((string) $phone_number);
        if ($phone_number === '') {
            return 0;
        }

        $table = $this->resolve_phone_numbers_table();
        $normalized = preg_replace('/\D+/', '', $phone_number);

        if ($table !== '') {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from plugin constant
            $columns = $wpdb->get_col("SHOW COLUMNS FROM {$table}", 0);
            $candidate_columns = array_values(array_intersect(
                array('phone_number', 'phone', 'number', 'twilio_number'),
                is_array($columns) ? $columns : array()
            ));

            foreach ($candidate_columns as $column) {
                $owner_id = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT user_id FROM {$table} WHERE {$column} = %s LIMIT 1",
                    $phone_number
                ));
                if ($owner_id > 0) {
                    return $owner_id;
                }

                if ($normalized !== '') {
                    $owner_id = (int) $wpdb->get_var($wpdb->prepare(
                        "SELECT user_id FROM {$table}
                         WHERE REPLACE(REPLACE(REPLACE(REPLACE({$column}, '+', ''), '-', ''), ' ', ''), '(', '') LIKE %s
                         LIMIT 1",
                        '%' . $wpdb->esc_like($normalized)
                    ));
                    if ($owner_id > 0) {
                        return $owner_id;
                    }
                }
            }
        }

        if ($normalized !== '') {
            $meta_owner = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT user_id FROM {$wpdb->usermeta}
                 WHERE meta_key IN ('vmo_phone_number', 'vmo_twilio_phone_number', 'vmo_business_phone', 'business_phone', 'vmo_twilio_number', 'phone')
                 AND REPLACE(REPLACE(REPLACE(REPLACE(meta_value, '+', ''), '-', ''), ' ', ''), '(', '') LIKE %s
                 LIMIT 1",
                '%' . $wpdb->esc_like($normalized)
            ));
            if ($meta_owner > 0) {
                return $meta_owner;
            }
        }

        return 0;
    }

    private function get_business_phone_for_user($user_id) {
        $user_id = (int) $user_id;
        if ($user_id <= 0) {
            return '';
        }

        if (class_exists('VMO_Twilio_Manager')) {
            try {
                return (string) (VMO_Twilio_Manager::get_instance()->get_user_phone($user_id) ?: '');
            } catch (Throwable $e) {
            }
        }

        $table = $this->resolve_phone_numbers_table();
        if ($table !== '') {
            global $wpdb;
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from plugin constant
            $columns = $wpdb->get_col("SHOW COLUMNS FROM {$table}", 0);
            $candidate_columns = array_values(array_intersect(
                array('phone_number', 'phone', 'number', 'twilio_number'),
                is_array($columns) ? $columns : array()
            ));

            foreach ($candidate_columns as $column) {
                $phone = (string) $wpdb->get_var($wpdb->prepare(
                    "SELECT {$column} FROM {$table} WHERE user_id = %d ORDER BY id DESC LIMIT 1",
                    $user_id
                ));
                if ($phone !== '') {
                    return $phone;
                }
            }
        }

        return (string) (get_user_meta($user_id, 'vmo_phone_number', true) ?: '');
    }

    private function append_phone_call_transcript($call_sid, $user_text, $ai_text) {
        global $wpdb;

        $call_sid = sanitize_text_field((string) $call_sid);
        if ($call_sid === '') {
            return;
        }

        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
        if (!$this->table_exists($phone_calls_table)) {
            return;
        }

        $segments = array();
        if ($user_text !== '') {
            $segments[] = 'Caller: ' . $user_text;
        }
        if ($ai_text !== '') {
            $segments[] = 'AI: ' . $ai_text;
        }
        if (empty($segments)) {
            return;
        }

        $existing_transcript = (string) $wpdb->get_var($wpdb->prepare(
            "SELECT transcript FROM {$phone_calls_table} WHERE call_sid = %s ORDER BY id DESC LIMIT 1",
            $call_sid
        ));

        $updated_transcript = trim($existing_transcript . "\n" . implode("\n", $segments));
        if ($updated_transcript === '') {
            return;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update(
            $phone_calls_table,
            array('transcript' => $updated_transcript),
            array('call_sid' => $call_sid),
            array('%s'),
            array('%s')
        );
    }
    
    /**
     * Register REST API routes for Node.js integration
     */
    public function register_rest_routes() {
        $namespace = 'vmo/v1';
        
        // Incoming call notification
        register_rest_route($namespace, '/call-notification', array(
            'methods' => 'POST',
            'callback' => array($this, 'handle_call_notification'),
            'permission_callback' => array($this, 'verify_node_service'),
            'args' => array(
                'call_sid' => array('required' => true, 'type' => 'string'),
                'from_number' => array('required' => true, 'type' => 'string'),
                'to_number' => array('required' => true, 'type' => 'string'),
                'conference_name' => array('required' => true, 'type' => 'string')
            )
        ));
        
        // Conference status updates
        register_rest_route($namespace, '/conference-status', array(
            'methods' => 'POST',
            'callback' => array($this, 'handle_conference_status'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Conversation updates
        register_rest_route($namespace, '/conversation-update', array(
            'methods' => 'POST',
            'callback' => array($this, 'handle_conversation_update'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Business information for AI
        register_rest_route($namespace, '/business-info', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_business_info'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Call preferences
        register_rest_route($namespace, '/call-preferences', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_call_preferences'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Send SMS alert
        register_rest_route($namespace, '/send-sms-alert', array(
            'methods' => 'POST',
            'callback' => array($this, 'send_sms_alert'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Send push notification
        register_rest_route($namespace, '/send-push-alert', array(
            'methods' => 'POST',
            'callback' => array($this, 'send_push_alert'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Send email alert
        register_rest_route($namespace, '/send-email-alert', array(
            'methods' => 'POST',
            'callback' => array($this, 'send_email_alert'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // WebSocket alert for live dashboard
        register_rest_route($namespace, '/websocket-alert', array(
            'methods' => 'POST',
            'callback' => array($this, 'send_websocket_alert'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Call controls for admin
        register_rest_route($namespace, '/call-controls/(?P<conference_sid>[a-zA-Z0-9_-]+)', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_call_controls'),
            'permission_callback' => function() { return current_user_can('manage_options'); }
        ));
        
        // Call completion logging
        register_rest_route($namespace, '/call-completion', array(
            'methods' => 'POST',
            'callback' => array($this, 'log_call_completion'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Test connection
        register_rest_route($namespace, '/test-connection', array(
            'methods' => 'GET',
            'callback' => array($this, 'test_connection'),
            'permission_callback' => array($this, 'verify_node_service')
        ));
        
        // Live call dashboard (admin only)
        register_rest_route($namespace, '/live-calls', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_live_calls'),
            'permission_callback' => function() { return current_user_can('manage_options'); }
        ));
        
        // Call control actions (admin only)
        register_rest_route($namespace, '/call-action', array(
            'methods' => 'POST',
            'callback' => array($this, 'handle_call_action'),
            'permission_callback' => function() { return current_user_can('manage_options'); }
        ));
    }
    
    /**
     * Handle incoming call notification from Node service
     */
    public function handle_call_notification($request) {
        $params = $request->get_json_params();
        
        $call_data = array(
            'call_sid' => sanitize_text_field($params['call_sid']),
            'from_number' => sanitize_text_field($params['from_number']),
            'to_number' => sanitize_text_field($params['to_number']),
            'conference_name' => sanitize_text_field($params['conference_name']),
            'status' => 'active',
            'start_time' => current_time('mysql'),
            'conversation' => array(),
            'ai_summary' => ''
        );
        
        // Store active call
        $this->active_calls[$params['call_sid']] = $call_data;
        
        // Save to database (enhanced business calls schema)
        global $wpdb;
        $calls_table = $wpdb->prefix . 'vmo_business_calls';
        $owner_id = $this->resolve_owner_id_by_number($call_data['to_number']);

        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
        if ($this->table_exists($phone_calls_table)) {
            $existing_phone_call = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$phone_calls_table} WHERE call_sid = %s ORDER BY id DESC LIMIT 1",
                $call_data['call_sid']
            ));

            $phone_call_payload = array(
                'user_id' => $owner_id,
                'call_sid' => $call_data['call_sid'],
                'caller_number' => $call_data['from_number'],
                'direction' => 'inbound',
                'status' => $call_data['status'],
                'topic' => 'conference:' . $call_data['conference_name'],
            );

            if ($existing_phone_call > 0) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->update(
                    $phone_calls_table,
                    $phone_call_payload,
                    array('id' => $existing_phone_call),
                    array('%d', '%s', '%s', '%s', '%s', '%s'),
                    array('%d')
                );
            } else {
                $phone_call_payload['created_at'] = current_time('mysql');
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->insert(
                    $phone_calls_table,
                    $phone_call_payload,
                    array('%d', '%s', '%s', '%s', '%s', '%s', '%s')
                );
            }
        }

        if ($this->table_exists($calls_table)) {
            $existing_business_call = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$calls_table} WHERE call_sid = %s ORDER BY id DESC LIMIT 1",
                $call_data['call_sid']
            ));

            $business_call_payload = array(
                'user_id' => $owner_id,
                'call_sid' => $call_data['call_sid'],
                'from_number' => $call_data['from_number'],
                'to_number' => $call_data['to_number'],
                'call_status' => $call_data['status'],
                'call_start_time' => $call_data['start_time'],
                'ai_summary' => $call_data['ai_summary'],
                'conference_sid' => $call_data['conference_name'],
                'last_activity' => current_time('mysql')
            );

            if ($existing_business_call > 0) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->update(
                    $calls_table,
                    $business_call_payload,
                    array('id' => $existing_business_call),
                    array('%d','%s','%s','%s','%s','%s','%s','%s','%s'),
                    array('%d')
                );
            } else {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->insert($calls_table, $business_call_payload, array('%d','%s','%s','%s','%s','%s','%s','%s','%s'));
            }
        }
        
        // Send real-time alerts
        $this->send_real_time_alerts($call_data);
        
        // Auto-dial business owner if enabled
        $auto_dial = get_option('vmo_auto_dial_owner', false);
        if ($auto_dial) {
            $this->schedule_owner_dial($call_data);
        }
        
        return new WP_REST_Response(array(
            'success' => true,
            'message' => 'Call notification received',
            'call_id' => $call_data['call_sid']
        ), 200);
    }
    
    /**
     * Handle conference status updates
     */
    public function handle_conference_status($request) {
        $params = $request->get_json_params();
        
        $conference_sid = sanitize_text_field($params['conference_sid']);
        $event = sanitize_text_field($params['event']);
        $participant = sanitize_text_field($params['participant']);
        
        // Log conference event
        error_log("Conference {$conference_sid}: {$event} - {$participant}");
        
        // Update active call status
        foreach ($this->active_calls as $call_sid => &$call_data) {
            if ($call_data['conference_name'] === $conference_sid) {
                $call_data['events'][] = array(
                    'event' => $event,
                    'participant' => $participant,
                    'timestamp' => current_time('mysql')
                );
                break;
            }
        }
        
        return new WP_REST_Response(array('success' => true), 200);
    }
    
    /**
     * Handle conversation updates
     */
    public function handle_conversation_update($request) {
        $params = $request->get_json_params();
        
        $call_sid = sanitize_text_field($params['call_sid']);
        $user_text = sanitize_textarea_field($params['user_text']);
        $ai_text = sanitize_textarea_field($params['ai_text']);
        
        // Add to active call conversation and persist to logs
        if (isset($this->active_calls[$call_sid])) {
            $this->active_calls[$call_sid]['conversation'][] = array(
                'timestamp' => current_time('mysql'),
                'user' => $user_text,
                'ai' => $ai_text
            );

            // Update database
            global $wpdb;
            $calls_table = $wpdb->prefix . 'vmo_business_calls';
            $logs_table = $wpdb->prefix . 'vmo_call_logs';
            if ($this->table_exists($calls_table) && $this->table_exists($logs_table)) {
                $call_id = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$calls_table} WHERE call_sid = %s LIMIT 1", $call_sid));
                if ($call_id) {
                    if (!empty($user_text)) {
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                        $wpdb->insert($logs_table, array(
                            'call_id' => (int)$call_id,
                            'speaker' => 'user',
                            'message' => $user_text,
                            'timestamp' => current_time('mysql')
                        ), array('%d','%s','%s','%s'));
                    }
                    if (!empty($ai_text)) {
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                        $wpdb->insert($logs_table, array(
                            'call_id' => (int)$call_id,
                            'speaker' => 'ai',
                            'message' => $ai_text,
                            'timestamp' => current_time('mysql')
                        ), array('%d','%s','%s','%s'));
                    }
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->update($calls_table, array('last_activity' => current_time('mysql')), array('id' => (int)$call_id), array('%s'), array('%d'));
                }
            } else {
                $this->append_phone_call_transcript($call_sid, $user_text, $ai_text);
            }
        }
        
        return new WP_REST_Response(array('success' => true), 200);
    }
    
    /**
     * Get business information for AI
     */
    public function get_business_info($request) {
        $owner_id = 0;

        // Prefer per-user business context if we can resolve the owner by destination number.
        $maybe_to = $request->get_param('to_number');
        if (empty($maybe_to)) {
            // Some integrations might pass headers instead
            $maybe_to = $request->get_header('x-vmo-to-number');
        }
        if (!empty($maybe_to)) {
            $owner_id = $this->resolve_owner_id_by_number($maybe_to);
        }

        if ($owner_id <= 0) {
            $uid = get_current_user_id();
            if ($uid) {
                $owner_id = (int) $uid;
            }
        }

        $business_name = $owner_id > 0 ? (string) get_user_meta($owner_id, 'business_name', true) : '';
        if ($business_name === '') {
            $business_name = get_option('vmo_business_name', get_bloginfo('name'));
        }

        $business_description = $owner_id > 0 ? (string) get_user_meta($owner_id, 'business_description', true) : '';
        if ($business_description === '') {
            $business_description = get_option('vmo_business_description', '');
        }

        $business_hours = $owner_id > 0 ? (string) get_user_meta($owner_id, 'business_hours', true) : '';
        if ($business_hours === '') {
            $business_hours = get_option('vmo_business_hours', '');
        }

        $website = $owner_id > 0 ? (string) get_user_meta($owner_id, 'website_url', true) : '';
        if ($website === '') {
            $website = home_url();
        }

        $business_email = $owner_id > 0 ? (string) get_user_meta($owner_id, 'business_email', true) : '';
        if ($business_email === '') {
            $business_email = get_option('admin_email');
        }

        $approved_business_context = array(
            'industry_type' => $owner_id > 0 ? get_user_meta($owner_id, 'industry_type', true) : '',
            'business_address' => $owner_id > 0 ? get_user_meta($owner_id, 'business_address', true) : '',
            'services' => $owner_id > 0 ? get_user_meta($owner_id, 'business_services', true) : '',
            'pricing' => $owner_id > 0 ? get_user_meta($owner_id, 'business_pricing', true) : '',
            'faq' => $owner_id > 0 ? get_user_meta($owner_id, 'business_faq', true) : '',
            'phone' => $owner_id > 0 ? $this->get_business_phone_for_user($owner_id) : '',
        );

        $platform_faq = '';
        $is_vmo_brand = function_exists('vmo_platform_faq_should_ground_current_site')
            ? vmo_platform_faq_should_ground_current_site((string) $business_name, (string) $website)
            : (stripos((string) $business_name . ' ' . (string) $website . ' ' . home_url('/'), 'virtual media office') !== false || preg_match('/\bvmo\b/i', (string) $business_name . ' ' . (string) $website . ' ' . home_url('/')) === 1);
        if ($is_vmo_brand && function_exists('vmo_get_platform_faq_grounding_text')) {
            $platform_faq = vmo_get_platform_faq_grounding_text('realtime_phone_business_info', 8500);
        }

        // Prefer per-user AI greeting if available, then global option.
        $ai_prompt = '';
        if ($owner_id > 0) {
            $ai_prompt = get_user_meta($owner_id, 'vmo_ai_phone_greeting', true);
            if (empty($ai_prompt)) {
                $settings = get_user_meta($owner_id, 'vmo_ai_receptionist_settings', true);
                if (is_array($settings) && !empty($settings['prompt'])) {
                    $ai_prompt = $settings['prompt'];
                }
            }
        }
        if (empty($ai_prompt)) {
            $ai_prompt = get_option('vmo_ai_phone_prompt', '');
        }
        
        return new WP_REST_Response(array(
            'user_id' => $owner_id,
            'business_name' => $business_name,
            'business_description' => $business_description,
            'ai_prompt' => $ai_prompt,
            'business_hours' => $business_hours,
            'approved_business_context' => $approved_business_context,
            'platform_faq_source_of_truth' => $platform_faq,
            'contact_info' => array(
                'website' => $website,
                'email' => $business_email,
                'phone' => $approved_business_context['phone']
            )
        ), 200);
    }
    
    /**
     * Get call preferences
     */
    public function get_call_preferences($request) {
        return new WP_REST_Response(array(
            'auto_dial_owner' => get_option('vmo_auto_dial_owner', false),
            'owner_phone' => get_option('vmo_owner_phone_number', ''),
            'dial_delay' => get_option('vmo_owner_dial_delay', 5), // seconds
            'enable_sms_alerts' => get_option('vmo_enable_sms_alerts', true),
            'enable_email_alerts' => get_option('vmo_enable_email_alerts', true),
            'enable_push_alerts' => get_option('vmo_enable_push_alerts', false)
        ), 200);
    }
    
    /**
     * Send SMS alert
     */
    public function send_sms_alert($request) {
        $params = $request->get_json_params();
        
        $owner_phone = get_option('vmo_owner_phone_number', '');
        if (empty($owner_phone) || !get_option('vmo_enable_sms_alerts', true)) {
            return new WP_REST_Response(array('success' => false, 'message' => 'SMS alerts disabled'), 200);
        }

        // ✅ TOKEN CHECK: Verify balance before sending SMS alert
        $user_id = (int) ($params['user_id'] ?? get_current_user_id());
        if ($user_id && class_exists('VMO_Twilio_Token_Economics')) {
            if (!VMO_Twilio_Token_Economics::get_instance()->can_send_sms($user_id)) {
                return new WP_REST_Response(array('success' => false, 'message' => 'Insufficient tokens for SMS'), 402);
            }
        }

        $from_number = $this->get_business_phone_for_user($user_id);
        if ($from_number === '') {
            return new WP_REST_Response(array('success' => false, 'message' => 'Business phone number not found for SMS alert'), 200);
        }
        
        // Use Twilio to send SMS
        try {
            if (!class_exists('VMO_Twilio_REST')) {
                require_once plugin_dir_path(__FILE__) . 'class-vmo-twilio-rest.php';
            }

            $twilio_client = new VMO_Twilio_REST();
            $message = sanitize_text_field($params['message']);
            $result = $twilio_client->send_sms($owner_phone, $from_number, $message);
            
            if (!is_wp_error($result)) {
                if ($user_id && class_exists('VMO_Twilio_Token_Economics')) {
                    VMO_Twilio_Token_Economics::get_instance()->deduct_sms_outbound($user_id);
                }
                return new WP_REST_Response(array('success' => true, 'message' => 'SMS sent'), 200);
            } else {
                return new WP_REST_Response(array(
                    'success' => false,
                    'message' => is_wp_error($result) ? $result->get_error_message() : 'SMS failed'
                ), 200);
            }
            
        } catch (Exception $e) {
            return new WP_REST_Response(array('success' => false, 'message' => $e->getMessage()), 500);
        }
    }
    
    /**
     * Queue an alert for polling clients and fall back to email when push providers are not configured.
     */
    public function send_push_alert($request) {
        $params = $request->get_json_params();

        $user_id = $params['user_id'] ?? 0;
        $title   = sanitize_text_field($params['title'] ?? 'Incoming Call');
        $body    = sanitize_text_field($params['body'] ?? '');
        $data    = $params['data'] ?? [];

        if (empty($user_id)) {
            return new WP_REST_Response(array('success' => false, 'message' => 'Missing user_id'), 400);
        }

        // Store alert as a transient so the polling endpoint can deliver it
        $alerts_key = 'vmo_push_alerts_' . intval($user_id);
        $existing = get_transient($alerts_key) ?: [];
        $existing[] = [
            'title' => $title,
            'body'  => $body,
            'data'  => $data,
            'time'  => current_time('mysql'),
        ];
        set_transient($alerts_key, $existing, 300); // Expire after 5 minutes

        // Also try wp_mail as fallback notification
        $user = get_user_by('ID', $user_id);
        if ($user && get_option('vmo_enable_email_alerts', true)) {
            wp_mail(
                $user->user_email,
                '[VMO] ' . $title,
                $body . "\n\nThis alert was sent because push notifications are not yet configured.",
                ['Content-Type: text/plain; charset=UTF-8']
            );
        }

        return new WP_REST_Response(array(
            'success' => true,
            'message' => 'Alert queued (transient + email fallback)',
            'delivery' => 'transient_polling'
        ), 200);
    }
    
    /**
     * Send email alert
     */
    public function send_email_alert($request) {
        $params = $request->get_json_params();
        
        if (!get_option('vmo_enable_email_alerts', true)) {
            return new WP_REST_Response(array('success' => false, 'message' => 'Email alerts disabled'), 200);
        }
        
        $to = get_option('admin_email');
        $subject = sanitize_text_field($params['subject']);
        $from_number = sanitize_text_field($params['from_number']);
        $call_sid = sanitize_text_field($params['call_sid']);
        
        $message = "You have an incoming call from {$from_number}.\n\n";
        $message .= "Call ID: {$call_sid}\n";
        $message .= "Time: " . current_time('F j, Y g:i a') . "\n\n";
        $message .= "Your AI assistant is handling the call. You can join by dialing your business number.\n\n";
        $message .= "View live calls: " . admin_url('admin.php?page=vmo-live-calls');
        
        $sent = wp_mail($to, $subject, $message);
        
        return new WP_REST_Response(array('success' => $sent), 200);
    }
    
    /**
     * Send WebSocket alert for live dashboard
     */
    public function send_websocket_alert($request) {
        $params = $request->get_json_params();
        
        // Store alert for WebSocket connections
        $alert_data = array(
            'type' => 'incoming_call',
            'call_sid' => sanitize_text_field($params['call_sid']),
            'from_number' => sanitize_text_field($params['from_number']),
            'conference_name' => sanitize_text_field($params['conference_name']),
            'timestamp' => current_time('mysql')
        );
        
        // In a real implementation, this would push to WebSocket connections
        // For now, store in transient for polling-based updates
        set_transient('vmo_live_call_alert_' . $params['call_sid'], $alert_data, 300); // 5 minutes
        
        return new WP_REST_Response(array('success' => true), 200);
    }
    
    /**
     * Get live calls for admin dashboard
     */
    public function get_live_calls($request) {
        global $wpdb;
        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';

        if ($this->table_exists($phone_calls_table)) {
            $requested_user_id = (int) ($request->get_param('user_id') ?: 0);
            $query = "SELECT user_id, call_sid, caller_number, direction, status, transcript, topic, ai_handled, created_at
                      FROM {$phone_calls_table}
                      WHERE (status IS NULL OR status NOT IN ('completed', 'busy', 'failed', 'canceled', 'no-answer', 'expired'))
                      AND created_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)";
            $params = array();

            if ($requested_user_id > 0) {
                $query .= " AND user_id = %d";
                $params[] = $requested_user_id;
            }

            $query .= " ORDER BY created_at DESC";
            $active_calls = !empty($params)
                ? $wpdb->get_results($wpdb->prepare($query, ...$params))
                : $wpdb->get_results($query);

            $formatted_calls = array();
            foreach (($active_calls ?: array()) as $call) {
                $business_phone = $this->get_business_phone_for_user((int) ($call->user_id ?? 0));
                $direction = (string) ($call->direction ?? 'inbound');
                $caller_number = (string) ($call->caller_number ?? '');
                $topic = (string) ($call->topic ?? '');
                $conference_name = (strpos($topic, 'conference:') === 0)
                    ? substr($topic, 11)
                    : 'vmo_call_' . (string) ($call->call_sid ?? '');

                $formatted_calls[] = array(
                    'call_sid' => (string) ($call->call_sid ?? ''),
                    'from_number' => ($direction === 'outbound') ? $business_phone : $caller_number,
                    'to_number' => ($direction === 'outbound') ? $caller_number : $business_phone,
                    'conference_name' => $conference_name,
                    'start_time' => (string) ($call->created_at ?? ''),
                    'duration' => !empty($call->created_at) ? human_time_diff(strtotime($call->created_at)) : '',
                    'conversation_count' => 0,
                    'last_activity' => (string) ($call->created_at ?? ''),
                    'ai_summary' => '',
                );
            }

            return new WP_REST_Response($formatted_calls, 200);
        }

        $calls_table = $wpdb->prefix . 'vmo_business_calls';
        $logs_table = $wpdb->prefix . 'vmo_call_logs';

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $active_calls = $wpdb->get_results(
            "SELECT * FROM {$calls_table} 
             WHERE call_status = 'active' 
             ORDER BY call_start_time DESC"
        );

        $formatted_calls = array();
        foreach ($active_calls as $call) {
            $conversation_count = (int)$wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$logs_table} WHERE call_id = %d",
                $call->id
            ));

            $formatted_calls[] = array(
                'call_sid' => $call->call_sid,
                'from_number' => $call->from_number,
                'to_number' => $call->to_number,
                'conference_name' => $call->conference_sid,
                'start_time' => $call->call_start_time,
                'duration' => human_time_diff(strtotime($call->call_start_time)),
                'conversation_count' => $conversation_count,
                'last_activity' => $call->last_activity,
                'ai_summary' => $call->ai_summary
            );
        }

        return new WP_REST_Response($formatted_calls, 200);
    }

    /**
     * Get live controls and transcript state for a conference.
     */
    public function get_call_controls($request) {
        $conference_sid = sanitize_text_field($request['conference_sid'] ?? '');
        if ($conference_sid === '') {
            return new WP_REST_Response(array(
                'success' => false,
                'message' => 'Missing conference identifier',
            ), 400);
        }

        $call_context = $this->resolve_call_context('', $conference_sid);
        if (empty($call_context['call_sid'])) {
            return new WP_REST_Response(array(
                'success' => false,
                'message' => 'Call not found for conference',
                'conference_sid' => $conference_sid,
            ), 404);
        }

        $transcript_state = array(
            'transcript' => $call_context['transcript'],
            'ai_handled' => $call_context['ai_handled'],
            'status' => $call_context['status'],
        );

        if (class_exists('VMO_Twilio_Manager')) {
            $twilio_manager = VMO_Twilio_Manager::get_instance();
            if (method_exists($twilio_manager, 'get_call_transcript')) {
                $twilio_state = $twilio_manager->get_call_transcript($call_context['call_sid'], (int) $call_context['user_id']);
                if (is_array($twilio_state)) {
                    $transcript_state = array_merge($transcript_state, $twilio_state);
                }
            }
        }

        return new WP_REST_Response(array(
            'success' => true,
            'call_sid' => $call_context['call_sid'],
            'conference_sid' => $call_context['conference_sid'],
            'user_id' => (int) $call_context['user_id'],
            'from_number' => $call_context['from_number'],
            'to_number' => $call_context['to_number'],
            'ai_summary' => $call_context['ai_summary'],
            'start_time' => $call_context['start_time'],
            'status' => $transcript_state['status'] ?? $call_context['status'],
            'transcript' => $transcript_state['transcript'] ?? '',
            'ai_handled' => !empty($transcript_state['ai_handled']),
            'available_actions' => array('join', 'listen', 'whisper', 'barge', 'resume_ai'),
        ), 200);
    }

    /**
     * Resolve call context from the business call tables.
     */
    private function resolve_call_context($call_sid = '', $conference_sid = '') {
        global $wpdb;

        $context = array(
            'user_id' => 0,
            'call_sid' => sanitize_text_field((string) $call_sid),
            'conference_sid' => sanitize_text_field((string) $conference_sid),
            'from_number' => '',
            'to_number' => '',
            'status' => '',
            'transcript' => '',
            'ai_summary' => '',
            'ai_handled' => false,
            'start_time' => '',
        );

        // ── Suppress wpdb HTML error output during this lookup ──
        // The legacy vmo_business_calls table on older installs may be missing
        // newer columns (from_number/to_number/conference_sid/etc.). When the
        // SELECT below fails on a missing column, $wpdb prints an HTML
        // <div id="error"> directly into the AJAX response, which the JS sees
        // as "Non-JSON response" and the Listen button reports as failure.
        // Suppressing here lets us silently skip the legacy table and continue.
        $prev_suppress = $wpdb->suppress_errors(true);
        $prev_show     = $wpdb->show_errors(false);

        $business_calls_table = $wpdb->prefix . 'vmo_business_calls';
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $business_calls_table)) === $business_calls_table) {
            // Verify required columns exist before issuing the SELECT.
            $cols = $wpdb->get_col("SHOW COLUMNS FROM {$business_calls_table}", 0);
            $needed = array('user_id','call_sid','conference_sid','from_number','to_number','call_status','transcription','ai_summary','call_start_time');
            $missing = is_array($cols) ? array_diff($needed, $cols) : $needed;
            $row = null;
            if (empty($missing)) {
                if ($conference_sid !== '') {
                    $row = $wpdb->get_row($wpdb->prepare(
                        "SELECT user_id, call_sid, conference_sid, from_number, to_number, call_status, transcription, ai_summary, call_start_time
                         FROM {$business_calls_table}
                         WHERE conference_sid = %s
                         ORDER BY id DESC
                         LIMIT 1",
                        $conference_sid
                    ), ARRAY_A);
                } elseif ($call_sid !== '') {
                    $row = $wpdb->get_row($wpdb->prepare(
                        "SELECT user_id, call_sid, conference_sid, from_number, to_number, call_status, transcription, ai_summary, call_start_time
                         FROM {$business_calls_table}
                         WHERE call_sid = %s
                         ORDER BY id DESC
                         LIMIT 1",
                        $call_sid
                    ), ARRAY_A);
                }
            } else {
                error_log('[VMO Realtime] Skipping legacy ' . $business_calls_table . ' lookup; missing columns: ' . implode(',', $missing));
            }

            if (is_array($row) && !empty($row)) {
                $context['user_id'] = (int) ($row['user_id'] ?? 0);
                $context['call_sid'] = sanitize_text_field((string) ($row['call_sid'] ?? $context['call_sid']));
                $context['conference_sid'] = sanitize_text_field((string) ($row['conference_sid'] ?? $context['conference_sid']));
                $context['from_number'] = sanitize_text_field((string) ($row['from_number'] ?? ''));
                $context['to_number'] = sanitize_text_field((string) ($row['to_number'] ?? ''));
                $context['status'] = sanitize_text_field((string) ($row['call_status'] ?? ''));
                $context['transcript'] = sanitize_textarea_field((string) ($row['transcription'] ?? ''));
                $context['ai_summary'] = sanitize_textarea_field((string) ($row['ai_summary'] ?? ''));
                $context['start_time'] = sanitize_text_field((string) ($row['call_start_time'] ?? ''));
            }
        }

        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $phone_calls_table)) === $phone_calls_table) {
            $phone_cols    = $wpdb->get_col("SHOW COLUMNS FROM {$phone_calls_table}", 0);
            $phone_needed  = array('user_id','call_sid','caller_number','status','transcript','topic','ai_handled','created_at');
            $phone_missing = is_array($phone_cols) ? array_diff($phone_needed, $phone_cols) : $phone_needed;
            $phone_row = null;
            if (empty($phone_missing)) {
                if ($call_sid !== '' || $context['call_sid'] !== '') {
                    $lookup_call_sid = $context['call_sid'] !== '' ? $context['call_sid'] : $call_sid;
                    $phone_row = $wpdb->get_row($wpdb->prepare(
                        "SELECT user_id, call_sid, caller_number, status, transcript, topic, ai_handled, created_at
                         FROM {$phone_calls_table}
                         WHERE call_sid = %s
                         ORDER BY id DESC
                         LIMIT 1",
                        $lookup_call_sid
                    ), ARRAY_A);
                } elseif ($conference_sid !== '' || $context['conference_sid'] !== '') {
                    $lookup_conference_sid = $context['conference_sid'] !== '' ? $context['conference_sid'] : $conference_sid;
                    $phone_row = $wpdb->get_row($wpdb->prepare(
                        "SELECT user_id, call_sid, caller_number, status, transcript, topic, ai_handled, created_at
                         FROM {$phone_calls_table}
                         WHERE topic = %s
                         ORDER BY id DESC
                         LIMIT 1",
                        'conference:' . $lookup_conference_sid
                    ), ARRAY_A);
                }
            } else {
                error_log('[VMO Realtime] Skipping ' . $phone_calls_table . ' lookup; missing columns: ' . implode(',', $phone_missing));
            }

            if (is_array($phone_row) && !empty($phone_row)) {
                $context['user_id'] = (int) ($phone_row['user_id'] ?? $context['user_id']);
                $context['call_sid'] = sanitize_text_field((string) ($phone_row['call_sid'] ?? $context['call_sid']));
                $context['from_number'] = $context['from_number'] !== '' ? $context['from_number'] : sanitize_text_field((string) ($phone_row['caller_number'] ?? ''));
                $context['status'] = $context['status'] !== '' ? $context['status'] : sanitize_text_field((string) ($phone_row['status'] ?? ''));
                $context['transcript'] = $context['transcript'] !== '' ? $context['transcript'] : sanitize_textarea_field((string) ($phone_row['transcript'] ?? ''));
                $context['ai_handled'] = !empty($phone_row['ai_handled']);
                $context['start_time'] = $context['start_time'] !== '' ? $context['start_time'] : sanitize_text_field((string) ($phone_row['created_at'] ?? ''));

                if ($context['conference_sid'] === '') {
                    $topic = (string) ($phone_row['topic'] ?? '');
                    if (strpos($topic, 'conference:') === 0) {
                        $context['conference_sid'] = sanitize_text_field(substr($topic, 11));
                    }
                }
            }
        }

        // Restore wpdb error display preferences
        $wpdb->suppress_errors($prev_suppress);
        $wpdb->show_errors($prev_show);

        return $context;
    }

    /**
     * Attempt call control via the optional Node bridge.
     */
    private function dispatch_call_action_to_node($action, $call_sid, $conference_name) {
        $node_service_url = trim((string) get_option('vmo_node_service_url', ''));
        if ($node_service_url === '') {
            return array(
                'success' => false,
                'message' => 'Node service URL not configured',
            );
        }

        $response = wp_remote_post(untrailingslashit($node_service_url) . '/wordpress/call-control', array(
            'timeout' => 2,
            'body' => wp_json_encode(array(
                'action' => $action,
                'callSid' => $call_sid,
                'conferenceSid' => $conference_name,
            )),
            'headers' => array(
                'Content-Type' => 'application/json',
            ),
        ));

        if (is_wp_error($response)) {
            return array(
                'success' => false,
                'message' => $response->get_error_message(),
            );
        }

        $body = wp_remote_retrieve_body($response);
        $result = json_decode($body, true);
        if (!is_array($result)) {
            return array(
                'success' => false,
                'message' => 'Node service returned an invalid response',
            );
        }

        return $result;
    }

    public function route_call_action($action, $call_sid, $conference_name = '', $via = 'auto', $child_sid = '', $user_id = 0) {
        $action = sanitize_text_field((string) $action);
        $call_sid = sanitize_text_field((string) $call_sid);
        $conference_name = sanitize_text_field((string) $conference_name);
        $via = sanitize_text_field((string) $via);
        $child_sid = sanitize_text_field((string) $child_sid);
        $user_id = (int) $user_id;

        if (!in_array($action, array('join', 'listen', 'whisper', 'barge', 'resume_ai', 'hold', 'unhold', 'end', 'hangup'), true)) {
            return array(
                'success' => false,
                'message' => 'Invalid action type',
                'status_code' => 400,
            );
        }

        $call_context = $this->resolve_call_context($call_sid, $conference_name);
        if ($call_sid === '') {
            $call_sid = $call_context['call_sid'];
        }
        if ($conference_name === '') {
            $conference_name = $call_context['conference_sid'];
        }

        // ── Child → Parent SID resolution via reverse transient ──
        // When the browser rings via REST API, the SDK only knows the child
        // leg SID. The voice webhook stores a reverse mapping transient
        // (vmo_child_ring_parent_{child}) → parent SID. Use it here when
        // call_sid is still empty but child_sid is available.
        if ($call_sid === '' && $child_sid !== '') {
            $reverse_key = 'vmo_child_ring_parent_' . sanitize_key($child_sid);
            $parent_sid  = get_transient($reverse_key);
            if (is_string($parent_sid) && $parent_sid !== '') {
                $call_sid = sanitize_text_field($parent_sid);
                // Re-resolve context with the recovered parent SID
                $call_context   = $this->resolve_call_context($call_sid, $conference_name);
                $conference_name = ($conference_name === '') ? $call_context['conference_sid'] : $conference_name;
            }
        }

        if ($call_sid === '') {
            return array(
                'success' => false,
                'message' => 'Missing call SID',
                'status_code' => 400,
            );
        }

        if (in_array($action, array('end', 'hangup'), true) && class_exists('VMO_Twilio_Manager')) {
            $resolved_user_id = !empty($call_context['user_id']) ? (int) $call_context['user_id'] : $user_id;
            if ($resolved_user_id <= 0) {
                $resolved_user_id = get_current_user_id();
            }

            $result = VMO_Twilio_Manager::get_instance()->end_call($call_sid, array(
                'user_id' => $resolved_user_id,
                'child_sid' => $child_sid,
                'reason' => 'manual',
                'status' => 'completed',
            ));

            if (is_wp_error($result)) {
                return array(
                    'success' => false,
                    'message' => $result->get_error_message(),
                    'status_code' => 500,
                );
            }

            $result = is_array($result) ? $result : array('message' => 'Call ended successfully');
            $result['success'] = true;
            $result['routed_via'] = 'twilio_teardown';
            return $result;
        }

        $node_result = $this->dispatch_call_action_to_node($action, $call_sid, $conference_name);
        if (!empty($node_result['success'])) {
            $needs_browser_join_payload = ($via === 'browser' && in_array($action, array('join', 'barge', 'listen'), true));
            $has_browser_join_payload = !empty($node_result['conference_name'])
                || !empty($node_result['conference_sid'])
                || !empty($node_result['listen_url']);
            if ($needs_browser_join_payload && !$has_browser_join_payload) {
                error_log('[VMO Realtime] Node call-control returned success without browser join payload; falling back to Twilio for ' . $action . ' on ' . $call_sid);
            } else {
                if (empty($node_result['call_sid'])) {
                    $node_result['call_sid'] = $call_sid;
                }
                if (empty($node_result['conference_name']) && $conference_name !== '') {
                    $node_result['conference_name'] = $conference_name;
                }
                $node_result['routed_via'] = $node_result['routed_via'] ?? 'node_bridge';
                return $node_result;
            }
        }

        if (class_exists('VMO_Twilio_Manager')) {
            $resolved_user_id = !empty($call_context['user_id']) ? (int) $call_context['user_id'] : $user_id;
            if ($resolved_user_id <= 0) {
                $resolved_user_id = get_current_user_id();
            }

            $twilio_manager = VMO_Twilio_Manager::get_instance();
            $result = $twilio_manager->call_action($action, $call_sid, $conference_name, $resolved_user_id, $via, $child_sid);

            if (!is_wp_error($result)) {
                if (!is_array($result)) {
                    $result = array('message' => 'Call action completed');
                }
                $result['success'] = true;
                $result['routed_via'] = 'twilio_fallback';
                if (!empty($node_result['message'])) {
                    $result['node_bridge_error'] = $node_result['message'];
                }

                return $result;
            }

            return array(
                'success' => false,
                'message' => $result->get_error_message(),
                'status_code' => 500,
                'node_bridge_error' => $node_result['message'] ?? '',
            );
        }

        return array(
            'success' => false,
            'message' => $node_result['message'] ?? 'No call control backend available',
            'status_code' => 500,
        );
    }
    
    /**
     * Handle call control actions
     */
    public function handle_call_action($request) {
        $params = $request->get_json_params();

        $action = sanitize_text_field($params['action'] ?? '');
        $call_sid = sanitize_text_field($params['call_sid'] ?? '');
        $conference_name = sanitize_text_field($params['conference_name'] ?? ($params['conference_sid'] ?? ''));
        $via = sanitize_text_field($params['via'] ?? 'auto');
        $child_sid = sanitize_text_field($params['child_sid'] ?? '');
        $result = $this->route_call_action($action, $call_sid, $conference_name, $via, $child_sid, get_current_user_id());
        $status_code = !empty($result['success']) ? 200 : (int) ($result['status_code'] ?? 500);
        unset($result['status_code']);

        return new WP_REST_Response($result, $status_code);
    }
    
    /**
     * Log call completion
     */
    public function log_call_completion($request) {
        $params = $request->get_json_params();
        
        $call_sid = sanitize_text_field($params['call_sid']);
        $duration = intval($params['duration']);
        $transcript = sanitize_textarea_field($params['transcript']);
        $summary = sanitize_textarea_field($params['summary']);
        
        global $wpdb;
        $calls_table = $wpdb->prefix . 'vmo_business_calls';
        $recordings_table = $wpdb->prefix . 'vmo_call_recordings';
        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';

        if ($this->table_exists($calls_table)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update($calls_table,
                array(
                    'call_status' => 'completed',
                    'call_end_time' => current_time('mysql'),
                    'call_duration' => $duration,
                    'transcription' => $transcript,
                    'ai_summary' => $summary
                ),
                array('call_sid' => $call_sid),
                array('%s', '%s', '%d', '%s', '%s'),
                array('%s')
            );
        }

        if ($this->table_exists($phone_calls_table)) {
            $phone_call_update = array(
                'status' => 'completed',
                'duration' => $duration,
                'transcript' => $transcript,
                'summary' => $summary,
            );
            $phone_call_formats = array('%s', '%d', '%s', '%s');

            if (!empty($params['recording_url'])) {
                $phone_call_update['recording_url'] = esc_url_raw($params['recording_url']);
                $phone_call_formats[] = '%s';
            }

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update(
                $phone_calls_table,
                $phone_call_update,
                array('call_sid' => $call_sid),
                $phone_call_formats,
                array('%s')
            );
        }

        // Persist recording if provided
        if ($this->table_exists($recordings_table) && !empty($params['recording_sid']) && !empty($params['recording_url'])) {
            $call_id = (int)$wpdb->get_var($wpdb->prepare("SELECT id FROM {$calls_table} WHERE call_sid = %s", $call_sid));
            if ($call_id) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->insert($recordings_table, array(
                    'call_id' => $call_id,
                    'recording_sid' => sanitize_text_field($params['recording_sid']),
                    'recording_url' => esc_url_raw($params['recording_url']),
                    'duration' => intval($params['recording_duration'] ?? 0)
                ), array('%d','%s','%s','%d'));
            }
        }
        
        // Remove from active calls
        unset($this->active_calls[$call_sid]);
        
        return new WP_REST_Response(array('success' => true), 200);
    }
    
    /**
     * Test connection from Node service
     */
    public function test_connection($request) {
        return new WP_REST_Response(array(
            'success' => true,
            'message' => 'WordPress connection successful',
            'timestamp' => current_time('mysql'),
            'wordpress_version' => get_bloginfo('version'),
            'plugin_version' => VMO_AI_BUILDER_VERSION
        ), 200);
    }
    
    /**
     * Verify requests from Node.js service
     */
    public function verify_node_service($request) {
        // Simple authentication - in production, use proper JWT or API keys
        $auth_header = $request->get_header('authorization');
        $expected_token = get_option('vmo_node_service_token', 'default_token');
        
        if ($auth_header && str_replace('Bearer ', '', $auth_header) === $expected_token) {
            return true;
        }
        
        // For development, allow localhost
        $origin = (string) ($request->get_header('origin') ?? '');
        if (strpos($origin, 'localhost') !== false || strpos($origin, '127.0.0.1') !== false) {
            return true;
        }
        
        return new WP_Error('auth_failed', 'Authentication required', array('status' => 401));
    }
    
    /**
     * Send real-time alerts
     */
    private function send_real_time_alerts($call_data) {
        // Schedule immediate alerts
        wp_schedule_single_event(time() + 1, 'vmo_send_call_alerts', array($call_data));
    }
    
    /**
     * Schedule business owner dial-in
     */
    private function schedule_owner_dial($call_data) {
        $delay = get_option('vmo_owner_dial_delay', 5);
        wp_schedule_single_event(time() + $delay, 'vmo_dial_owner', array($call_data));
    }
    
    /**
     * Setup real-time alert delivery via a lightweight REST polling endpoint.
     */
    public function setup_websocket_handler() {
        // WebSocket requires a persistent server process which WordPress cannot provide natively.
        // Instead, we register a lightweight REST polling endpoint that clients can hit every few seconds
        // to retrieve queued alerts (stored as transients by send_push_alert).
        register_rest_route('vmo-phone/v1', '/poll-alerts', [
            'methods'             => 'GET',
            'callback'            => [$this, 'poll_alerts'],
            'permission_callback' => function() {
                return is_user_logged_in();
            },
        ]);
    }

    /**
     * Polling endpoint: return and clear any queued push alerts for the current user.
     */
    public function poll_alerts($request) {
        $user_id = get_current_user_id();
        $alerts_key = 'vmo_push_alerts_' . $user_id;
        $alerts = get_transient($alerts_key) ?: [];

        if (!empty($alerts)) {
            delete_transient($alerts_key);
        }

        return new WP_REST_Response([
            'success' => true,
            'alerts'  => $alerts,
        ], 200);
    }
    
    /**
     * Cleanup expired calls
     */
    public function cleanup_expired_calls() {
        global $wpdb;
        $calls_table = $wpdb->prefix . 'vmo_business_calls';

        // Expire calls inactive for > 1 hour
        $threshold = date('Y-m-d H:i:s', strtotime('-1 hour'));
        if ($this->table_exists($calls_table)) {
            $wpdb->query($wpdb->prepare(
                "UPDATE {$calls_table} SET call_status = 'expired' WHERE call_status = 'active' AND last_activity < %s",
                $threshold
            ));
        }

        $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
        if ($this->table_exists($phone_calls_table)) {
            $stale_call_sids = $wpdb->get_col($wpdb->prepare(
                "SELECT call_sid
                 FROM {$phone_calls_table}
                 WHERE call_sid <> ''
                 AND (status IS NULL OR status NOT IN ('completed', 'busy', 'failed', 'canceled', 'no-answer', 'expired'))
                 AND created_at < %s
                 LIMIT 50",
                $threshold
            ));

            if (class_exists('VMO_Twilio_Manager')) {
                $twilio_manager = VMO_Twilio_Manager::get_instance();
                foreach ((array) $stale_call_sids as $stale_call_sid) {
                    $twilio_manager->end_call((string) $stale_call_sid, array(
                        'reason' => 'expired_cleanup',
                        'status' => 'expired',
                    ));
                }
            }

            $wpdb->query($wpdb->prepare(
                "UPDATE {$phone_calls_table}
                 SET status = 'expired'
                 WHERE (status IS NULL OR status NOT IN ('completed', 'busy', 'failed', 'canceled', 'no-answer', 'expired'))
                 AND created_at < %s",
                $threshold
            ));
        }
        
        // Clean up active calls array
        foreach ($this->active_calls as $call_sid => $call_data) {
            if (strtotime($call_data['start_time']) < strtotime('-1 hour')) {
                unset($this->active_calls[$call_sid]);
            }
        }
    }
}

// Initialize the realtime call manager
VMO_Realtime_Call_Manager::get_instance();
