<?php

namespace App\Console\Commands;

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

class ThreeCXWebSocketRunner extends Command
{
    protected $signature = 'threecx:ws-run {--debug : Enable debug output}';
    protected $description = 'Run 3CX WebSocket listener (internal use - managed by threecx:ws)';

    protected $authService;
    protected $baseUrl;
    protected $wsUrl;
    protected $loop;
    protected $connector;
    protected $participantsState = [];
    protected $debug = false;

    public function __construct(ThreeCXAuthService $authService)
    {
        parent::__construct();
        
        $this->authService = $authService;
        $this->baseUrl = rtrim(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();
            if (!$token) {
                $this->error("❌ Failed to get authentication token");
                return 1;
            }

            $this->info("🔐 Got authentication token");
            $this->connectToWebSocket($token);

        } catch (Exception $e) {
            $this->error("❌ Failed to start WebSocket listener: " . $e->getMessage());
            return 1;
        }

        return 0;
    }

    protected function connectToWebSocket($token)
    {
        $this->info("👂 Connecting to real WebSocket...");
        
        $wsUrlWithAuth = $this->wsUrl . '?access_token=' . $token;
        
        $this->info("🔌 Connecting to 3CX WebSocket with Ratchet...");
        $this->info("📡 URL: {$wsUrlWithAuth}");

        // Use ChatGPT recommendation: no Sec-WebSocket-Protocol header
        $this->connector->__invoke($wsUrlWithAuth, [], [
            'Origin' => $this->baseUrl,
            'User-Agent' => 'Laravel-3CX-Integration/1.0'
        ])
        ->then(function ($conn) use ($token) {
            $this->info("✅ Connected to 3CX WebSocket successfully!");
            
            $conn->on('message', function ($msg) {
                $this->handleWebSocketMessage($msg->getPayload());
            });

            $conn->on('close', function ($code = null, $reason = null) use ($token) {
                $this->warn("❌ WebSocket connection closed. Code: {$code}, Reason: {$reason}");
                $this->info("🔄 Falling back to enhanced polling instead of reconnecting...");
                
                // Fall back to polling instead of endless reconnection attempts
                $this->enhancedPollingWithStateTracking($token);
            });

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

        $this->loop->run();
    }

    protected function handleWebSocketMessage($message)
    {
        try {
            // Always log WebSocket messages for debugging
            Log::debug('[3CX WebSocket] Received message', ['raw_message' => $message]);
            
            $data = json_decode($message, true);
            
            if (isset($data['event']) && isset($data['event']['entity'])) {
                $entity = $data['event']['entity'];
                $eventType = $data['event']['event_type'] ?? null;
                
                Log::info('[3CX WebSocket] Processing event', [
                    'entity' => $entity,
                    'event_type' => $eventType
                ]);
                
                if (preg_match('#/callcontrol/(\d+)/participants/(\d+)#', $entity, $matches)) {
                    $callId = $matches[1];
                    $participantId = $matches[2];
                    
                    Log::info('[3CX WebSocket] Extracted IDs', [
                        'call_id' => $callId,
                        'participant_id' => $participantId,
                        'event_type' => $eventType
                    ]);
                    
                    if ($eventType === 0) { // Participant Upsert (added/updated)
                        Log::info('[3CX WebSocket] Handling participant upsert', ['participant_id' => $participantId]);
                        $this->handleParticipantUpsert($callId, $participantId, $entity);
                    } elseif ($eventType === 1) { // Participant Remove
                        Log::info('[3CX WebSocket] Handling participant remove', ['participant_id' => $participantId]);
                        $this->handleParticipantRemove($callId, $participantId);
                    }
                } else {
                    Log::warning('[3CX WebSocket] Entity regex did not match', ['entity' => $entity]);
                }
            } else {
                Log::warning('[3CX WebSocket] Missing event or entity', ['data' => $data]);
            }
            
        } 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 {
            // Get list of currently 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
                $configuredExtension = config('integrations.threecx.call_using_extension', '324');
                
                // Parse the entity to see if it's from our extension
                if (preg_match('#/callcontrol/(\d+)/participants/(\d+)#', $entity, $matches)) {
                    // Fetch details to check if this is on our extension
                    $participantDetails = $this->fetchParticipantDetails($callId, $participantId);
                    
                    if ($participantDetails && isset($participantDetails['dn']) && $participantDetails['dn'] == $configuredExtension) {
                        $this->info("🎯 Detected call on our extension {$configuredExtension} - starting preemptive tracking");
                        $this->createPreemptiveCallTracking($callId, $participantId, $participantDetails);
                        $isTrackedCall = true;
                    }
                }
                
                // ENHANCED: Check if this might be a call transition (extension answered, now calling customer)
                if (!$isTrackedCall) {
                    // Look for any active calls with the same call ID but different participant
                    $activeCalls = Cache::get('threecx_active_calls', []);
                    foreach ($activeCalls as $activeParticipantId) {
                        $activeCallInfo = Cache::get("threecx_call_{$activeParticipantId}");
                        if ($activeCallInfo && isset($activeCallInfo['call_id']) && $activeCallInfo['call_id'] == $callId) {
                            $this->info("🔄 Detected call transition - participant {$activeParticipantId} → {$participantId} for call {$callId}");
                            
                            // Fetch new participant details
                            $participantDetails = $this->fetchParticipantDetails($callId, $participantId);
                            if ($participantDetails) {
                                Log::info('[3CX WebSocket] Call transition detected', [
                                    'old_participant' => $activeParticipantId,
                                    'new_participant' => $participantId,
                                    'call_id' => $callId,
                                    'new_participant_details' => $participantDetails
                                ]);
                                
                                // Transfer tracking from old participant to new one
                                $this->transferCallTracking($activeParticipantId, $participantId, $participantDetails);
                                $isTrackedCall = true;
                            }
                            break;
                        }
                    }
                }
                
                if (!$isTrackedCall) {
                    $this->info("📞 Participant {$participantId} upserted for call {$callId} (not our tracked call)");
                    $trackedCallsDebug = array_slice($trackedCalls, 0, 5);
                    $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);
                }
                
                $this->handleParticipantUpdate($callId, $participantId, $participantDetails);
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error handling participant upsert: " . $e->getMessage());
        }
    }

    protected function handleParticipantUpdate($callId, $participantId, $details)
    {
        try {
            // Track state changes
            $currentState = $this->participantsState[$participantId] ?? null;
            $newStatus = $details['status'] ?? 'Unknown';
            $newPartyDnType = $details['party_dn_type'] ?? null;
            
            $isNewParticipant = $currentState === null;
            $statusChanged = !$isNewParticipant && ($currentState['status'] ?? null) !== $newStatus;
            $partyDnTypeChanged = !$isNewParticipant && ($currentState['party_dn_type'] ?? null) !== $newPartyDnType;
            
            if ($isNewParticipant || $statusChanged || $partyDnTypeChanged) {
                // Update our state tracking
                $this->participantsState[$participantId] = [
                    'status' => $newStatus,
                    'party_dn_type' => $newPartyDnType,
                    'last_update' => now()
                ];
                
                if ($isNewParticipant) {
                    $this->info("📞 Participant {$participantId} NEW - Status: {$newStatus}");
                    Log::info('[3CX WebSocket] New participant details', [
                        'participant_id' => $participantId,
                        'call_id' => $callId,
                        'details' => $details
                    ]);
                } else {
                    $changeDescription = [];
                    if ($statusChanged) $changeDescription[] = "status: {$newStatus}";
                    if ($partyDnTypeChanged) $changeDescription[] = "party_dn_type: {$newPartyDnType}";
                    
                    $this->info("📞 Participant {$participantId} CHANGED - " . implode(', ', $changeDescription));
                }
                
                // Map to UI status and update cache
                $uiStatus = $this->mapParticipantStatus($details);
                $this->updateCallCacheFromWebSocket($participantId, $uiStatus, $details, 'participant_update');
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error updating participant: " . $e->getMessage());
        }
    }

    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}");
                
                // NEW APPROACH: Use stable call ID tracking instead of participant migration
                $realCallId = $callInfo['event_data']['callid'] ?? null;
                $hasOtherParticipantsForSameCall = false;
                
                $this->info("🔍 STABLE: Checking active calls for real callId {$realCallId} (extension call {$callId})");
                
                if ($realCallId) {
                    // Check if there are any active participants with the same real callId
                    $activeCalls = Cache::get('threecx_active_calls', []);
                    $activeParticipantsForSameCall = [];
                    
                    foreach ($activeCalls as $activeParticipantId) {
                        if ($activeParticipantId == $participantId) continue; // Skip the one being removed
                        
                        $activeCallInfo = Cache::get("threecx_call_{$activeParticipantId}");
                        if ($activeCallInfo && isset($activeCallInfo['event_data']['callid']) && 
                            $activeCallInfo['event_data']['callid'] == $realCallId) {
                            $activeParticipantsForSameCall[] = $activeParticipantId;
                        }
                    }
                    
                    if (!empty($activeParticipantsForSameCall)) {
                        $hasOtherParticipantsForSameCall = true;
                        $this->info("🔍 STABLE: Found " . count($activeParticipantsForSameCall) . " active participants for callId {$realCallId}: " . implode(', ', $activeParticipantsForSameCall) . " - NOT marking as ended");
                    } else {
                        $this->info("🔍 STABLE: No other active participants found for callId {$realCallId} - call truly ended");
                    }
                } else {
                    $this->info("🔍 STABLE: No real callId found in event data - defaulting to ended");
                }
                
                if ($hasOtherParticipantsForSameCall) {
                    // Don't mark as ended - there are other participants for this call
                    $this->info("✅ Call {$callId} continues with other participants");
                    
                    // DON'T delete the old participant cache if it has migration info
                    $shouldKeepCache = false;
                    $oldCallInfo = Cache::get("threecx_call_{$participantId}");
                    if ($oldCallInfo && isset($oldCallInfo['migrated_to'])) {
                        $shouldKeepCache = true;
                        $this->info("🔄 Keeping cache for migrated participant {$participantId} → {$oldCallInfo['migrated_to']}");
                    } else {
                        Cache::forget("threecx_call_{$participantId}");
                        $this->info("🗑️ Cleaned up old participant cache: {$participantId}");
                    }
                    
                    $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);
                    
                } else {
                    // No other participants for this call - mark as ended
                    $this->info("📞 Last participant {$participantId} removed - marking call {$callId} as ended");
                    
                    // Determine rejection reason based on previous status
                    $previousStatus = $callInfo['status'] ?? 'unknown';
                    $rejectionReason = $this->determineRejectionReason($previousStatus, $callInfo);
                    
                    $this->info("📞 Call ended - Previous status: {$previousStatus}, Reason: {$rejectionReason}");
                    
                    $this->updateCallCacheFromWebSocket($participantId, 'ended', [
                        'status' => 'Ended',
                        'reason' => $rejectionReason
                    ], $rejectionReason);
                    
                    // Don't immediately clean up cache - let frontend read the rejection reason first
                    // Cache will be cleaned up by TTL or next call
                    $this->info("📞 Cache preserved for frontend to read rejection reason");
                    Cache::forget("threecx_call_by_id_{$callId}");
                    if (isset($callInfo['primary_key'])) {
                        Cache::forget("threecx_call_primary_{$callInfo['primary_key']}");
                    }
                    
                    $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);
                    
