<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use App\Modules\ThreeCX\Services\ThreeCXAuthService;
use App\Models\Setting;
use Ratchet\Client\Connector;
use Ratchet\Client\WebSocket;
use React\EventLoop\Loop;
use Exception;

/**
 * 3CX WebSocket Listener - Real WebSocket Implementation
 * 
 * Listens to actual 3CX WebSocket events for participant changes
 * Detects when participants are added/removed/updated
 */
class ThreeCXWebSocketListener extends Command
{
    protected $signature = 'threecx:listen-websocket {--debug : Enable debug output}';
    protected $description = 'Listen to real 3CX Call Control WebSocket events for participant changes';

    protected $authService;
    protected $baseUrl;
    protected $wsUrl;
    protected $debug = false;
    protected $connector;
    protected $loop;
    
    /**
     * In-memory participant state tracking
     * Key: participantId => ['status' => 'Dialing', 'call_id' => 123, ...]
     */
    protected $participantsState = [];

    public function __construct()
    {
        parent::__construct();
        $this->authService = app(\App\Modules\ThreeCX\Services\ThreeCXAuthService::class);
        $this->baseUrl = Setting::get('integrations.threecx.base_url', '');
        $this->wsUrl = str_replace(['http://', 'https://'], ['ws://', 'wss://'], $this->baseUrl) . '/callcontrol/ws';
        $this->loop = Loop::get();
        $this->connector = new Connector($this->loop);
    }

    public function handle()
    {
        $this->debug = $this->option('debug');
        
        $this->info('🚀 Starting 3CX Real WebSocket listener...');
        $this->info('📡 WebSocket URL: ' . $this->wsUrl);
        
        try {
            $token = $this->authService->getToken();
            $this->info('🔐 Got authentication token');
            
            $this->info('👂 Connecting to real WebSocket...');
            $this->connectToWebSocket($token);
            
        } catch (Exception $e) {
            $this->error('❌ Failed to start WebSocket listener: ' . $e->getMessage());
            return 1;
        }
    }

    protected function connectToWebSocket($token)
    {
        $connector = new Connector($this->loop);
        $wsUrlWithAuth = $this->wsUrl . '?access_token=' . $token;
        
        $this->info('🔌 Connecting to 3CX WebSocket with Ratchet...');
        $this->info('📡 URL: ' . $wsUrlWithAuth);
        
        // Try without Sec-WebSocket-Protocol first as per ChatGPT expert advice
        $connector($wsUrlWithAuth, [], [
            'Origin' => $this->baseUrl,
            'User-Agent' => 'Laravel-3CX-Integration/1.0'
        ])
        ->then(function (WebSocket $conn) {
            $this->info('✅ Connected to 3CX WebSocket successfully!');
            
            $conn->on('message', function ($msg) {
                $this->handleWebSocketMessage($msg->getPayload());
            });
            
            $conn->on('close', function ($code = null, $reason = null) {
                $this->error("❌ WebSocket connection closed ({$code} - {$reason})");
                $this->info('🔄 Attempting to reconnect in 5 seconds...');
                
                $this->loop->addTimer(5, function() {
                    try {
                        $newToken = $this->authService->getToken();
                        $this->connectToWebSocket($newToken);
                    } catch (Exception $e) {
                        $this->error('❌ Reconnection failed: ' . $e->getMessage());
                        $this->enhancedPollingWithStateTracking($newToken ?? null);
                    }
                });
            });
            
            $conn->on('error', function (Exception $e) {
                $this->error('❌ WebSocket error: ' . $e->getMessage());
            });
            
        }, function (Exception $e) use ($token) {
            $this->error('❌ Could not connect to WebSocket: ' . $e->getMessage());
            $this->info('🔄 Falling back to enhanced polling...');
            $this->enhancedPollingWithStateTracking($token);
        });
        
        // Start the event loop for real WebSocket connection
        $this->loop->run();
    }

