<?php

namespace App\Modules\ThreeCX\Services;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use App\Modules\ThreeCX\Jobs\ProcessThreeCXCall;
use App\Modules\ThreeCX\Models\TaskCallLog;
use Carbon\Carbon;

/**
 * Call Queue Service for 3CX
 * 
 * Provides a robust queuing system for call processing
 * Handles retries, failures, and ensures calls are processed reliably
 */
class ThreeCXCallQueueService
{
    protected ThreeCXCallControlService $callControlService;
    
    /**
     * Queue status constants
     */
    const STATUS_PENDING = 'pending';
    const STATUS_PROCESSING = 'processing';
    const STATUS_COMPLETED = 'completed';
    const STATUS_FAILED = 'failed';
    
    public function __construct(ThreeCXCallControlService $callControlService)
    {
        $this->callControlService = $callControlService;
    }
    
    /**
     * Queue a call for processing
     * 
     * @param string $fromExtension
     * @param string $toNumber
     * @param int|null $taskId
     * @param array $options Additional options (priority, delay, etc.)
     * @return array
     */
    public function queueCall(string $fromExtension, string $toNumber, ?int $taskId = null, array $options = []): array
    {
        try {
            // Create call log record with queued status
            $callLog = TaskCallLog::create([
                'task_id' => $taskId,
                'user_id' => auth()->id(),
                'from_extension' => $fromExtension,
                'to_number' => $toNumber,
                'called_at' => now(),
                'status' => 'queued',
                'direction' => 'outbound',
                'metadata' => [
                    'queued_at' => now()->toIso8601String(),
                    'priority' => $options['priority'] ?? 'normal',
                    'max_attempts' => $options['max_attempts'] ?? 3
                ]
            ]);
            
            Log::info('[3CX Queue] Call queued for processing', [
                'call_log_id' => $callLog->id,
                'from' => $fromExtension,
                'to' => $toNumber,
                'task_id' => $taskId
            ]);
            
            // Determine queue priority
            $queue = $options['priority'] === 'high' ? 'high-priority-calls' : 'default';
            
            // Dispatch job to queue
            ProcessThreeCXCall::dispatch($callLog->id)
                ->onQueue($queue)
                ->delay($options['delay'] ?? 0);
            
            return [
                'success' => true,
                'data' => [
                    'call_log_id' => $callLog->id,
                    'message' => 'Call queued successfully',
                    'queue' => $queue
                ]
            ];
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Failed to queue call', [
                'error' => $e->getMessage(),
                'from' => $fromExtension,
                'to' => $toNumber
            ]);
            
