<?php

namespace App\Modules\ThreeCX\Services;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use App\Models\Setting;

/**
 * 3CX Call Tracking Service
 * 
 * Handles call status polling and tracking for specific calls
 * Stores call status updates for dashboard display
 */
class ThreeCXCallTrackingService
{
    protected $authService;
    protected $baseUrl;

    public function __construct(ThreeCXAuthService $authService)
    {
        $this->authService = $authService;
        $this->baseUrl = Setting::get('integrations.threecx.base_url', '');
    }

    /**
     * Start monitoring a specific call/participant
     * 
     * @param int $participantId The participant ID from the call response
     * @param int $callId The call ID from the call response
     * @param string $phoneNumber The phone number being called
     * @return bool Success status
     */
    public function startMonitoring(int $participantId, int $callId, string $phoneNumber, array $context = []): bool
    {
        try {
            Log::info('[3CX Call Tracking] Starting call monitoring', [
                'participant_id' => $participantId,
                'call_id' => $callId,
                'phone_number' => $phoneNumber
            ]);

            // Store call info in cache for tracking
            $callInfo = [
                'participant_id' => $participantId,
                'call_id' => $callId,
                'phone_number' => $phoneNumber,
                'extension' => $context['extension'] ?? \App\Modules\ThreeCX\Services\ThreeCXExtensionResolver::getExtensionForUser(),
                'status' => 'Dialing',
                'started_at' => now()->toISOString(),
                'last_update' => now()->toISOString(),
                // Include task context for automatic call logging
                'task_id' => $context['task_id'] ?? null,
                'user_id' => $context['user_id'] ?? \Auth::id(),
            ];
            
            // Store both in main cache and tracking cache for server-side auto-logging
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600); // 1 hour cache
            Cache::put("threecx_call_tracking_{$participantId}", $callInfo, 3600); // For auto-logging
            
            // Register this call in the active calls list
            $activeCalls = Cache::get('threecx_active_calls', []);
            if (!in_array($participantId, $activeCalls)) {
                $activeCalls[] = $participantId;
                Cache::put('threecx_active_calls', $activeCalls, 3600);
                
                Log::info('[3CX Call Tracking] Call added to active calls list', [
                    'participant_id' => $participantId,
                    'total_active_calls' => count($activeCalls)
                ]);
            }
            
            // Setup initial polling info
            $this->setupPollingInfo($participantId);
            
            Log::info('[3CX Call Tracking] Call monitoring setup completed (non-blocking)', [
                'participant_id' => $participantId,
                'call_id' => $callId
            ]);
            
            return true;
            
        } catch (\Exception $e) {
            Log::error('[3CX Call Tracking] Failed to start monitoring', [
                'error' => $e->getMessage(),
                'participant_id' => $participantId
            ]);
            
            return false;
        }
    }

    /**
     * Setup polling info for call monitoring
     * 
     * @param int $participantId
     */
    protected function setupPollingInfo(int $participantId): void
    {
        try {
            // Get OAuth token
            $token = $this->authService->getToken();
            
            Log::info('[3CX Call Tracking] Setting up polling info', [
                'participant_id' => $participantId
            ]);
            
            // Do an immediate status poll to get initial state
            $this->pollCallStatusOnce($participantId, $token);
            
            // Store polling info for continued monitoring
            Cache::put("threecx_polling_{$participantId}", [
                'token' => $token,
                'last_poll' => now()->toISOString(),
                'poll_count' => 1,
                'status' => 'active'
            ], 3600);
            
            Log::info('[3CX Call Tracking] Initial status poll completed - ready for frontend polling', [
                'participant_id' => $participantId
            ]);
            
        } catch (\Exception $e) {
            Log::error('[3CX Call Tracking] Polling setup failed', [
                'error' => $e->getMessage(),
                'participant_id' => $participantId
            ]);
        }
    }

    /**
     * Get current call status from cache
     * 
     * @param int $participantId
     * @return array|null
     */
    public function getCallStatus(int $participantId): ?array
    {
        return Cache::get("threecx_call_{$participantId}");
    }

    /**
     * Update call status (called when WebSocket events are received)
     * 
     * @param int $participantId
     * @param string $status
     * @param array $eventData
     */
    public function updateCallStatus(int $participantId, string $status, array $eventData = []): void
    {
        $callInfo = $this->getCallStatus($participantId);
        
        if ($callInfo) {
            $callInfo['status'] = $status;
            $callInfo['last_update'] = now()->toISOString();
            $callInfo['event_data'] = $eventData;
            
            // Only set connected_at timestamp once when first transitioning to connected
            if ($status === 'connected' && !isset($callInfo['connected_at'])) {
                $callInfo['connected_at'] = now()->toISOString();
            } elseif (in_array($status, ['ended', 'failed', 'cancelled'])) {
                $callInfo['ended_at'] = now()->toISOString();
                // Calculate duration from connected_at to ended_at
                if (isset($callInfo['connected_at'])) {
                    $connectedTime = \Carbon\Carbon::parse($callInfo['connected_at']);
                    $endedTime = \Carbon\Carbon::parse($callInfo['ended_at']);
                    $callInfo['duration'] = $connectedTime->diffInSeconds($endedTime);
                } else {
                    $callInfo['duration'] = null;
                }
                
                // 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);
                
                // Schedule WebSocket stop after call ends (with 15 second delay as requested)
                $this->scheduleWebSocketStop($participantId);
            }
            
            Cache::put("threecx_call_{$participantId}", $callInfo, 3600);
            
            Log::info('[3CX Call Tracking] Call status updated', [
                'participant_id' => $participantId,
                'new_status' => $status,
                'call_info' => $callInfo
            ]);
        }
    }

    /**
     * Get all active call statuses for dashboard display
     * 
     * @return array
     */
    public function getActiveCallStatuses(): array
    {
        // In a real implementation, you'd query your cache/database for all active calls
        // For now, return empty array as we'd need to implement cache pattern matching
        return [];
    }
    
    /**
     * Clear call cache to prevent stale data
     * 
     * @param int $participantId
     */
    public function clearCallCache(int $participantId): void
    {
        Cache::forget("threecx_call_{$participantId}");
        Cache::forget("threecx_polling_{$participantId}");
        Cache::forget("threecx_failures_{$participantId}");
        
        Log::info('[3CX Call Tracking] Cleared call cache', [
            'participant_id' => $participantId
        ]);
    }




    /**
     * Poll call status once from 3CX API and update immediately
     * This method is called both during setup and by frontend polling
     * 
     * @param int $participantId
     * @param string $token
     */
    public function pollCallStatusOnce(int $participantId, string $token = null): void
    {
        try {
            // Get fresh token to prevent expiry issues
            $token = $this->authService->getToken();
            
            // First check if we should timeout the call due to being stuck
            $callInfo = $this->getCallStatus($participantId);
            if ($callInfo && isset($callInfo['started_at'])) {
                $startTime = \Carbon\Carbon::parse($callInfo['started_at']);
                $timeElapsed = $startTime->diffInMinutes(now());
                
                // If call has been in the same state for more than 5 minutes, consider it ended
                // Real calls can take time to connect, especially international numbers and busy extensions
                Log::info('[3CX Call Tracking] Checking call timeout', [
                    'participant_id' => $participantId,
                    'current_status' => $callInfo['status'],
                    'elapsed_minutes' => $timeElapsed,
                    'should_timeout' => ($timeElapsed > 5 && in_array($callInfo['status'], ['Dialing', 'Ringing']))
                ]);
                
                if ($timeElapsed > 5 && in_array($callInfo['status'], ['Dialing', 'Ringing'])) {
                    Log::info('[3CX Call Tracking] Call timeout - ending stuck call', [
                        'participant_id' => $participantId,
                        'current_status' => $callInfo['status'],
                        'elapsed_minutes' => $timeElapsed
                    ]);
                    $this->updateCallStatus($participantId, 'Ended', ['reason' => 'timeout']);
                    return;
                }
            }
            
            // Get call info to extract call ID for better status detection
            $callInfo = $this->getCallStatus($participantId);
            $callId = $callInfo['call_id'] ?? null;
            
            // For pure polling, try call endpoint first if we have call ID
            // This avoids participant removal issues entirely
            if ($callId) {
                Log::info('[3CX Call Tracking] Using call-based tracking instead of participant tracking', [
                    'participant_id' => $participantId,
                    'call_id' => $callId,
                    'approach' => 'call_endpoint_primary'
                ]);
            }
            
            // Try multiple endpoints for better status detection
            // Centralize REST call via MakeCallService
            $statusResult = app(\App\Modules\ThreeCX\Services\ThreeCXMakeCallService::class)->getCallStatus((string)$participantId);
            if (($statusResult['success'] ?? false) && isset($statusResult['status'])) {
                $statusData = $statusResult['status'];
                
                Log::info('[3CX Call Tracking] Retrieved call status (real-time)', [
                    'participant_id' => $participantId,
                    'call_id' => $callId,
                    'endpoint_used' => 'participant_endpoint',
                    'api_status' => $statusData['status'] ?? 'unknown',
                    'party_dn' => $statusData['party_dn'] ?? null,
                    'party_dn_type' => $statusData['party_dn_type'] ?? null,
                    'full_response' => $statusData
                ]);

                // Clear failure count on successful response
                Cache::forget("threecx_failures_{$participantId}");
                
                // Update cache with current status
                if (isset($statusData['status'])) {
                    // Enhanced status detection based on multiple fields
                    $detectedStatus = $this->detectRealStatus($statusData);
                    
                    // Check if status actually changed by comparing to cached status
                    $currentCallInfo = $this->getCallStatus($participantId);
                    $currentStatus = $currentCallInfo['status'] ?? 'unknown';
                    $statusChanged = ($detectedStatus !== $currentStatus);
                    
                    Log::info('[3CX Call Tracking] Status analysis', [
                        'participant_id' => $participantId,
                        'api_status' => $statusData['status'],
                        'detected_status' => $detectedStatus,
                        'current_cached_status' => $currentStatus,
                        'status_changed' => $statusChanged
                    ]);
                    
                    // Only update call status if it actually changed
                    if ($statusChanged) {
                        $this->updateCallStatus($participantId, $detectedStatus, $statusData);
                    } else {
                        Log::info('[3CX Call Tracking] Status unchanged - skipping update to preserve timestamps', [
                            'participant_id' => $participantId,
                            'status' => $detectedStatus
                        ]);
                    }
                }
            } else {
                // API failed - check if WebSocket has more recent data
                $callInfo = $this->getCallStatus($participantId);
                $currentStatus = $callInfo['status'] ?? 'unknown';
                $dataSource = $callInfo['data_source'] ?? 'polling';
                
                // If WebSocket provided recent data, trust that instead of API polling
                if ($dataSource === 'websocket_realtime') {
                    Log::info('[3CX Call Tracking] Using WebSocket data instead of failed API polling', [
                        'participant_id' => $participantId,
                        'websocket_status' => $currentStatus,
                        'last_update' => $callInfo['last_update'] ?? 'unknown'
                    ]);
                    return; // Trust WebSocket data
                }
                
                // Fallback: Track polling failures for calls without WebSocket data
                $failureCount = Cache::get("threecx_failures_{$participantId}", 0) + 1;
                Cache::put("threecx_failures_{$participantId}", $failureCount, 300);
                
                Log::info('[3CX Call Tracking] API polling failed - WebSocket should provide real-time data', [
                    'participant_id' => $participantId,
                    'failure_count' => $failureCount,
                    'current_status' => $currentStatus,
                    'note' => 'WebSocket listener should update this call with real data'
                ]);
                
                // Be much more patient before marking as ended - calls can take time to establish
                if ($failureCount >= 15) { // Increased from 5 to 15 for more patience
                    Log::info('[3CX Call Tracking] Multiple failures and no WebSocket data - marking as ended', [
                        'participant_id' => $participantId,
                        'failure_count' => $failureCount
                    ]);
                    
                    $this->updateCallStatus($participantId, 'ended', [
                        'reason' => 'no_realtime_data',
                        'note' => 'Neither API nor WebSocket providing call status'
                    ]);
                    Cache::forget("threecx_failures_{$participantId}");
                } else {
                    // Keep last known state - WebSocket may update it
                    Log::info('[3CX Call Tracking] Keeping last known state - waiting for WebSocket update', [
                        'participant_id' => $participantId,
                        'failure_count' => $failureCount,
                        'current_status' => $currentStatus
                    ]);
                }
                
                return;
            }

        } catch (\Exception $e) {
            Log::error('[3CX Call Tracking] Failed to poll call status (real-time)', [
                'error' => $e->getMessage(),
                'participant_id' => $participantId
            ]);
            
            // Clear cache on repeated errors to prevent stale data
            $this->clearCallCache($participantId);
        }
    }

    
    /**
     * Detect the real status based on 3CX API response fields
     * 
     * @param array $statusData
     * @return string
     */
    protected function detectRealStatus(array $statusData): string
    {
        $apiStatus = $statusData['status'] ?? 'Unknown';
        $partyDn = $statusData['party_dn'] ?? '';
        $partyDnType = $statusData['party_dn_type'] ?? '';
        $callId = $statusData['callid'] ?? null;
        $dn = $statusData['dn'] ?? '';
        $partyCallerId = $statusData['party_caller_id'] ?? '';
        
        // Log detailed status for debugging
        Log::info('[3CX Call Tracking] Detailed status analysis', [
            'api_status' => $apiStatus,
            'party_dn' => $partyDn,
            'party_dn_type' => $partyDnType,
            'call_id' => $callId,
            'dn' => $dn,
            'full_data' => $statusData
        ]);
        
        // Enhanced status detection for the step-by-step call flow:
        // 1. Dialing Reception Extension
        // 2. Reception Answered → Dialing Customer 
        // 3. Customer Connected → Show Timer
        // 4. Call Ended
        
        if (!empty($callId)) {
            // Check for connected states (when both parties are talking)
            if (in_array($apiStatus, ['Talking', 'Connected', 'InCall', 'Established'])) {
                return 'connected';
            }
            
            // Check for ended states
            if (in_array($apiStatus, ['Ended', 'Completed', 'Disconnected', 'Finished'])) {
                return 'ended';
            }
            
            // Check for failed states
            if (in_array($apiStatus, ['Failed', 'Busy', 'NoAnswer', 'Rejected'])) {
                return 'failed';
            }
            
            // Step-by-step call progression detection
            if ($apiStatus === 'Dialing') {
                // If party_dn_type is external line, reception answered and now dialing customer
                if ($partyDnType === 'Wexternalline' && !empty($partyDn)) {
                    Log::info('[3CX Call Tracking] Reception answered, dialing customer', [
                        'participant_id' => $statusData['id'] ?? 'unknown',
                        'party_dn' => $partyDn,
                        'party_dn_type' => $partyDnType
                    ]);
                    return 'dialing_customer';
                }
                
                // If no external line yet, still dialing reception
                Log::info('[3CX Call Tracking] Still dialing reception extension', [
                    'participant_id' => $statusData['id'] ?? 'unknown',
                    'extension' => $dn
                ]);
                return 'dialing_reception';
            }
            
            // When call is ringing, determine if it's reception or customer
            if ($apiStatus === 'Ringing') {
                // If party_dn_type is external, customer is ringing
                if ($partyDnType === 'Wexternalline' && !empty($partyDn)) {
                    Log::info('[3CX Call Tracking] Customer is ringing', [
                        'participant_id' => $statusData['id'] ?? 'unknown',
                        'customer_number' => $partyDn
                    ]);
                    return 'ringing_customer';
                }
                
                // Reception is ringing
                Log::info('[3CX Call Tracking] Reception is ringing', [
                    'participant_id' => $statusData['id'] ?? 'unknown',
                    'extension' => $dn
                ]);
                return 'ringing_reception';
            }
            
            // Default to the API status in lowercase for consistency
            return strtolower($apiStatus);
        }
        
        // If no call ID, consider the participant/call ended
        return 'ended';
    }
    
    /**
     * Schedule WebSocket stop after call ends (with delay for cleanup)
     * 
     * @param int $participantId
     */
    protected function scheduleWebSocketStop(int $participantId): void
    {
        try {
            // Check if this WebSocket was started for this specific participant
            $wsParticipantId = Cache::get('threecx_websocket_for_participant');
            
            if ($wsParticipantId == $participantId) {
                Log::info('[3CX Call Tracking] Scheduling WebSocket stop for ended call', [
                    'participant_id' => $participantId,
                    'delay_seconds' => 15
                ]);
                
                // Schedule stop time in cache for later cleanup
                Cache::put('threecx_websocket_stop_at', now()->addSeconds(15)->toISOString(), 900);
                
                // Clear the WebSocket participant tracking
                Cache::forget('threecx_websocket_for_participant');
            } else {
                Log::info('[3CX Call Tracking] WebSocket not started for this participant, no need to stop', [
                    'participant_id' => $participantId,
                    'websocket_participant_id' => $wsParticipantId
                ]);
            }
        } catch (\Exception $e) {
            Log::error('[3CX Call Tracking] Error scheduling WebSocket stop', [
                'participant_id' => $participantId,
                'error' => $e->getMessage()
            ]);
        }
    }
}