                    // Auto-stop WebSocket if no more active calls
                    if (count($activeCalls) === 0) {
                        $this->info("🛑 No more active calls - auto-stopping WebSocket");
                        
                        // Trigger auto-logging for any remaining calls before shutdown
                        $this->autoLogRemainingCallsOnShutdown();
                        
                        $this->autoStopWebSocket();
                    }
                }
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error handling participant remove: " . $e->getMessage());
        }
    }

    /**
     * Finalize task call logs before WebSocket shutdown
     * Uses the new task-based finalization API endpoint
     */
    protected function autoLogRemainingCallsOnShutdown()
    {
        try {
            $this->info("📝 Finalizing task call logs before WebSocket shutdown...");
            
            // Find task IDs from any recent call tracking data
            $taskIds = [];
            
            // Get all active calls from cache first
            $activeCalls = \Cache::get('threecx_active_calls', []);
            $this->info("🔍 Checking active calls for task finalization: " . implode(', ', $activeCalls));
            
            // Check all active participant IDs
            foreach ($activeCalls as $participantId) {
                $callInfo = \Cache::get("threecx_call_{$participantId}");
                if ($callInfo && !empty($callInfo['task_id'])) {
                    $taskId = $callInfo['task_id'];
                    if (!in_array($taskId, $taskIds)) {
                        $taskIds[] = $taskId;
                        $this->info("📋 Found task ID {$taskId} for finalization (participant {$participantId})");
                    }
                }
            }
            
            // Also check for any recently cached call tracking data in a reasonable range
            if (!empty($activeCalls)) {
                $this->info("🔍 Checking recent call tracking cache for additional tasks...");
                $maxParticipant = max($activeCalls);
                $recentMin = max(1, $maxParticipant - 100); // Check 100 IDs before the highest active call
                $recentMax = $maxParticipant + 10; // Check 10 IDs after the highest active call
                
                for ($i = $recentMin; $i <= $recentMax; $i++) {
                    $trackingData = \Cache::get("threecx_call_tracking_{$i}");
                    if ($trackingData && !empty($trackingData['task_id'])) {
                        $taskId = $trackingData['task_id'];
                        if (!in_array($taskId, $taskIds)) {
                            $taskIds[] = $taskId;
                            $this->info("📋 Found task ID {$taskId} for finalization (tracking participant {$i})");
                        }
                    }
                }
            } else {
                // If no active calls, check a broad range of recent participant IDs
                $this->info("🔍 No active calls found, checking broad range for recent call tracking...");
                
                // Get the highest participant ID from recent call logs to establish a reasonable range
                $recentCallLog = \App\Modules\ThreeCX\Models\TaskCallLog::orderBy('called_at', 'desc')->first();
                if ($recentCallLog && isset($recentCallLog->metadata['participant_id'])) {
                    $lastParticipantId = $recentCallLog->metadata['participant_id'];
                    $recentMin = max(1, $lastParticipantId - 50);
                    $recentMax = $lastParticipantId + 50;
                    
                    for ($i = $recentMin; $i <= $recentMax; $i++) {
                        $trackingData = \Cache::get("threecx_call_tracking_{$i}");
                        if ($trackingData && !empty($trackingData['task_id'])) {
                            $taskId = $trackingData['task_id'];
                            if (!in_array($taskId, $taskIds)) {
                                $taskIds[] = $taskId;
                                $this->info("📋 Found task ID {$taskId} for finalization (tracking participant {$i})");
                            }
                        }
                    }
                }
            }
            
            if (empty($taskIds)) {
                $this->info("ℹ️  No task calls found in recent cache - skipping finalization");
                return;
            }
            
            // Call the finalization API for each unique task
            $totalUpdated = 0;
            foreach ($taskIds as $taskId) {
                try {
                    $this->info("🔄 Finalizing call logs for task {$taskId}...");
                    
                    // Use the controller directly instead of HTTP request to avoid networking issues
                    $controller = app(\App\Modules\ThreeCX\Controllers\ThreeCXController::class);
                    $response = $controller->finalizeTaskCallLogs($taskId);
                    $data = $response->getData();
                    
                    if ($data->success) {
                        $updatedCount = $data->updated_count ?? 0;
                        $totalUpdated += $updatedCount;
                        
                        $this->info("✅ Task {$taskId}: {$updatedCount} call logs finalized - {$data->message}");
                    } else {
                        $this->error("❌ Failed to finalize task {$taskId}: {$data->message}");
                    }
                    
                } catch (\Exception $e) {
                    $this->error("❌ Error finalizing task {$taskId}: " . $e->getMessage());
                    $this->error("❌ Stack trace: " . $e->getTraceAsString());
                }
            }
            
            $this->info("✅ Finalization completed: {$totalUpdated} total call logs updated across " . count($taskIds) . " tasks");
            
        } catch (\Exception $e) {
            $this->error("❌ Error during finalization on shutdown: " . $e->getMessage());
        }
    }

    protected function createPreemptiveCallTracking($callId, $participantId, $participantDetails)
    {
        try {
            // Extract legId for callId:legId primary key (ChatGPT recommendation)
            $legId = $participantDetails['legid'] ?? $participantDetails['leg_id'] ?? '1';
            $primaryKey = "{$callId}:{$legId}";
            
            $phoneNumber = $participantDetails['party_caller_id'] ?? 'Unknown';
            $extension = $participantDetails['dn'] ?? '324';
            
            // Try to inherit task context from existing participants for the same call
            $taskId = null;
            $callLogId = null;
            $userId = null;
            
            // Look for existing tracking data for this call to inherit context
            $activeCalls = Cache::get('threecx_active_calls', []);
            foreach ($activeCalls as $existingParticipantId) {
                $existingTracking = Cache::get("threecx_call_tracking_{$existingParticipantId}");
                if ($existingTracking && 
                    isset($existingTracking['call_id']) && 
                    $existingTracking['call_id'] == $participantDetails['callid']) {
                    
                    // Found existing tracking for same call - inherit context
                    $taskId = $existingTracking['task_id'] ?? null;
                    $callLogId = $existingTracking['call_log_id'] ?? null;
                    $userId = $existingTracking['user_id'] ?? null;
                    
                    $this->info("🔄 Inherited task context for participant {$participantId}: task_id={$taskId}, call_log_id={$callLogId}");
                    break;
                }
            }
            
            $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'
            ];
            
            // Add inherited context if available
            if ($taskId) {
                $callInfo['task_id'] = $taskId;
            }
            if ($callLogId) {
                $callInfo['call_log_id'] = $callLogId;
            }
            if ($userId) {
                $callInfo['user_id'] = $userId;
            }
            
            // 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
            
            // CRITICAL FIX: Update any existing participants for this call to point to the new one
            $activeCalls = Cache::get('threecx_active_calls', []);
            foreach ($activeCalls as $existingParticipantId) {
                if ($existingParticipantId != $participantId) {
                    $existingCallInfo = Cache::get("threecx_call_{$existingParticipantId}");
                    if ($existingCallInfo && 
                        isset($existingCallInfo['event_data']['callid']) && 
                        $existingCallInfo['event_data']['callid'] == $participantDetails['callid']) {
                        
                        // Update the old participant cache to include migration info
                        $existingCallInfo['migrated_to'] = $participantId;
                        $existingCallInfo['migration_timestamp'] = now()->toISOString();
                        Cache::put("threecx_call_{$existingParticipantId}", $existingCallInfo, 3600);
                        
                        $this->info("🔄 Updated migration pointer: {$existingParticipantId} → {$participantId}");
                    }
                }
            }
            
            // IMPORTANT: Also create tracking cache entry for auto-logging
            if ($taskId && $callLogId) {
                Cache::put("threecx_call_tracking_{$participantId}", $callInfo, 3600);
                $this->info("📝 Created tracking cache for task call: participant {$participantId}");
            }
            
            // 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());
        }
    }

    protected function updateCallCacheFromWebSocket($participantId, $uiStatus, $details, $updateType)
    {
        try {
            $callInfo = Cache::get("threecx_call_{$participantId}");
            
            if ($callInfo) {
                $callInfo['status'] = $uiStatus;
                $callInfo['last_update'] = now()->toISOString();
                $callInfo['data_source'] = 'websocket_realtime';
                $callInfo['details'] = $details;
                
                if ($uiStatus === 'connected' && !isset($callInfo['connected_at'])) {
                    $callInfo['connected_at'] = now()->toISOString();
                }
                
                if ($uiStatus === 'ended') {
                    $callInfo['ended_at'] = now()->toISOString();
                    if (isset($callInfo['connected_at'])) {
                        $connectedAt = \Carbon\Carbon::parse($callInfo['connected_at']);
                        $endedAt = \Carbon\Carbon::parse($callInfo['ended_at']);
                        $callInfo['duration'] = $endedAt->diffInSeconds($connectedAt);
                    }
                    
                    // Preserve rejection reason in event_data for frontend
                    if (isset($details['reason'])) {
                        $callInfo['event_data'] = $details;
                        $callInfo['rejection_reason'] = $details['reason'];
                    }
                }
                
                Cache::put("threecx_call_{$participantId}", $callInfo, 3600);
                
                // Also update the tracking cache used by ThreeCXController
                $trackingKey = "threecx_call_tracking_{$participantId}";
                $trackingData = Cache::get($trackingKey, []);
                $trackingData = array_merge($trackingData, $callInfo);
                Cache::put($trackingKey, $trackingData, 3600);
                
                // Also update primary key cache if available
                if (isset($callInfo['primary_key'])) {
                    Cache::put("threecx_call_primary_{$callInfo['primary_key']}", $callInfo, 3600);
                }
                
                $this->info("📝 Updated call cache: {$participantId} → {$uiStatus} (via {$updateType})");
            }
            
        } catch (Exception $e) {
            $this->error("❌ Error updating call cache from WebSocket: " . $e->getMessage());
        }
    }

    protected function mapParticipantStatus($participantDetails)
    {
        $status = $participantDetails['status'] ?? 'Unknown';
        $partyDnType = $participantDetails['party_dn_type'] ?? null;
        $partyDn = $participantDetails['party_dn'] ?? '';
        
        Log::info('[3CX WebSocket] Status mapping', [
            'status' => $status,
            'party_dn_type' => $partyDnType,
            'party_dn' => $partyDn,
            'full_details' => $participantDetails
        ]);
        
        // Map raw 3CX statuses to UI-friendly statuses
        switch ($status) {
            case 'Dialing':
                $mapped = ($partyDnType === 'Wexternalline') ? 'dialing_customer' : 'dialing_reception';
                Log::info('[3CX WebSocket] Dialing status mapped', [
                    'from' => $status,
                    'party_dn_type' => $partyDnType,
                    'mapped_to' => $mapped
                ]);
                return $mapped;
            case 'Ringing':
                return 'ringing_customer';
            case 'Talking':
            case 'Connected':
                return 'connected';
            case 'Ended':
            case 'Disconnected':
                return 'ended';
            default:
                Log::info('[3CX WebSocket] Unknown status, using fallback', [
                    'status' => $status,
                    'fallback' => 'dialing_reception'
                ]);
                return 'dialing_reception'; // Default fallback
        }
    }

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

    protected function fetchParticipantDetails($callId, $participantId)
    {
        try {
            $token = $this->authService->getToken();
            $participantUrl = rtrim($this->baseUrl, '/') . "/callcontrol/{$callId}/participants/{$participantId}";
            
            $response = \Illuminate\Support\Facades\Http::withToken($token)
                ->timeout(10)
                ->get($participantUrl);
            
            if ($response->successful()) {
                return $response->json();
            }
            
            return null;
            
        } catch (Exception $e) {
            $this->error("❌ Error fetching participant details: " . $e->getMessage());
            return null;
        }
    }

    protected function enhancedPollingWithStateTracking($token)
    {
        $this->info("🔄 Starting enhanced polling with state tracking...");
        
        // Use a different approach - exit completely when no calls
        $this->loop->addPeriodicTimer(5, function () {
            try {
                // Check if we should stop the WebSocket (scheduled stop time)
                $stopAt = Cache::get('threecx_websocket_stop_at');
                if ($stopAt) {
                    $stopTime = \Carbon\Carbon::parse($stopAt);
                    if (now()->gte($stopTime)) {
                        $this->info("⏰ Scheduled stop time reached - gracefully stopping WebSocket");
                        Cache::forget('threecx_websocket_stop_at');
                        $this->info("🔄 Exiting process completely - scheduled stop time reached");
                        exit(0); // Force exit the entire process
                    }
                }
                
                // Get fresh token for each poll to avoid 401 errors
                $freshToken = $this->authService->getToken();
                if (!$freshToken) {
                    $this->error("⚠️ Could not get fresh authentication token for polling");
                    return;
                }
                
                $activeCalls = Cache::get('threecx_active_calls', []);
                
                // Clean up stale active calls (where the call info no longer exists)
                $validActiveCalls = array_filter($activeCalls, function($participantId) {
                    $callInfo = Cache::get("threecx_call_{$participantId}");
                    return $callInfo !== null;
                });
                
                // Update cache if we found stale entries
                if (count($validActiveCalls) !== count($activeCalls)) {
                    $staleCount = count($activeCalls) - count($validActiveCalls);
                    $this->info("🧹 Found {$staleCount} stale active call entries - cleaning up");
                    Cache::put('threecx_active_calls', array_values($validActiveCalls), 3600);
                    $activeCalls = $validActiveCalls;
                }
                
                $this->info("👂 Polling... Active calls: " . count($activeCalls) . ", Tracked states: " . count($this->participantsState));
                
                // Auto-stop if no active calls remain (even without scheduled stop)
                if (count($activeCalls) === 0) {
                    $this->info("🛑 No active calls remaining - auto-stopping enhanced polling");
                    Cache::forget('threecx_websocket_stop_at');
                    $this->info("🔄 Exiting process completely - no more calls to monitor");
                    exit(0); // Force exit the entire process
                }
                
                // Note: The scheduled stop logic above already handles the case where stopAt is set
                
                // Poll each active call
                foreach ($activeCalls as $participantId) {
                    $callInfo = Cache::get("threecx_call_{$participantId}");
                    if ($callInfo && isset($callInfo['call_id'])) {
                        $details = $this->fetchParticipantDetails($callInfo['call_id'], $participantId);
                        if ($details) {
                            $this->handleParticipantUpdate($callInfo['call_id'], $participantId, $details);
                        }
                    }
                }
                
            } catch (Exception $e) {
                $this->error("❌ Error in polling: " . $e->getMessage());
            }
        });
    }
    
    protected function transferCallTracking($oldParticipantId, $newParticipantId, $newParticipantDetails)
    {
        try {
            // Get the old call info
            $oldCallInfo = Cache::get("threecx_call_{$oldParticipantId}");
            if (!$oldCallInfo) {
                Log::warning('[3CX WebSocket] Cannot transfer tracking - old call info not found', [
                    'old_participant' => $oldParticipantId,
                    'new_participant' => $newParticipantId
                ]);
                return;
            }
            
            Log::info('[3CX WebSocket] Transferring call tracking', [
                'from_participant' => $oldParticipantId,
                'to_participant' => $newParticipantId,
                'call_id' => $oldCallInfo['call_id'] ?? 'unknown',
                'phone_number' => $oldCallInfo['phone_number'] ?? 'unknown'
            ]);
            
            // Create new call info with updated participant details
            $newCallInfo = $oldCallInfo;
            $newCallInfo['participant_id'] = $newParticipantId;
            $newCallInfo['last_update'] = now()->toISOString();
            $newCallInfo['event_data'] = $newParticipantDetails;
            
            // Determine new status based on participant details
            $newStatus = $this->mapParticipantStatus($newParticipantDetails);
            $newCallInfo['status'] = $newStatus;
            
            // Store new call info
            Cache::put("threecx_call_{$newParticipantId}", $newCallInfo, 3600);
            
            // Update active calls list
            $activeCalls = Cache::get('threecx_active_calls', []);
            $activeCallsUpdated = array_map(function($id) use ($oldParticipantId, $newParticipantId) {
                return $id == $oldParticipantId ? $newParticipantId : $id;
            }, $activeCalls);
            Cache::put('threecx_active_calls', $activeCallsUpdated, 3600);
            
            // Remove old call info
            Cache::forget("threecx_call_{$oldParticipantId}");
            
            Log::info('[3CX WebSocket] Call tracking transferred successfully', [
                'old_participant' => $oldParticipantId,
                'new_participant' => $newParticipantId,
                'new_status' => $newStatus,
                'active_calls_count' => count($activeCallsUpdated)
            ]);
            
        } catch (Exception $e) {
            Log::error('[3CX WebSocket] Error transferring call tracking', [
                'old_participant' => $oldParticipantId,
                'new_participant' => $newParticipantId,
                'error' => $e->getMessage()
            ]);
        }
    }
    
    /**
     * Auto-stop the WebSocket when no active calls remain
     */
    protected function autoStopWebSocket()
    {
        try {
            $this->info("🔄 Gracefully shutting down WebSocket - no active calls");
            
            // Exit the event loop to stop the WebSocket
            $this->loop->stop();
            
            // Log the auto-stop event
            Log::info('[3CX WebSocket] Auto-stopped due to no active calls');
            
        } catch (Exception $e) {
            $this->error("❌ Error during auto-stop: " . $e->getMessage());
            Log::error('[3CX WebSocket] Auto-stop error', ['error' => $e->getMessage()]);
        }
    }

    /**
     * Determine the rejection reason based on the previous call status
     */
    protected function determineRejectionReason($previousStatus, $callInfo)
    {
        switch ($previousStatus) {
            case 'dialing_reception':
            case 'ringing_reception':
                // Call ended before extension picked up = extension rejected
                return 'reception_rejected';
                
            case 'dialing_customer':
            case 'ringing_customer':
                // Call ended after extension answered but before customer picked up = customer rejected
                return 'customer_rejected';
                
            case 'connected':
                // Call ended while connected = normal completion (someone hung up)
                return 'call_completed';
                
            case 'failed':
                // Call already marked as failed
                return 'call_failed';
                
            default:
                // Unknown status - default to participant removed
                return 'participant_removed';
        }
    }
}