            return [
                'success' => false,
                'error' => 'Failed to queue call: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Process a queued call
     * 
     * @param int $callLogId
     * @return array
     */
    public function processQueuedCall(int $callLogId): array
    {
        try {
            $callLog = TaskCallLog::find($callLogId);
            
            if (!$callLog) {
                Log::error('[3CX Queue] Call log not found', ['call_log_id' => $callLogId]);
                return ['success' => false, 'error' => 'Call log not found'];
            }
            
            // Check if call is already processed
            if (in_array($callLog->status, ['completed', 'failed', 'cancelled'])) {
                Log::info('[3CX Queue] Call already processed', [
                    'call_log_id' => $callLogId,
                    'status' => $callLog->status
                ]);
                return ['success' => true, 'message' => 'Call already processed'];
            }
            
            // Update status to processing
            $callLog->update(['status' => 'processing']);
            
            // Make the actual call with retry mechanism
            $result = $this->callControlService->makeCallWithRetry(
                $callLog->from_extension,
                $callLog->to_number,
                $callLog->task_id
            );
            
            // Update call log based on result
            if ($result['success']) {
                $callLog->update([
                    'status' => 'initiated',
                    'metadata' => array_merge($callLog->metadata ?? [], [
                        'processed_at' => now()->toIso8601String(),
                        'call_result' => $result['data'] ?? []
                    ])
                ]);
                
                Log::info('[3CX Queue] Call processed successfully', [
                    'call_log_id' => $callLogId,
                    'result' => $result
                ]);
            } else {
                $callLog->update([
                    'status' => 'failed',
                    'metadata' => array_merge($callLog->metadata ?? [], [
                        'failed_at' => now()->toIso8601String(),
                        'error' => $result['error'] ?? 'Unknown error'
                    ])
                ]);
                
                Log::error('[3CX Queue] Call processing failed', [
                    'call_log_id' => $callLogId,
                    'error' => $result['error'] ?? 'Unknown error'
                ]);
            }
            
            return $result;
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Exception during call processing', [
                'call_log_id' => $callLogId,
                'error' => $e->getMessage()
            ]);
            
            // Update call log with error
            if (isset($callLog)) {
                $callLog->update([
                    'status' => 'failed',
                    'metadata' => array_merge($callLog->metadata ?? [], [
                        'exception_at' => now()->toIso8601String(),
                        'exception' => $e->getMessage()
                    ])
                ]);
            }
            
            return [
                'success' => false,
                'error' => 'Processing failed: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Get queue statistics
     * 
     * @return array
     */
    public function getQueueStats(): array
    {
        try {
            $stats = [
                'queued' => TaskCallLog::where('status', 'queued')->count(),
                'processing' => TaskCallLog::where('status', 'processing')->count(),
                'completed_today' => TaskCallLog::where('status', 'completed')
                    ->whereDate('created_at', today())
                    ->count(),
                'failed_today' => TaskCallLog::where('status', 'failed')
                    ->whereDate('created_at', today())
                    ->count(),
                'avg_processing_time' => $this->calculateAverageProcessingTime()
            ];
            
            return ['success' => true, 'data' => $stats];
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Failed to get queue stats', ['error' => $e->getMessage()]);
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    /**
     * Retry failed calls
     * 
     * @param int $hoursBack How many hours back to look for failed calls
     * @return array
     */
    public function retryFailedCalls(int $hoursBack = 1): array
    {
        try {
            $failedCalls = TaskCallLog::where('status', 'failed')
                ->where('created_at', '>=', Carbon::now()->subHours($hoursBack))
                ->whereRaw("JSON_EXTRACT(metadata, '$.retry_count') < 3 OR JSON_EXTRACT(metadata, '$.retry_count') IS NULL")
                ->get();
            
            $retryCount = 0;
            
            foreach ($failedCalls as $callLog) {
                $metadata = $callLog->metadata ?? [];
                $currentRetryCount = $metadata['retry_count'] ?? 0;
                
                // Update retry count
                $metadata['retry_count'] = $currentRetryCount + 1;
                $metadata['last_retry_at'] = now()->toIso8601String();
                
                $callLog->update([
                    'status' => 'queued',
                    'metadata' => $metadata
                ]);
                
                // Re-queue the call
                ProcessThreeCXCall::dispatch($callLog->id)
                    ->onQueue('default')
                    ->delay(60 * ($currentRetryCount + 1)); // Exponential backoff
                
                $retryCount++;
            }
            
            Log::info('[3CX Queue] Retried failed calls', ['count' => $retryCount]);
            
            return [
                'success' => true,
                'data' => [
                    'retried_count' => $retryCount,
                    'message' => "Retried {$retryCount} failed calls"
                ]
            ];
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Failed to retry calls', ['error' => $e->getMessage()]);
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    /**
     * Calculate average processing time for completed calls
     * 
     * @return float
     */
    protected function calculateAverageProcessingTime(): float
    {
        try {
            $completedCalls = TaskCallLog::where('status', 'completed')
                ->whereDate('created_at', today())
                ->whereNotNull('answered_at')
                ->get();
            
            if ($completedCalls->isEmpty()) {
                return 0;
            }
            
            $totalTime = 0;
            $count = 0;
            
            foreach ($completedCalls as $call) {
                if ($call->called_at && $call->answered_at) {
                    $totalTime += Carbon::parse($call->answered_at)->diffInSeconds(Carbon::parse($call->called_at));
                    $count++;
                }
            }
            
            return $count > 0 ? round($totalTime / $count, 2) : 0;
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Failed to calculate average processing time', ['error' => $e->getMessage()]);
            return 0;
        }
    }
    
    /**
     * Clear old queue entries
     * 
     * @param int $daysOld
     * @return array
     */
    public function clearOldQueueEntries(int $daysOld = 30): array
    {
        try {
            $deleted = TaskCallLog::whereIn('status', ['completed', 'failed', 'cancelled'])
                ->where('created_at', '<', Carbon::now()->subDays($daysOld))
                ->delete();
            
            Log::info('[3CX Queue] Cleared old queue entries', [
                'deleted_count' => $deleted,
                'days_old' => $daysOld
            ]);
            
            return [
                'success' => true,
                'data' => [
                    'deleted_count' => $deleted,
                    'message' => "Deleted {$deleted} old call records"
                ]
            ];
            
        } catch (\Exception $e) {
            Log::error('[3CX Queue] Failed to clear old entries', ['error' => $e->getMessage()]);
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
}