    protected function handleWebSocketMessage($message)
    {
        try {
            $data = json_decode($message, true);
            
            // Always log WebSocket messages for debugging the tracking issue
            Log::debug('[3CX WebSocket] Received message', ['raw_message' => $data]);
            
            if (isset($data['event'])) {
                $event = $data['event'];
                $eventType = $event['event_type'] ?? null;
                $entity = $event['entity'] ?? '';
                
                // Parse participant information from entity URL
                if (preg_match('#/callcontrol/(\d+)/participants/(\d+)#', $entity, $matches)) {
                    $callId = $matches[1];
                    $participantId = $matches[2];
                    
                    if ($eventType === 0) { // Participant Upsert (added/updated)
                        $this->handleParticipantUpsert($callId, $participantId, $entity);
                    } elseif ($eventType === 1) { // Participant Remove
                        $this->handleParticipantRemove($callId, $participantId);
                    }
                }
            }
            
        } catch (Exception $e) {
            $this->error('❌ Error processing WebSocket message: ' . $e->getMessage());
            Log::error('[3CX WebSocket] Message processing error', [
                'error' => $e->getMessage(),
                'message' => $message
            ]);
        }
    }

    protected function handleParticipantUpsert($callId, $participantId, $entity = null)
    {
        try {
            // Check if this is one of our tracked calls
            $trackedCalls = Cache::get('threecx_active_calls', []);
            $isTrackedCall = $this->isCallTracked($callId, $trackedCalls);
            
            if (!$isTrackedCall) {
                // ChatGPT insight: Be more aggressive - track calls that might be ours
                // Check if this looks like a call from our configured extension
                $configuredExtension = config('integrations.threecx.call_using_extension', '324');
                
                // Parse the entity to see if it's from our extension
                if (preg_match('#/callcontrol/' . $configuredExtension . '/participants#', $entity ?? '')) {
                    $this->info("🎯 Participant {$participantId} on our extension {$configuredExtension} - starting to track preemptively");
                    
                    // Fetch details to see if this is a real call
                    $participantDetails = $this->fetchParticipantDetails($callId, $participantId);
                    if ($participantDetails) {
                        // This is a real call on our extension - start tracking it
                        $this->createPreemptiveCallTracking($callId, $participantId, $participantDetails);
                        $this->handleParticipantUpdate($callId, $participantId, $participantDetails);
                        return;
                    }
                }
                
                // Always log this for debugging
                $this->info("📞 Participant {$participantId} upserted for call {$callId} (not our tracked call)");
                $trackedCallsDebug = array_slice($trackedCalls, 0, 5); // Show first 5 for debugging
                $this->info("🔍 Currently tracking calls: " . json_encode($trackedCallsDebug));
                return;
            }
            
            // Fetch participant details
            $participantDetails = $this->fetchParticipantDetails($callId, $participantId);
            
            if ($participantDetails) {
                // Extract legId and create primary key
                $legId = $participantDetails['legid'] ?? $participantDetails['leg_id'] ?? '1';
                $primaryKey = "{$callId}:{$legId}";
                
                // Update the primary key tracking for this call
                $oldPrimaryKey = Cache::get("threecx_call_by_id_{$callId}");
                if ($oldPrimaryKey !== $primaryKey) {
                    $this->info("🔄 Primary key changed for call {$callId}: {$oldPrimaryKey} → {$primaryKey}");
                    Cache::put("threecx_call_by_id_{$callId}", $primaryKey, 3600);
                }
                
                // Check for participant ID migration (key insight from ChatGPT)
                $this->checkForParticipantMigration($callId, $participantId, $participantDetails, $trackedCalls);
                
                $this->handleParticipantUpdate($callId, $participantId, $participantDetails);
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error handling participant upsert: " . $e->getMessage());
        }
    }

    /**
     * Handle participant state changes with real-time tracking
     * This is your suggested implementation!
     */
    protected function handleParticipantUpdate($callId, $participantId, $details)
    {
        $currentStatus = $details['status'] ?? 'Unknown';
        $partyDn = $details['party_dn'] ?? '';
        $partyDnType = $details['party_dn_type'] ?? '';
        
        // Create state key for this participant
        $stateKey = $participantId;
        
        // First time seeing this participant
        if (!isset($this->participantsState[$stateKey])) {
            $this->participantsState[$stateKey] = [
                'status' => $currentStatus,
                'call_id' => $callId,
                'party_dn' => $partyDn,
                'party_dn_type' => $partyDnType,
                'last_update' => now()->toISOString()
            ];
            
            // Map to our UI status and update cache
            $uiStatus = $this->mapParticipantStatus($details);
            $this->updateCallCacheFromWebSocket($participantId, $uiStatus, $details, 'new_participant');
            
            $this->info("📞 NEW Participant {$participantId}: {$currentStatus} → {$uiStatus}");
            return;
        }
        
        // Check for status changes
        $oldState = $this->participantsState[$stateKey];
        $oldStatus = $oldState['status'];
        $oldPartyDnType = $oldState['party_dn_type'] ?? '';
        
        // Detect meaningful changes
        $statusChanged = ($oldStatus !== $currentStatus);
        $partyTypeChanged = ($oldPartyDnType !== $partyDnType);
        
        if ($statusChanged || $partyTypeChanged) {
            // Update our state tracking
            $this->participantsState[$stateKey] = [
                'status' => $currentStatus,
                'call_id' => $callId,
                'party_dn' => $partyDn,
                'party_dn_type' => $partyDnType,
                'last_update' => now()->toISOString(),
                'previous_status' => $oldStatus
            ];
            
            // Map to UI status
            $newUiStatus = $this->mapParticipantStatus($details);
            $oldUiStatus = $this->mapParticipantStatus($oldState);
            
            // Handle special case: participant ID might change during call progression
            $this->handleParticipantMigration($callId, $participantId, $details);
            
            // Update cache with new status
            $this->updateCallCacheFromWebSocket($participantId, $newUiStatus, $details, 'status_changed');
            
            $this->info("🔄 Participant {$participantId} CHANGED: {$oldStatus} → {$currentStatus} (UI: {$oldUiStatus} → {$newUiStatus})");
            
            if ($partyTypeChanged) {
                $this->info("📞 Party type changed: {$oldPartyDnType} → {$partyDnType} (Extension answered/dialing customer)");
            }
        } else {
            if ($this->debug) {
                $this->info("📞 Participant {$participantId}: No change ({$currentStatus})");
            }
        }
    }
    
    /**
     * Handle potential participant ID migration during call progression
     */
    protected function handleParticipantMigration($callId, $newParticipantId, $details)
    {
        $trackedCalls = Cache::get('threecx_active_calls', []);
        
        // Look for existing tracked calls with same call ID but different participant ID
        foreach ($trackedCalls as $trackedParticipantId) {
            if ($trackedParticipantId == $newParticipantId) {
                continue; // Same participant
            }
            
            $callInfo = Cache::get("threecx_call_{$trackedParticipantId}");
            if ($callInfo && isset($callInfo['call_id']) && $callInfo['call_id'] == $callId) {
                // Found a tracked call with same call ID but different participant ID
                $this->info("🔄 Migrating tracking: {$trackedParticipantId} → {$newParticipantId} (Call ID: {$callId})");
                $this->migrateParticipantTracking($trackedParticipantId, $newParticipantId, $details);
                break;
            }
        }
    }
    
    /**
     * Check if a call is being tracked by our system
     */
    protected function isCallTracked($callId, $trackedCalls)
    {
        foreach ($trackedCalls as $participantId) {
            $callInfo = Cache::get("threecx_call_{$participantId}");
            if ($callInfo && isset($callInfo['call_id']) && $callInfo['call_id'] == $callId) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Update call cache from WebSocket data
     */
    protected function updateCallCacheFromWebSocket($participantId, $uiStatus, $details, $updateType)
    {
        $callInfo = Cache::get("threecx_call_{$participantId}");
        
        if ($callInfo) {
            $callInfo['status'] = $uiStatus;
            $callInfo['last_update'] = now()->toISOString();
            $callInfo['event_data'] = $details;
            $callInfo['data_source'] = 'websocket_realtime';
            $callInfo['update_type'] = $updateType;
            
            // Track connection time
            if ($uiStatus === 'connected' && !isset($callInfo['connected_at'])) {
                $callInfo['connected_at'] = now()->toISOString();
            }
            
            // Track end time
            if (in_array($uiStatus, ['ended', 'failed', 'cancelled'])) {
                $callInfo['ended_at'] = now()->toISOString();
                
                // Calculate duration if was connected
                if (isset($callInfo['connected_at'])) {
                    $connectedTime = \Carbon\Carbon::parse($callInfo['connected_at']);
                    $endedTime = \Carbon\Carbon::parse($callInfo['ended_at']);
                    $callInfo['duration'] = $connectedTime->diffInSeconds($endedTime);
                }
            }
            
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600);
            
            Log::info('[3CX WebSocket] Real-time status update', [
                'participant_id' => $participantId,
                'ui_status' => $uiStatus,
                'update_type' => $updateType,
                'raw_status' => $details['status'] ?? 'unknown',
                'data_source' => 'websocket_realtime'
            ]);
        }
    }

    protected function handleParticipantRemove($callId, $participantId)
    {
        try {
            // Remove from our state tracking
            unset($this->participantsState[$participantId]);
            
            // Check if we were tracking this participant
            $callInfo = Cache::get("threecx_call_{$participantId}");
            
            if ($callInfo) {
                $this->info("📞 Participant {$participantId} REMOVED from call {$callId}");
                
                // ChatGPT insight: Check if there's a newer participant for the same callId using primary key tracking
                $currentPrimaryKey = Cache::get("threecx_call_by_id_{$callId}");
                $hasOtherParticipantsForSameCall = false;
                
                if ($currentPrimaryKey) {
                    $currentCallInfo = Cache::get("threecx_call_primary_{$currentPrimaryKey}");
                    
                    if ($currentCallInfo && $currentCallInfo['participant_id'] !== $participantId) {
                        // There's a newer participant for this call - this is normal migration
                        $hasOtherParticipantsForSameCall = true;
                        $this->info("🔍 Found newer participant {$currentCallInfo['participant_id']} for call {$callId} (primary key: {$currentPrimaryKey}) - NOT marking as ended");
                    }
                }
                
                // Fallback: Check active calls list for other participants with same callId
                if (!$hasOtherParticipantsForSameCall) {
                    $activeCalls = Cache::get('threecx_active_calls', []);
                    foreach ($activeCalls as $activeParticipantId) {
                        if ($activeParticipantId != $participantId) {
                            $otherCallInfo = Cache::get("threecx_call_{$activeParticipantId}");
                            if ($otherCallInfo && isset($otherCallInfo['call_id']) && $otherCallInfo['call_id'] == $callId) {
                                $hasOtherParticipantsForSameCall = true;
                                $this->info("🔍 Found other participant {$activeParticipantId} for same call {$callId} via active calls - NOT marking as ended");
                                break;
                            }
                        }
                    }
                }
                
                if ($hasOtherParticipantsForSameCall) {
                    // Don't mark as ended - there are other participants for this call
                    $this->info("✅ Call {$callId} continues with other participants");
                } else {
                    // No other participants for this call - mark as ended
                    $this->info("📞 Last participant {$participantId} removed - marking call {$callId} as ended");
                    
                    $this->updateCallCacheFromWebSocket($participantId, 'ended', [
                        'status' => 'Ended',
                        'reason' => 'participant_removed'
                    ], 'participant_removed');
                }
                
                // Remove this participant from active calls list
                $activeCalls = array_filter($activeCalls, function($id) use ($participantId) {
                    return $id != $participantId;
                });
                Cache::put('threecx_active_calls', array_values($activeCalls), 3600);
                
            } else {
                // Check if this participant belongs to one of our tracked calls
                $trackedCalls = Cache::get('threecx_active_calls', []);
                foreach ($trackedCalls as $trackedParticipantId) {
                    $trackedCallInfo = Cache::get("threecx_call_{$trackedParticipantId}");
                    if ($trackedCallInfo && isset($trackedCallInfo['call_id']) && $trackedCallInfo['call_id'] == $callId) {
                        $this->info("📞 Related participant {$participantId} removed from tracked call {$trackedParticipantId} (Call ID: {$callId})");
                        
                        // This might be the end of the call for our tracked participant
                        // Let's check if there are other participants left for this call
                        $this->checkCallEndCondition($trackedParticipantId, $callId);
                        break;
                    }
                }
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error handling participant remove: " . $e->getMessage());
        }
    }

    /**
     * Check if a call should be marked as ended when participants are removed
     */
    protected function checkCallEndCondition($trackedParticipantId, $callId)
    {
        // Count remaining participants for this call ID
        $remainingParticipants = 0;
        foreach ($this->participantsState as $pid => $state) {
            if ($state['call_id'] == $callId) {
                $remainingParticipants++;
            }
        }
        
        if ($remainingParticipants === 0) {
            $this->info("📞 No participants left for Call ID {$callId} - ending tracked call {$trackedParticipantId}");
            $this->updateCallCacheFromWebSocket($trackedParticipantId, 'ended', [
                'status' => 'Ended',
                'reason' => 'all_participants_removed'
            ], 'call_ended');
        } else {
            $this->info("📞 {$remainingParticipants} participants still active for Call ID {$callId}");
        }
    }

    protected function migrateParticipantTracking($oldParticipantId, $newParticipantId, $participantDetails)
    {
        // Get old call info
        $oldCallInfo = Cache::get("threecx_call_{$oldParticipantId}");
        
        if ($oldCallInfo) {
            // Update participant ID and status
            $oldCallInfo['participant_id'] = $newParticipantId;
            $oldCallInfo['last_update'] = now()->toISOString();
            $oldCallInfo['event_data'] = $participantDetails;
            $oldCallInfo['data_source'] = 'websocket_realtime';
            
            // Map new status
            $newStatus = $this->mapParticipantStatus($participantDetails);
            $oldCallInfo['status'] = $newStatus;
            
            // Store under new participant ID
            Cache::put("threecx_call_{$newParticipantId}", $oldCallInfo, 3600);
            
            // Update active calls list
            $activeCalls = Cache::get('threecx_active_calls', []);
            $activeCalls = array_map(function($id) use ($oldParticipantId, $newParticipantId) {
                return $id == $oldParticipantId ? $newParticipantId : $id;
            }, $activeCalls);
            Cache::put('threecx_active_calls', $activeCalls, 3600);
            
            // Remove old participant cache
            Cache::forget("threecx_call_{$oldParticipantId}");
            
            Log::info('[3CX WebSocket] Participant tracking migrated', [
                'old_participant_id' => $oldParticipantId,
                'new_participant_id' => $newParticipantId,
                'new_status' => $newStatus,
                'call_id' => $oldCallInfo['call_id'] ?? 'unknown'
            ]);
        }
    }

    protected function updateParticipantStatus($participantId, $participantDetails)
    {
        $newStatus = $this->mapParticipantStatus($participantDetails);
        
        $callInfo = Cache::get("threecx_call_{$participantId}");
        if ($callInfo) {
            $callInfo['status'] = $newStatus;
            $callInfo['last_update'] = now()->toISOString();
            $callInfo['event_data'] = $participantDetails;
            $callInfo['data_source'] = 'websocket_realtime';
            
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600);
            
            Log::info('[3CX WebSocket] Participant status updated', [
                'participant_id' => $participantId,
                'status' => $newStatus,
                'data_source' => 'websocket_realtime'
            ]);
        }
    }

    protected function mapParticipantStatus($participantDetails)
    {
        $status = $participantDetails['status'] ?? 'Unknown';
        $partyDn = $participantDetails['party_dn'] ?? '';
        $partyDnType = $participantDetails['party_dn_type'] ?? '';
        
        // Map to our UI statuses
        if (in_array($status, ['Talking', 'Connected', 'InCall', 'Established'])) {
            return 'connected';
        }
        
        if (in_array($status, ['Ended', 'Completed', 'Disconnected', 'Finished'])) {
            return 'ended';
        }
        
        if ($status === 'Dialing') {
            if ($partyDnType === 'Wexternalline' && !empty($partyDn)) {
                return 'dialing_customer';
            }
            return 'dialing_reception';
        }
        
        if ($status === 'Ringing') {
            if ($partyDnType === 'Wexternalline' && !empty($partyDn)) {
                return 'ringing_customer';
            }
            return 'ringing_reception';
        }
        
        return strtolower($status);
    }

    /**
     * Enhanced polling that tracks participant state changes like WebSocket
     */
    protected function enhancedPollingWithStateTracking($token)
    {
        $this->info('🔍 Starting enhanced polling with real-time state detection...');
        
        while (true) {
            try {
                $this->pollAndDetectChanges($token);
                
                if ($this->debug) {
                    $activeCalls = Cache::get('threecx_active_calls', []);
                    $stateCount = count($this->participantsState);
                    $this->info("👂 Polling... Active calls: " . count($activeCalls) . ", Tracked states: {$stateCount}");
                }
                
                sleep(1); // Fast polling for real-time feel
                
            } catch (Exception $e) {
                $this->error('❌ Error in enhanced polling: ' . $e->getMessage());
                sleep(3);
                
                // Try to get fresh token
                try {
                    $token = $this->authService->getToken();
                } catch (Exception $authError) {
                    $this->error('❌ Failed to refresh token: ' . $authError->getMessage());
                }
            }
        }
    }

    /**
     * Poll call control and detect participant changes
     */
    protected function pollAndDetectChanges($token)
    {
        try {
            $callControlUrl = rtrim($this->baseUrl, '/') . '/callcontrol/';
            
            $response = Http::withToken($token)
                ->withOptions(['verify' => false])
                ->timeout(10)
                ->accept('application/json')
                ->get($callControlUrl);
                
            if (!$response->successful()) {
                if ($this->debug) {
                    $this->warn('⚠️ Call control API request failed: ' . $response->status());
                }
                return;
            }
            
            $callControlData = $response->json();
            $currentParticipants = [];
            
            // Collect all current participants
            foreach ($callControlData as $extension) {
                $participants = $extension['participants'] ?? [];
                
                foreach ($participants as $participant) {
                    $participantId = $participant['id'] ?? null;
                    $callId = $participant['callid'] ?? null;
                    
                    if ($participantId && $callId) {
                        $currentParticipants[$participantId] = $participant;
                        
                        // Check if this is a tracked call
                        if ($this->isCallTracked($callId, Cache::get('threecx_active_calls', []))) {
                            // Simulate WebSocket upsert event
                            $this->handleParticipantUpdate($callId, $participantId, $participant);
                        }
                    }
                }
            }
            
            // Detect removed participants (simulate WebSocket remove events)
            $this->detectRemovedParticipants($currentParticipants);
            
        } catch (Exception $e) {
            $this->error('❌ Error in polling and detection: ' . $e->getMessage());
        }
    }

    /**
     * Detect participants that were removed since last poll
     */
    protected function detectRemovedParticipants($currentParticipants)
    {
        $currentParticipantIds = array_keys($currentParticipants);
        
        // Check our tracked states for participants that are no longer present
        foreach ($this->participantsState as $participantId => $state) {
            if (!in_array($participantId, $currentParticipantIds)) {
                $callId = $state['call_id'];
                $this->info("📞 Detected REMOVED participant {$participantId} (Call ID: {$callId})");
                
                // Simulate WebSocket remove event
                $this->handleParticipantRemove($callId, $participantId);
            }
        }
    }

    protected function fallbackToPolling($token)
    {
        // This is now the same as enhanced polling
        $this->enhancedPollingWithStateTracking($token);
    }

    protected function fetchParticipantDetails($callId, $participantId)
    {
        try {
            $token = $this->authService->getToken();
            $participantUrl = rtrim($this->baseUrl, '/') . "/callcontrol/{$callId}/participants/{$participantId}";
            
            $response = Http::withToken($token)
                ->withOptions(['verify' => false])
                ->timeout(10)
                ->accept('application/json')
                ->get($participantUrl);
                
            if ($response->successful()) {
                return $response->json();
            } else {
                Log::warning('[3CX WebSocket] Failed to fetch participant details', [
                    'call_id' => $callId,
                    'participant_id' => $participantId,
                    'status' => $response->status()
                ]);
                return null;
            }
            
        } catch (Exception $e) {
            Log::error('[3CX WebSocket] Error fetching participant details', [
                'call_id' => $callId,
                'participant_id' => $participantId,
                'error' => $e->getMessage()
            ]);
            return null;
        }
    }

    protected function updateCallStatus($participantId, $status, $participantData)
    {
        $callInfo = Cache::get("threecx_call_{$participantId}");
        
        if ($callInfo) {
            $callInfo['status'] = $status;
            $callInfo['last_update'] = now()->toISOString();
            $callInfo['event_data'] = $participantData;
            $callInfo['data_source'] = 'websocket_realtime';
            
            // Track connection time
            if ($status === 'connected' && !isset($callInfo['connected_at'])) {
                $callInfo['connected_at'] = now()->toISOString();
            }
            
            // Track end time and remove from active calls
            if (in_array($status, ['ended', 'failed', 'cancelled'])) {
                $callInfo['ended_at'] = now()->toISOString();
                
                // Calculate duration if was connected
                if (isset($callInfo['connected_at'])) {
                    $connectedTime = \Carbon\Carbon::parse($callInfo['connected_at']);
                    $endedTime = \Carbon\Carbon::parse($callInfo['ended_at']);
                    $callInfo['duration'] = $connectedTime->diffInSeconds($endedTime);
                }
                
                // Remove from active calls list
                $activeCalls = Cache::get('threecx_active_calls', []);
                $activeCalls = array_filter($activeCalls, function($id) use ($participantId) {
                    return $id != $participantId;
                });
                Cache::put('threecx_active_calls', array_values($activeCalls), 3600);
            }
            
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600);
            
            Log::info('[3CX WebSocket] Real-time call status updated', [
                'participant_id' => $participantId,
                'status' => $status,
                'data_source' => 'websocket_realtime'
            ]);
        }
    }

    /**
     * Check all active calls using the /callcontrol/ endpoint (fallback)
     */
    protected function checkAllActiveCalls($token)
    {
        try {
            $callControlUrl = rtrim($this->baseUrl, '/') . '/callcontrol/';
            
            $response = Http::withToken($token)
                ->withOptions(['verify' => false])
                ->timeout(10)
                ->accept('application/json')
                ->get($callControlUrl);
                
            if (!$response->successful()) {
                if ($this->debug) {
                    $this->warn('⚠️ Call control API request failed: ' . $response->status());
                }
                return;
            }
            
            $callControlData = $response->json();
            
            // Process each extension's participants for our tracked calls
            foreach ($callControlData as $extension) {
                $participants = $extension['participants'] ?? [];
                
                foreach ($participants as $participant) {
                    $participantId = $participant['id'] ?? null;
                    
                    if ($participantId) {
                        // Check if we're tracking this call
                        $callInfo = Cache::get("threecx_call_{$participantId}");
                        if ($callInfo) {
                            $newStatus = $this->mapParticipantStatus($participant);
                            $this->updateParticipantStatus($participantId, $participant);
                        }
                    }
                }
            }
            
        } catch (Exception $e) {
            $this->error('❌ Error in fallback polling: ' . $e->getMessage());
        }
    }

    /**
     * Check for participant ID migration based on call context
     * Key insight from ChatGPT: Use callId correlation instead of participant ID tracking
     */
    protected function checkForParticipantMigration($callId, $newParticipantId, $participantDetails, $trackedCalls)
    {
        try {
            // ChatGPT insight: Use callid:legid as primary key, not participant ID
            $newLegId = $participantDetails['legid'] ?? 1;
            $newCallKey = "{$callId}:{$newLegId}";
            
            $this->info("🔍 Checking migration for call key: {$newCallKey}, participant: {$newParticipantId}");
            
            // Look for existing tracking entries for the same callId but different participant ID
            foreach ($trackedCalls as $existingParticipantId) {
                $existingCallInfo = Cache::get("threecx_call_status_{$existingParticipantId}");
                
                if ($existingCallInfo && 
                    isset($existingCallInfo['call_id']) && 
                    $existingCallInfo['call_id'] == $callId && 
                    $existingParticipantId != $newParticipantId) {
                    
                    // Participant ID migration detected!
                    $this->info("🔄 PARTICIPANT MIGRATION DETECTED!");
                    $this->info("   Call ID: {$callId}");
                    $this->info("   Old participant: {$existingParticipantId} → New participant: {$newParticipantId}");
                    $this->info("   Call Key: {$newCallKey}");
                    
                    // Migrate the tracking to the new participant ID
                    $this->migrateParticipantTracking($existingParticipantId, $newParticipantId, $participantDetails);
                    
                    // Update the active calls list
                    $updatedTrackedCalls = array_diff($trackedCalls, [$existingParticipantId]);
                    $updatedTrackedCalls[] = $newParticipantId;
                    Cache::put('threecx_active_calls', array_values($updatedTrackedCalls), now()->addHours(1));
                    
                    break;
                }
            }
        } catch (Exception $e) {
            $this->error("❌ Error checking participant migration: " . $e->getMessage());
        }
    }

    /**
     * Create preemptive call tracking when WebSocket detects calls on our extension
     * This solves the timing issue where WebSocket events arrive before frontend registration
     */
    protected function createPreemptiveCallTracking($callId, $participantId, $participantDetails)
    {
        try {
            // Create call info similar to ThreeCXCallTrackingService::startMonitoring
            $phoneNumber = $participantDetails['party_caller_id'] ?? 'Unknown';
            $extension = $participantDetails['dn'] ?? '324';
            
            // Extract legId for callId:legId primary key (ChatGPT recommendation)
            $legId = $participantDetails['legid'] ?? $participantDetails['leg_id'] ?? '1';
            $primaryKey = "{$callId}:{$legId}";
            
            $callInfo = [
                'participant_id' => $participantId,
                'call_id' => $callId,
                'leg_id' => $legId,
                'primary_key' => $primaryKey,
                'phone_number' => $phoneNumber,
                'extension' => $extension,
                'status' => 'dialing_reception',
                'started_at' => now()->toISOString(),
                'last_update' => now()->toISOString(),
                'event_data' => $participantDetails,
                'data_source' => 'websocket_preemptive'
            ];
            
            // Store using BOTH keys for compatibility and migration tracking
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600);      // Old key for compatibility
            Cache::put("threecx_call_primary_{$primaryKey}", $callInfo, 3600); // New primary key
            Cache::put("threecx_call_by_id_{$callId}", $primaryKey, 3600);     // Track by callId for migration
            
            // Add to active calls list
            $activeCalls = Cache::get('threecx_active_calls', []);
            if (!in_array($participantId, $activeCalls)) {
                $activeCalls[] = $participantId;
                Cache::put('threecx_active_calls', $activeCalls, 3600);
            }
            
            $this->info("✅ Created preemptive tracking for call {$callId}:{$legId}, participant {$participantId}");
            
        } catch (Exception $e) {
            $this->error("❌ Error creating preemptive call tracking: " . $e->getMessage());
        }
    }
}
