<?php

namespace App\Jobs;

use App\Models\BlockNumber;
use App\Models\CampaignTransactionLog;
use App\Models\CreditManage;
use App\Models\CutoffDetail;
use App\Models\PlanAssign;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\RcsSendMessage;
use App\Models\RcsSendNumber;
use App\Models\SmartCutoff;
use App\Models\User;
use App\Models\RcsToken;
use App\Models\RcsGateway;
use App\Models\RcsTemplate;
use App\Models\RoutingPlan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Pool as GuzzlePool;
use GuzzleHttp\Psr7\Request as GuzzleRequest;

class RcsSendDynamicNumbersJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $timeout = 10800; // 3 hours
    public $tries = 3;
    public $backoff = [60, 300, 900]; // Retry after 1min, 5min, 15min

    protected $messagesCacheKey; // Cache key instead of messages array
    protected $messages; // Will be populated from cache
    protected $message;
    protected $userId;
    protected $routeId;
    protected $creditPerNumber;
    protected $gatewayName;
    protected $endPoint;
    protected $accessToken;
    protected $clientId;
    protected $gatewayId;
    protected $isVariable;
    protected $gatewayTemplateId;
    protected $cardTitle;
    protected $urlLink;
    protected $buttonText;
    protected $imagePath;

    // Store the actual data instead of IDs
    protected $storedGatewayName;
    protected $storedEndpoint;
    protected $storedClientId;
    protected $storedAccessToken;
    protected $storedBotType;
    protected $storedTemplateId;
    protected $tokenId;
    protected $storedTokenId;

    public function __construct(string $messagesCacheKey, RcsSendMessage $message, int $userId, int $routeId, int $creditPerNumber = 1)
    {
        $this->messagesCacheKey = $messagesCacheKey;
        $this->message = $message;
        $this->userId = $userId;
        $this->routeId = $routeId;
        $this->creditPerNumber = $creditPerNumber;
        $this->storedBotType = $message->bot_type;

        /* $this->fetchGatewayDetails(); */

        $this->cardTitle = $message->card_title ?? null;
        $this->urlLink = $message->url_link ?? null;
        $this->buttonText = $message->button_text ?? null;
        $this->imagePath = $message->image_path ?? null;
        $this->isVariable = $message->is_variable ?? 0;
        
        // Fetch and store gateway details in constructor to avoid race conditions
        $this->fetchAndStoreGatewayDetails();
    }

    protected function fetchAndStoreGatewayDetails()
    {
        try {
            // Use DB transaction to ensure data consistency
            DB::transaction(function () {
                // First get plan assignment
                $planAssignDetail = PlanAssign::where('user_ID', $this->userId)
                                    ->where('isActive', 1)
                                    ->lockForUpdate()
                                    ->first();

                if(!$planAssignDetail) 
                {
                    throw new \Exception('Plan assign details not found');
                }

                $plan_ID = $planAssignDetail->plan_ID;

                // Get routing plan
                $routingPlanDetail = RoutingPlan::where('plan_ID', $plan_ID)
                                    ->where('route_ID', $this->routeId)
                                    ->where('isActive', 1)
                                    ->lockForUpdate()
                                    ->first();

                if(!$routingPlanDetail) 
                {
                    throw new \Exception('Routing plan details not found');
                }

                $gateway_ID = $routingPlanDetail->gateway_ID;

                // Get gateway details
                $rcsGatewayDetail = RcsGateway::where('id', $gateway_ID)
                                    ->where('isActive', 1)
                                    ->lockForUpdate()
                                    ->first();

                if(!$rcsGatewayDetail) 
                {
                    throw new \Exception('RCS gateway details not found');
                }

                $this->storedGatewayName = $rcsGatewayDetail->gateway_name;
                $this->storedEndpoint = $rcsGatewayDetail->end_point;
                $this->gatewayId = $gateway_ID;

                // If the template already remembers which specific token it belongs
                // to, use that instead of guessing between multiple matching tokens.
                $templateDetail = RcsTemplate::where('template_name', $this->message->template_name)
                    ->where('bot_type', $this->storedBotType)
                    ->where('user_ID', $this->userId)
                    ->where('isActive', 1)
                    ->first();

                if($templateDetail && $templateDetail->token_id)
                {
                    $rcsTokenDetail = RcsToken::where('id', $templateDetail->token_id)
                                    ->where('isActive', 1)
                                    ->sharedLock()
                                    ->first();
                }
                else
                {
                    // Get token details with gateway_id - use sharedLock to avoid blocking
                    $rcsTokenDetail = RcsToken::where('user_ID', $this->userId)
                                    ->where('bot_type', $this->storedBotType)
                                    ->where('gateway_id', $this->gatewayId)
                                    ->where('isActive', 1)
                                    ->sharedLock()
                                    ->first();
                }

                if($rcsTokenDetail)
                {
                    $this->storedClientId = $rcsTokenDetail->client_id;
                    $this->storedAccessToken = $rcsTokenDetail->access_token;
                    $this->storedTokenId = $rcsTokenDetail->id;
                    $this->storedTemplateId = $templateDetail->template_id ?? null;
                }
                else
                {
                    throw new \Exception('RCS token details not found');
                }
            });

            // Set the instance variables from stored data
            $this->gatewayName = $this->storedGatewayName;
            $this->endPoint = $this->storedEndpoint;
            $this->clientId = $this->storedClientId;
            $this->accessToken = $this->storedAccessToken;
            $this->tokenId = $this->storedTokenId;
            $this->gatewayTemplateId = $this->storedTemplateId;

            $this->isVariable = $this->message->is_variable ?? 0;
            $this->cardTitle = $this->message->card_title ?? null;
            $this->urlLink = $this->message->url_link ?? null;
            $this->buttonText = $this->message->button_text ?? null;
            $this->imagePath = $this->message->image_path ?? null;
        } 
        catch (\Exception $e) 
        {
            throw $e;
        }
    }

    /* protected function fetchGatewayDetails()
    {
        $rcsTokenDetail = RcsToken::where('user_ID', $this->userId)
                        ->where('bot_type', $this->message->bot_type)
                        ->where('isActive', 1)
                        ->first();

        if($rcsTokenDetail) 
        {
            $this->clientId = $rcsTokenDetail->client_id;
            $this->accessToken = $rcsTokenDetail->access_token;
            
            $rcsGatewayDetail   = RcsGateway::where('id', $rcsTokenDetail->gateway_id)
                                ->where('isActive', 1)
                                ->first();
                
            if($rcsGatewayDetail) 
            {
                $this->gatewayName = $rcsGatewayDetail->gateway_name;
                $this->endPoint = $rcsGatewayDetail->end_point;
            }
        }
        
        $this->isVariable = $this->message->is_variable ?? 0;
    } */

    public function handle(): void
    {
        $startTime = microtime(true);

        try {
        // Set memory and time limits
        @ini_set('memory_limit', '2048M');
        @ini_set('max_execution_time', '10800');
        @set_time_limit(10800);

        // Retrieve messages from cache
        $this->messages = Cache::get($this->messagesCacheKey);

        if (!$this->messages || !is_array($this->messages)) {
            Log::error('RcsSendDynamicNumbersJob: Messages not found in cache or expired', [
                'job_id' => $this->message->job_id,
                'cache_key' => $this->messagesCacheKey
            ]);
            return;
        }

        Log::info('RcsSendDynamicNumbersJob: Messages retrieved from cache', [
            'job_id' => $this->message->job_id,
            'cache_key' => $this->messagesCacheKey,
            'messages_count' => count($this->messages),
            'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2)
        ]);

        // Recalculate creditPerNumber from template_type to ensure correctness
        // Simple/text = 1 credit per number, Rich card = 2 credits per number
        $this->creditPerNumber = ($this->message->template_type === 'rich_card') ? 2 : 1;

        Log::info('RcsSendDynamicNumbersJob: Credit per number determined', [
            'job_id'           => $this->message->job_id,
            'template_type'    => $this->message->template_type,
            'credit_per_number' => $this->creditPerNumber,
        ]);

        if(!$this->gatewayName || !$this->endPoint || !$this->accessToken || !$this->clientId)
        {
            $this->fetchAndStoreGatewayDetails();

            if(!$this->gatewayName || !$this->endPoint || !$this->accessToken || !$this->clientId)
            {
                Log::error('RCS Job: Missing gateway details after fetch attempt', [
                    'user_id' => $this->userId,
                    'route_id' => $this->routeId,
                    'gateway_name' => $this->gatewayName,
                    'endpoint' => $this->endPoint
                ]);
                return;
            }
        }

        $parent_id = User::find($this->userId)?->created_by ?? $this->userId;

        // Log job start
        Log::info('RcsSendDynamicNumbersJob started', [
            'job_id' => $this->message->job_id,
            'user_id' => $this->userId,
            'route_id' => $this->routeId,
            'total_numbers' => count($this->messages),
            'gateway' => $this->gatewayName
        ]);

        $records = [];

        // OPTIMIZATION: Use hash maps (isset) instead of in_array for O(1) lookup
        $processedNumbersMap = [];
        $allNumbersForLookupMap = [];

        $validNumbersCount = 0;

        $cutoffCounter = 0;
        $numbersToProcess = [];

        // First pass: collect all numbers for block number lookup
        foreach($this->messages as $msg)
        {
            $number = trim($msg['number']);

            if(empty($number) || !is_numeric(preg_replace('/[^0-9]/', '', $number)))
            {
                continue;
            }

            $normalizedOriginalNumber = $this->normalizeNumber($number);

            // OPTIMIZATION: Use isset() for O(1) lookup instead of in_array() which is O(n)
            if(isset($processedNumbersMap[$normalizedOriginalNumber]))
            {
                continue;
            }

            $processedNumbersMap[$normalizedOriginalNumber] = true;

            $numberForStorage = $number;

            if(!str_starts_with($numberForStorage, '+91'))
            {
                if(str_starts_with($numberForStorage, '91') && strlen($numberForStorage) === 12)
                {
                    $numberForStorage = '+' . $numberForStorage;
                }
                elseif(strlen($numberForStorage) === 10)
                {
                    $numberForStorage = '+91' . $numberForStorage;
                }
            }

            if(strlen($numberForStorage) > 13)
            {
                continue;
            }

            // OPTIMIZATION: Use hash map instead of array + array_unique inside loop
            $allNumbersForLookupMap[$normalizedOriginalNumber] = true;

            if(str_starts_with($normalizedOriginalNumber, '9') && strlen($normalizedOriginalNumber) === 10)
            {
                $allNumbersForLookupMap['+91' . $normalizedOriginalNumber] = true;
                $allNumbersForLookupMap['91' . $normalizedOriginalNumber] = true;
            }
        }

        // Convert hash map keys to array for DB queries
        $allNumbersForLookup = array_keys($allNumbersForLookupMap);
        unset($allNumbersForLookupMap); // Free memory

        // Fetch block numbers in batches to avoid too many placeholders
        $blockNumbers = [];
        if(!empty($allNumbersForLookup)) 
        {
            $chunkSize = 1000; // Adjust this based on your database limits
            
            foreach(array_chunk($allNumbersForLookup, $chunkSize) as $chunkIndex => $chunk) 
            {
                $chunkBlockNumbers = BlockNumber::whereIn('mobile_number', $chunk)
                                    ->where('user_ID', $this->userId)
                                    ->get()
                                    ->mapWithKeys(function ($item) {
                                        return [$this->normalizeNumber($item->mobile_number) => $item->status];
                                    })
                                    ->toArray();
                
                $blockNumbers = array_merge($blockNumbers, $chunkBlockNumbers);
                
                Log::debug('RCS Dynamic - Fetched block numbers chunk', [
                    'chunk_index' => $chunkIndex + 1,
                    'chunk_size' => count($chunk),
                    'block_numbers_in_chunk' => count($chunkBlockNumbers),
                    'total_block_numbers_so_far' => count($blockNumbers)
                ]);
            }
            
            Log::info('RCS Dynamic - Block numbers fetched in batches', [
                'total_unique_numbers_to_fetch' => count($allNumbersForLookup),
                'chunk_size_used' => $chunkSize,
                'total_block_numbers_found' => count($blockNumbers)
            ]);
        }

        // Cut-Off Details (fetch once outside the loop for efficiency)
        $cutOffDetail = CutoffDetail::where('user_ID', $this->userId)
                        ->where('cutoff_route_ID', $this->routeId)
                        ->where('isActive', 1)
                        ->first();

        $throughput = null;
        $min_cut_value = null;
        $cutoff_status = null;

        if($cutOffDetail)
        {
            $throughput = $cutOffDetail->throughput;
            $min_cut_value = $cutOffDetail->min_cut_value;
            $cutoff_status = $cutOffDetail->cutoff_status;
            
            Log::info('RCS Dynamic - Cutoff details found', [
                'user_id' => $this->userId,
                'throughput' => $throughput,
                'min_cut_value' => $min_cut_value,
                'cutoff_status' => $cutoff_status
            ]);
        }

        // Count valid numbers from unique processed numbers (not all messages)
        foreach($processedNumbersMap as $normalizedNumber => $flag)
        {
            $effectiveBlockStatus = $blockNumbers[$normalizedNumber] ?? null;

            if($effectiveBlockStatus !== 1)
            {
                $validNumbersCount++;
            }
        }

        Log::info('RCS Dynamic - Number validation completed', [
            'total_input_numbers' => count($this->messages),
            'unique_numbers' => count($processedNumbersMap),
            'valid_numbers_count' => $validNumbersCount,
            'block_numbers_count' => count($blockNumbers)
        ]);

        // Apply cutoff logic only if min_cut_value is less than total valid numbers
        $applyCutoff = $cutOffDetail && 
                      $min_cut_value !== null && 
                      $min_cut_value < $validNumbersCount &&
                      $throughput && 
                      $cutoff_status;

        $cutoffPercentage = null;
        $numbersToMarkAsCutoff = 0;

        if($applyCutoff) 
        {
            $throughputParts = explode('-', $throughput);
            
            if(count($throughputParts) === 2) 
            {
                $minPercent = (int)trim($throughputParts[0]);
                $maxPercent = (int)trim($throughputParts[1]);
                
                $cutoffPercentage = rand($minPercent, $maxPercent);
                
                $numbersToMarkAsCutoff = (int)ceil(($cutoffPercentage / 100) * $validNumbersCount);
                
                Log::info('RCS Dynamic - Cutoff calculation', [
                    'apply_cutoff' => $applyCutoff,
                    'cutoff_percentage' => $cutoffPercentage,
                    'numbers_to_mark_as_cutoff' => $numbersToMarkAsCutoff,
                    'min_percent' => $minPercent,
                    'max_percent' => $maxPercent
                ]);
            }
        }

        // Second pass: process numbers and create records
        // OPTIMIZATION: Use a separate hash map to track duplicates in this pass
        $secondPassProcessed = [];

        Log::info('RCS Dynamic - Starting second pass (record creation)', [
            'job_id' => $this->message->job_id,
            'total_messages' => count($this->messages)
        ]);

        foreach($this->messages as $msg)
        {
            $number = trim($msg['number']);
            $sendMsg = $msg['message'] ?? '';
            $charCount = $msg['char_count'] ?? mb_strlen($sendMsg); // Handle missing char_count
            $msgCredit = $this->creditPerNumber;

            if(empty($number) || !is_numeric(preg_replace('/[^0-9]/', '', $number)))
            {
                continue;
            }

            $normalizedOriginalNumber = $this->normalizeNumber($number);

            // OPTIMIZATION: Use isset() for O(1) duplicate check instead of in_array() O(n)
            // Skip if this number was NOT seen in first pass
            if(!isset($processedNumbersMap[$normalizedOriginalNumber]))
            {
                continue;
            }

            // Skip if already processed in THIS pass (prevent duplicates in records)
            if(isset($secondPassProcessed[$normalizedOriginalNumber]))
            {
                continue;
            }
            $secondPassProcessed[$normalizedOriginalNumber] = true;

            $numberForStorage = $number;

            if(!str_starts_with($numberForStorage, '+91'))
            {
                if(str_starts_with($numberForStorage, '91') && strlen($numberForStorage) === 12)
                {
                    $numberForStorage = '+' . $numberForStorage;
                }
                elseif(strlen($numberForStorage) === 10)
                {
                    $numberForStorage = '+91' . $numberForStorage;
                }
            }

            if(strlen($numberForStorage) > 13)
            {
                continue;
            }

            $status = "Submitted";
            $isPicked = 0;

            $effectiveBlockStatus = $blockNumbers[$normalizedOriginalNumber] ?? null;

            $masterJobId = $this->generateRandomAlphaNumeric(20);

            if($applyCutoff && $effectiveBlockStatus !== 1 && $cutoffCounter < $numbersToMarkAsCutoff)
            {
                $status = $cutoff_status;
                $isPicked = 1;
                $cutoffCounter++;
            }
            elseif($effectiveBlockStatus === 1)
            {
                continue;
            }
            elseif($effectiveBlockStatus === 0)
            {
                $status = 'Blacklisted';
                $isPicked = 1;
            }
            else
            {
                $numbersToProcess[] = [
                    'number' => $number,
                    'is_picked' => $isPicked,
                    'message' => $sendMsg,
                    'master_job_id' => $masterJobId,
                ];
            }

            $record = [
                'message_id' => $this->message->id,
                'mobile_number' => $number,
                'master_job_id' => $masterJobId,
                'msg_job_id' => $this->message->job_id,
                'parent_id' => $parent_id,
                'message' => $sendMsg,
                'msgcredit' => $msgCredit,
                'route' => $this->message->route,
                'userid' => $this->userId,
                'status' => $status,
                'is_picked' => $isPicked,
                'service_id' => $this->gatewayName,
                'created_by' => $this->userId,
                'created_at' => now(),
                'updated_at' => null,
            ];

            $records[] = $record;

            // Log progress every 10k records
            if(count($records) % 10000 === 0)
            {
                Log::info('RCS Dynamic - Record creation progress', [
                    'records_so_far' => count($records),
                    'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2)
                ]);
            }
        }

        // Free memory from hash maps
        unset($processedNumbersMap, $secondPassProcessed);

        if(!empty($records)) 
        {
            Log::info('RCS Dynamic - Inserting records', [
                'record_count' => count($records),
                'numbers_to_process' => count($numbersToProcess),
                'gateway' => $this->gatewayName
            ]);

            // Insert records in batches to avoid "too many placeholders" error
            $insertBatchSize = 1000; // Adjust this based on your database limits
            $totalInserted = 0;
            
            foreach(array_chunk($records, $insertBatchSize) as $chunkIndex => $chunk) 
            {
                RcsSendNumber::insert($chunk);
                $totalInserted += count($chunk);
                
                Log::debug('RCS Dynamic - Inserted batch of records', [
                    'batch_index' => $chunkIndex + 1,
                    'batch_size' => count($chunk),
                    'total_inserted_so_far' => $totalInserted
                ]);
            }
            
            Log::info('RCS Dynamic - All records inserted successfully', [
                'total_records_inserted' => $totalInserted,
                'batch_size_used' => $insertBatchSize
            ]);

            $this->message->update(['numbers_count' => count($records)]);

            // ── Credit recalculation (same as RcsSendNumbersJob) ──────────────
            // Actual credit = creditPerNumber × actual records inserted
            // (excludes blocked / invalid numbers that were filtered out)
            $actualCreditUsed = $this->creditPerNumber * count($records);

            // Store the original estimated credit before correcting
            $originalEstimatedCredit = $this->message->msg_credit;

            // Update msg_credit on the campaign with the real value
            $this->message->update(['msg_credit' => $actualCreditUsed]);

            // Refund excess credits if actual usage < estimated
            if($actualCreditUsed != $originalEstimatedCredit)
            {
                $creditDifference = $originalEstimatedCredit - $actualCreditUsed;

                $creditManageDetail = CreditManage::where('route_id', $this->routeId)
                                    ->where('userid', $this->userId)
                                    ->where('isActive', 1)
                                    ->first();

                if($creditManageDetail)
                {
                    $newBalance = $creditManageDetail->balance + $creditDifference;
                    $creditManageDetail->update(['balance' => $newBalance]);

                    // Update transaction log with actual figures
                    CampaignTransactionLog::where('job_id', $this->message->job_id)
                        ->update([
                            'total_numbers' => count($records),
                            'total_credit'  => $actualCreditUsed,
                            'new_balance'   => $newBalance,
                        ]);

                    Log::info('[RCS Dynamic] Credit adjusted after actual processing', [
                        'job_id'                    => $this->message->job_id,
                        'template_type'             => $this->message->template_type,
                        'credit_per_number'         => $this->creditPerNumber,
                        'original_estimated_credit' => $originalEstimatedCredit,
                        'actual_credit_used'        => $actualCreditUsed,
                        'credit_refunded'           => $creditDifference,
                        'new_balance'               => $newBalance,
                    ]);
                }
            }
            else
            {
                Log::info('[RCS Dynamic] Credit exact — no adjustment needed', [
                    'job_id'             => $this->message->job_id,
                    'template_type'      => $this->message->template_type,
                    'credit_per_number'  => $this->creditPerNumber,
                    'actual_credit_used' => $actualCreditUsed,
                ]);
            }
            // ─────────────────────────────────────────────────────────────────

            $this->processNumbers($numbersToProcess);

            // Cut Off
            if($applyCutoff) 
            {
                SmartCutoff::create([
                    'user_ID' => $this->userId,
                    'job_id' => $this->message->job_id,
                    'msg_count' => count($records),
                    'throughput' => $throughput,
                    'percent' => $cutoffPercentage,
                    'route_ID' => $this->routeId,
                    'min_value' => $min_cut_value,
                    'parent_id' => $parent_id,
                    'created_by' => $this->userId,
                    'created_at' => now(),
                    'updated_at' => null,
                ]);
                
                Log::info('RCS Dynamic - SmartCutoff record created', [
                    'job_id' => $this->message->job_id,
                    'cutoff_percentage' => $cutoffPercentage,
                    'throughput' => $throughput
                ]);
            }
            
            // Clean up cache after successful processing
            Cache::forget($this->messagesCacheKey);

            $executionTime = round(microtime(true) - $startTime, 2);

            Log::info('RcsSendDynamicNumbersJob completed successfully', [
                'job_id' => $this->message->job_id,
                'total_records_inserted' => $totalInserted,
                'execution_time' => $executionTime . 's',
                'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
                'cache_cleared' => $this->messagesCacheKey
            ]);
        }
        else
        {
            // Clean up cache even if no records to insert
            Cache::forget($this->messagesCacheKey);

            Log::warning('RcsSendDynamicNumbersJob: No valid records to insert', [
                'job_id' => $this->message->job_id,
                'total_input_messages' => count($this->messages),
                'cache_cleared' => $this->messagesCacheKey
            ]);
        }

        } catch (\Exception $e) {
            $executionTime = round(microtime(true) - $startTime, 2);

            Log::error('RcsSendDynamicNumbersJob EXCEPTION', [
                'job_id' => $this->message->job_id ?? 'unknown',
                'user_id' => $this->userId,
                'error' => $e->getMessage(),
                'error_file' => $e->getFile(),
                'error_line' => $e->getLine(),
                'execution_time' => $executionTime . 's',
                'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2)
            ]);

            throw $e;
        }
    }

    protected function processNumbers($numbersToProcess)
    {
        $toSend = array_filter($numbersToProcess, fn($n) => $n['is_picked'] == 0);

        if(empty($toSend)) {
            return;
        }

        if(!$this->gatewayName || !$this->endPoint || !$this->accessToken || !$this->clientId) {
            Log::warning('[RCS Dynamic] Missing gateway details, skipping API calls', [
                'gateway' => $this->gatewayName
            ]);
            return;
        }

        $toSendArray = array_values($toSend);

        $concurrency = match($this->gatewayName) {
            'vi'         => 20,   // matches Vi's ~100 req/sec rate limit
            'jio'        => 150,  // no rate-limit issues — maximize throughput
            'valuefirst' => 100,
            'pinnacle'   => 30,
            default      => 50,
        };

        $requestDelayUs = 0;

        $batchSize = match($this->gatewayName) {
            'vi'         => 5000,  // 100k ÷ 5000 = 20 batches
            default      => 0,     // no batching needed
        };

        $batchPauseSeconds = match($this->gatewayName) {
            'vi'         => 1,  // 1 s cool-down between Vi batches
            default      => 0,
        };

        Log::info('[RCS Dynamic] Sending API requests with streaming pool', [
            'job_id' => $this->message->job_id,
            'total_to_send' => count($toSendArray),
            'concurrency' => $concurrency,
            'gateway' => $this->gatewayName
        ]);

        // Log sample payload for debugging
        $firstMasterJobId = $this->generateRandomAlphaNumeric(20);
        $firstPayload = $this->buildDynamicApiPayload($toSendArray[0]['number'], $firstMasterJobId, $toSendArray[0]['message'] ?? null);
        if($firstPayload) {
            Log::debug('[RCS Dynamic][API] Sample request payload', [
                'url' => $firstPayload['url'],
                'data' => json_encode($firstPayload['data']),
                'gateway' => $this->gatewayName
            ]);
        }

        // Use the master_job_ids already stored in the DB for each number
        $masterJobIds = [];
        foreach($toSendArray as $index => $numberData) {
            $masterJobIds[$index] = $numberData['master_job_id'] ?? $this->generateRandomAlphaNumeric(20);
        }

        // Run pool with retry logic for 401/429 errors
        $maxRetries = 3;
        $pendingIndices = array_keys($toSendArray); // All indices to send
        $totalSuccess = 0;
        $totalFail = 0;

        for($attempt = 0; $attempt <= $maxRetries; $attempt++)
        {
            if(empty($pendingIndices)) {
                break;
            }

            if($attempt > 0) {
                // Longer backoff before retry: 5s, 15s, 30s
                $backoffSeconds = match($attempt) {
                    1 => 5,
                    2 => 15,
                    3 => 30,
                    default => 30,
                };

                Log::info('[RCS Dynamic] Retrying failed requests', [
                    'job_id' => $this->message->job_id,
                    'attempt' => $attempt + 1,
                    'pending_count' => count($pendingIndices),
                    'backoff_seconds' => $backoffSeconds,
                    'concurrency' => $concurrency,
                    'request_delay_us' => $requestDelayUs
                ]);

                sleep($backoffSeconds);

                // Halve concurrency on every retry
                $concurrency = max(5, intval($concurrency / 2));

                // Add / double per-request delay on retry so we back off after 429.
                // For Vi the initial delay is 0, so seed it to 10ms on first retry.
                if($requestDelayUs === 0) {
                    $requestDelayUs = ($this->gatewayName === 'vi') ? 10000 : 0; // 10ms seed for Vi
                } else {
                    $requestDelayUs = intval($requestDelayUs * 2); // double on subsequent retries
                }
            }

            $has401 = false;
            $has429 = false;
            $failedIndices = [];
            $batchSuccess = 0;
            $batchFail = 0;
            $firstException = null;
            $firstErrorStatus = null;
            $firstErrorBody = null;

            // Build Guzzle client with current token
            $client = new GuzzleClient([
                'connect_timeout' => 15,
                'timeout' => 30,
                'http_errors' => false,
                'headers' => match($this->gatewayName) {
                    'pinnacle' => [
                        'Content-Type' => 'application/json',
                        'apikey' => $this->accessToken,
                        'botid' => $this->clientId,
                    ],
                    default => [
                        'Content-Type' => 'application/json',
                        'Authorization' => 'Bearer ' . $this->accessToken,
                    ],
                },
                'curl' => [
                    CURLOPT_TCP_KEEPALIVE => 1,
                    CURLOPT_TCP_KEEPIDLE => 30,
                ],
            ]);

            // Split pending indices into batches if batchSize > 0
            $batches = ($batchSize > 0)
                ? array_chunk($pendingIndices, $batchSize)
                : [$pendingIndices];

            Log::info('[RCS Dynamic] Processing with batching', [
                'job_id' => $this->message->job_id,
                'attempt' => $attempt + 1,
                'total_pending' => count($pendingIndices),
                'batch_count' => count($batches),
                'batch_size' => $batchSize ?: 'all',
                'batch_pause_seconds' => $batchPauseSeconds,
                'concurrency' => $concurrency,
                'request_delay_us' => $requestDelayUs
            ]);

            foreach($batches as $batchIndex => $batchIndices)
            {
                // Pause between batches (not before first batch)
                if($batchIndex > 0 && $batchPauseSeconds > 0) {
                    Log::debug('[RCS Dynamic] Pausing between batches', [
                        'batch_index' => $batchIndex + 1,
                        'pause_seconds' => $batchPauseSeconds
                    ]);
                    sleep($batchPauseSeconds);
                }

                // Generator yields requests with throttling delay
                $currentBatch = $batchIndices;
                $currentDelayUs = $requestDelayUs;
                $requests = function () use ($toSendArray, $masterJobIds, $currentBatch, $currentDelayUs) {
                    foreach($currentBatch as $index) {
                        $numberData = $toSendArray[$index];
                        $payload = $this->buildDynamicApiPayload(
                            $numberData['number'],
                            $masterJobIds[$index],
                            $numberData['message'] ?? null
                        );
                        if($payload) {
                            // Throttle: add delay between yielding requests to control rate
                            if($currentDelayUs > 0) {
                                usleep($currentDelayUs);
                            }
                            yield $index => new GuzzleRequest(
                                'POST',
                                $payload['url'],
                                [],
                                json_encode($payload['data'])
                            );
                        }
                    }
                };

                try {
                    $pool = new GuzzlePool($client, $requests(), [
                        'concurrency' => $concurrency,
                        'fulfilled' => function ($response, $index) use (&$batchSuccess, &$batchFail, &$has401, &$has429, &$failedIndices, &$firstErrorStatus, &$firstErrorBody) {
                            $status = $response->getStatusCode();
                            if($status >= 200 && $status < 300) {
                                $batchSuccess++;
                            } else {
                                $batchFail++;
                                $failedIndices[] = $index;

                                if($status === 401) {
                                    $has401 = true;
                                }
                                if($status === 429) {
                                    $has429 = true;
                                }
                                if($firstErrorBody === null) {
                                    $firstErrorStatus = $status;
                                    $firstErrorBody = substr((string) $response->getBody(), 0, 2000);
                                }
                            }
                        },
                        'rejected' => function ($reason, $index) use (&$batchFail, &$failedIndices, &$firstException) {
                            $batchFail++;
                            $failedIndices[] = $index;
                            if($firstException === null) {
                                $firstException = [
                                    'message' => $reason->getMessage(),
                                    'code' => $reason->getCode(),
                                    'type' => get_class($reason)
                                ];
                            }
                        },
                    ]);

                    $pool->promise()->wait();
                } catch(\Throwable $e) {
                    Log::error('[RCS Dynamic][POOL] Pool error', [
                        'job_id' => $this->message->job_id,
                        'attempt' => $attempt + 1,
                        'batch_index' => $batchIndex + 1,
                        'error' => $e->getMessage()
                    ]);
                }

                // If we start getting 429 mid-way through batches, stop and retry all remaining
                if($has429) {
                    Log::warning('[RCS Dynamic] 429 detected mid-batch, stopping remaining batches for retry', [
                        'batch_index' => $batchIndex + 1,
                        'total_batches' => count($batches)
                    ]);
                    // Add remaining un-processed batch indices to failed
                    for($remainingBatch = $batchIndex + 1; $remainingBatch < count($batches); $remainingBatch++) {
                        $failedIndices = array_merge($failedIndices, $batches[$remainingBatch]);
                    }
                    break; // Exit batch loop to go to retry
                }
            }

            $totalSuccess += $batchSuccess;

            if($batchFail > 0) {
                Log::warning('[RCS Dynamic][API] Pool batch results', [
                    'job_id' => $this->message->job_id,
                    'attempt' => $attempt + 1,
                    'batch_success' => $batchSuccess,
                    'batch_failed' => $batchFail,
                    'first_error_status' => $firstErrorStatus,
                    'first_error_body' => $firstErrorBody,
                    'first_exception' => $firstException,
                    'has_401' => $has401,
                    'has_429' => $has429,
                    'gateway' => $this->gatewayName
                ]);
            }

            // Handle 401 - refresh token before retry
            if($has401) {
                Log::info('[RCS Dynamic] Token expired (401), refreshing for retry', [
                    'job_id' => $this->message->job_id
                ]);
                $this->refreshToken();
            }

            // Set pending indices to only the failed ones for next retry
            if($has401 || $has429) {
                $pendingIndices = $failedIndices;
            } else {
                // Other errors (5xx, network) - no point retrying all
                $totalFail += $batchFail;
                $pendingIndices = []; // Clear so final count doesn't double-add
                break;
            }
        }

        // Any remaining pending after all retries count as failures
        $totalFail += count($pendingIndices);

        Log::info('[RCS Dynamic] All API requests processed', [
            'job_id' => $this->message->job_id,
            'total_sent' => count($toSendArray),
            'total_success' => $totalSuccess,
            'total_failed' => $totalFail,
            'retries_used' => min($maxRetries, max(0, $maxRetries - count($pendingIndices)))
        ]);
    }

    /* ========================== BUILD API PAYLOAD (for GuzzlePool) ========================== */
    protected function buildDynamicApiPayload($number, $masterJobId, $message = null): ?array
    {
        return match($this->gatewayName) {
            'jio'        => $this->buildJioPayload($number, $masterJobId, $message),
            'vi'         => $this->buildViPayload($number, $masterJobId, $message),
            'valuefirst' => $this->buildValueFirstPayload($number, $masterJobId, $message),
            'pinnacle'   => $this->buildPinnaclePayload($number, $masterJobId, $message),
            default      => null
        };
    }

    protected function buildJioPayload($number, $masterJobId, $message = null): array
    {
        $endpoint = rtrim($this->endPoint, '/') . "/v1/messaging/users/{$number}/assistantMessages/async?messageId={$masterJobId}";
        $sendMsg = $message ?: $this->getMessageForNumber($number);

        if($this->message->template_type === 'rich_card')
        {
            $imgUrl = null;

            if($this->message->image_path)
            {
                $imgUrl = filter_var($this->message->image_path, FILTER_VALIDATE_URL)
                    ? $this->message->image_path
                    : url($this->message->image_path);
            }
            elseif($this->imagePath)
            {
                $imgUrl = filter_var($this->imagePath, FILTER_VALIDATE_URL)
                    ? $this->imagePath
                    : url($this->imagePath);
            }

            if($imgUrl && !$this->urlLink)
            {
                $data = [
                    'content' => [
                        'richCardDetails' => [
                            'standalone' => [
                                'cardOrientation' => "VERTICAL",
                                'content' => [
                                    'cardTitle' => $this->cardTitle,
                                    'cardDescription' => urldecode($sendMsg),
                                    'cardMedia' => [
                                        'mediaHeight' => "TALL",
                                        'contentInfo' => ['fileUrl' => $imgUrl]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ];
            }
            elseif($imgUrl && $this->urlLink)
            {
                $data = [
                    'content' => [
                        'richCardDetails' => [
                            'standalone' => [
                                'cardOrientation' => "VERTICAL",
                                'content' => [
                                    'cardTitle' => $this->cardTitle,
                                    'cardDescription' => urldecode($sendMsg),
                                    'cardMedia' => [
                                        'mediaHeight' => "TALL",
                                        'contentInfo' => ['fileUrl' => $imgUrl]
                                    ],
                                    'suggestions' => [
                                        [
                                            'action' => [
                                                'plainText' => $this->buttonText,
                                                'postBack' => ['data' => " "],
                                                'openUrl' => ['url' => $this->urlLink]
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ];
            }
            else
            {
                $data = ['content' => ['plainText' => urldecode($sendMsg)]];
            }
        }
        else
        {
            $data = ['content' => ['plainText' => urldecode($sendMsg)]];
        }

        return ['url' => $endpoint, 'data' => $data];
    }

    protected function buildViPayload($number, $masterJobId, $message = null): array
    {
        $endpoint = rtrim($this->endPoint, '/') .
            "/rcs/v1/phones/{$number}/agentMessages/async?sendGipLink=false&messageId={$masterJobId}&botId={$this->clientId}";

        $sendMsg = $message ?: $this->getMessageForNumber($number);

        // Prefer the real values from the uploaded row; only guess from the resolved
        // text if this preview was generated before that data was available (see
        // getVariablesForNumber()).
        $customParams = $this->getVariablesForNumber($number);
        if(empty($customParams))
        {
            $customParams = $this->extractCustomParams($this->message->message, $sendMsg);
        }

        $templatePayload = ['templateCode' => $this->message->template_name];

        if (!empty($customParams)) {
            // Vi's API expects customParams as a JSON-encoded string, not a nested object
            // (per their docs: "customParams":"{\"name\":\"user1\"}").
            $templatePayload['customParams'] = json_encode($customParams, JSON_UNESCAPED_UNICODE);
        } else {
            // Warn if the template has variables but extraction failed
            preg_match_all('/\{([^}]+)\}|\[([^\]]+)\]/', $this->message->message, $varMatches);
            $hasVars = !empty(array_filter(array_merge($varMatches[1], $varMatches[2])));
            if ($hasVars) {
                Log::warning('[RCS Vi] customParams extraction failed — template has variables but none could be extracted', [
                    'job_id'          => $this->message->job_id,
                    'template_name'   => $this->message->template_name,
                    'number'          => $number,
                    'send_msg_sample' => mb_substr($sendMsg, 0, 100),
                ]);
            }
        }

        $data = [
            'contentMessage' => [
                'templateMessage' => $templatePayload,
            ]
        ];

        return ['url' => $endpoint, 'data' => $data];
    }

    protected function buildValueFirstPayload($number, $masterJobId, $message = null): array
    {
        $sendMsg = $message ?: $this->getMessageForNumber($number);

        // Prefer the real values from the uploaded row; only guess from the resolved
        // text if this preview was generated before that data was available.
        $customParams = $this->getVariablesForNumber($number);
        if(empty($customParams) && $this->isVariable)
        {
            $customParams = $this->extractCustomParams($this->message->message, $sendMsg);
        }

        $data = [
            'apiver' => '1.0',
            'rcs' => [
                'messages' => [
                    [
                        'id' => $masterJobId,
                        'msgtype' => $this->message->template_type === 'rich_card' ? 'rich_card' : 'text',
                        'template_name' => $this->message->template_name,
                        'addresses' => [
                            [
                                'seq' => $masterJobId,
                                'to' => $number,
                                'from' => $this->clientId,
                                'custom_params' => $this->isVariable
                                    ? $customParams
                                    : null
                            ]
                        ]
                    ]
                ]
            ]
        ];

        return ['url' => $this->endPoint, 'data' => $data];
    }

    protected function buildPinnaclePayload($number, $masterJobId, $message = null): array
    {
        // rcs_gateway.end_point for pinnacle should be stored as "https://rcsapi.pinnacle.in/api"
        $endpoint = rtrim($this->endPoint, '/') . '/v1/send-message';

        $sendMsg = $message ?: $this->getMessageForNumber($number);

        // Prefer the real values from the uploaded row; only guess from the resolved
        // text if this preview was generated before that data was available.
        $customParams = $this->getVariablesForNumber($number);
        if(empty($customParams))
        {
            $customParams = $this->extractCustomParams($this->message->message, $sendMsg);
        }

        $variables = [];

        foreach($customParams as $key => $value)
        {
            $variables[] = ['key' => $key, 'value' => $value];
        }

        $data = [
            'category' => match(strtolower($this->storedBotType)) {
                'promo' => 'promotional',
                'trans' => 'transactional',
                default => strtolower($this->storedBotType),
            },
            'correlationId' => $masterJobId,
            'messages' => [
                [
                    'to' => $number,
                    'templateId' => $this->gatewayTemplateId ?: $this->message->template_name,
                    'variables' => $variables,
                    'isSMSFallbackRequired' => false,
                ]
            ]
        ];

        return ['url' => $endpoint, 'data' => $data];
    }

    protected function refreshToken()
    {
        try
        {
            $response = null;

            if($this->gatewayName === 'jio')
            {
                $tokenEndpoint = 'https://tgs.businessmessaging.jio.com/v1/oauth/token';

                $response = Http::asForm()->post($tokenEndpoint, [
                    'grant_type' => 'client_credentials',
                    'client_id' => $this->clientId,
                    'client_secret' => $this->getClientSecret(),
                    'scope' => 'read'
                ]);
            }
            elseif($this->gatewayName === 'vi')
            {
                $authHeader = 'Basic ' . base64_encode($this->clientId . ':' . $this->getClientSecret());

                $response = Http::withHeaders([
                    'Authorization' => $authHeader,
                    'Content-Type' => 'application/x-www-form-urlencoded',
                    'Accept' => 'application/json'
                ])->post('https://auth.virbm.in/auth/oauth/token?grant_type=client_credentials');
            }
            else
            {
                Log::warning('[RCS Dynamic][TOKEN] Token refresh not supported for gateway', [
                    'gateway' => $this->gatewayName
                ]);
                return;
            }

            if($response && $response->successful())
            {
                $tokenData = $response->json();

                if(isset($tokenData['access_token']))
                {
                    $this->accessToken = $tokenData['access_token'];

                    RcsToken::where('id', $this->tokenId)
                            ->update([
                                'access_token' => $this->accessToken,
                                'token_expires_at' => isset($tokenData['expires_in'])
                                    ? now()->addSeconds($tokenData['expires_in'] - 60)
                                    : null,
                                'updated_at' => now()
                            ]);

                    Log::info('[RCS Dynamic][TOKEN] Token refreshed successfully', [
                        'job_id' => $this->message->job_id,
                        'gateway' => $this->gatewayName
                    ]);
                }
                else
                {
                    Log::error('[RCS Dynamic][TOKEN] No access_token in response', [
                        'gateway' => $this->gatewayName,
                        'response' => $tokenData
                    ]);
                }
            }
            else
            {
                Log::error('[RCS Dynamic][TOKEN] Failed to refresh token', [
                    'gateway' => $this->gatewayName,
                    'status' => $response ? $response->status() : 'no response',
                    'response' => $response ? $response->body() : 'no response'
                ]);
            }

        }
        catch(\Exception $e)
        {
            Log::error('[RCS Dynamic][TOKEN] Exception during token refresh', [
                'gateway' => $this->gatewayName,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
        }
    }

    protected function getClientSecret()
    {
        $tokenDetail = RcsToken::where('id', $this->tokenId)
                        ->where('isActive', 1)
                        ->first();

        return $tokenDetail ? $tokenDetail->client_secret : null;
    }

    protected function extractCustomParams($templateMessage, $requestMessage)
    {
        // Match both {param} and [param] placeholder formats
        preg_match_all('/\{([^}]+)\}|\[([^\]]+)\]/', $templateMessage, $matches);

        // Merge results from both formats, removing empty strings
        $paramNames = array_values(array_unique(array_filter(
            array_merge($matches[1], $matches[2])
        )));

        $params = [];

        foreach($paramNames as $paramName)
        {
            $value = $this->getParamValue($paramName, $templateMessage, $requestMessage);
            // Only include params with a non-null, non-empty value
            if ($value !== null && $value !== '') {
                $params[$paramName] = $value;
            }
        }

        return $params;
    }

    protected function getParamValue($paramName, $templateMessage, $requestMessage)
    {
        // Primary: reliable string-based context extraction (works for long templates)
        $value = $this->extractByContext($paramName, $templateMessage, $requestMessage);
        if ($value !== null && $value !== '') {
            return $value;
        }

        // Fallback: regex-based extraction (may fail on very long templates)
        $pattern = $this->createExtractionPattern($templateMessage, $paramName);
        if (@preg_match($pattern, $requestMessage, $matches)) {
            $extracted = trim($matches[1] ?? '');
            if ($extracted !== '') {
                return $extracted;
            }
        }

        return null;
    }

    /**
     * Extract a variable's value by locating its surrounding context (prefix/suffix)
     * in the resolved message. More reliable than regex for long templates.
     */
    protected function extractByContext($paramName, $templateMessage, $requestMessage)
    {
        // Try [param] format first, then {param}
        foreach ([('[' . $paramName . ']'), ('{' . $paramName . '}')] as $placeholder) {
            $pos = strpos($templateMessage, $placeholder);
            if ($pos === false) {
                continue;
            }

            $prefix = substr($templateMessage, 0, $pos);
            $suffix = substr($templateMessage, $pos + strlen($placeholder));

            // Locate the start position after the prefix
            if ($prefix !== '') {
                $prefixPos = strpos($requestMessage, $prefix);
                if ($prefixPos === false) {
                    continue;
                }
                $start = $prefixPos + strlen($prefix);
            } else {
                $start = 0;
            }

            // Locate the end position using the suffix as anchor (first 60 chars is enough)
            if ($suffix !== '') {
                $suffixAnchor = substr(ltrim($suffix), 0, 60);
                $suffixPos = strpos($requestMessage, $suffixAnchor, $start);
                if ($suffixPos === false) {
                    continue;
                }
                $value = substr($requestMessage, $start, $suffixPos - $start);
            } else {
                $value = substr($requestMessage, $start);
            }

            $value = trim($value);
            if ($value !== '') {
                return $value;
            }
        }

        return null;
    }

    protected function createExtractionPattern($templateMessage, $paramName)
    {
        $escapedTemplate = preg_quote($templateMessage, '/');

        // Replace the target param placeholder — support both {param} and [param] formats
        $escapedTemplate = str_replace('\{' . $paramName . '\}', '(.+?)', $escapedTemplate);
        $escapedTemplate = str_replace('\[' . $paramName . '\]', '(.+?)', $escapedTemplate);

        // Replace any remaining {other} placeholders with a wildcard
        $escapedTemplate = preg_replace('/\\\\\{[^}]+\\\\\}/', '.+?', $escapedTemplate);
        // Replace any remaining [other] placeholders with a wildcard
        $escapedTemplate = preg_replace('/\\\\\[[^\]]+\\\\\]/', '.+?', $escapedTemplate);

        return '/^' . $escapedTemplate . '$/s';
    }

    // Real variable values for this number, straight from the uploaded CSV row (see
    // RcsSendMessage::generatePreviewMessages). Empty when the preview was generated before
    // this fix or via the old shared preview endpoint — callers fall back to extractCustomParams().
    protected function getVariablesForNumber($number): array
    {
        foreach($this->messages as $msg)
        {
            $currentNumber = trim($msg['number']);
            $normalizedCurrent = $this->normalizeNumber($currentNumber);
            $normalizedTarget = $this->normalizeNumber($number);

            if($normalizedCurrent === $normalizedTarget)
            {
                return $msg['variables'] ?? [];
            }
        }

        return [];
    }

    protected function getMessageForNumber($number)
    {
        foreach($this->messages as $msg) 
        {
            $currentNumber = trim($msg['number']);
            $normalizedCurrent = $this->normalizeNumber($currentNumber);
            $normalizedTarget = $this->normalizeNumber($number);
            
            if($normalizedCurrent === $normalizedTarget) 
            {
                return $msg['message'];
            }
        }
        
        return $this->message->message ?? '';
    }

    protected function normalizeNumber(string $number): string
    {
        $number = preg_replace('/[^0-9]/', '', $number);

        if(str_starts_with($number, '91') && strlen($number) === 12) 
        {
            return substr($number, 2);
        }
        elseif(strlen($number) === 10 && preg_match('/^[6-9]\d{9}$/', $number)) 
        {
            return $number;
        }

        return $number;
    }
    
    protected function generateRandomAlphaNumeric(int $length = 20): string
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';

        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }

        return $randomString;
    }

    /**
     * Handle job failure
     */
    public function failed(\Throwable $exception): void
    {
        Log::error('RcsSendDynamicNumbersJob failed', [
            'message_id' => $this->message->id ?? 'unknown',
            'job_id' => $this->message->job_id ?? 'unknown',
            'user_id' => $this->userId,
            'gateway' => $this->gatewayName ?? 'unknown',
            'error' => $exception->getMessage(),
            'trace' => $exception->getTraceAsString()
        ]);

        // Update message status to failed if possible
        try {
            if (isset($this->message) && $this->message->id) {
                $this->message->update(['status' => 'failed']);
            }
        } catch (\Exception $e) {
            Log::error('Failed to update message status', [
                'error' => $e->getMessage()
            ]);
        }

        // Clean up cache if job fails
        if (isset($this->messagesCacheKey)) {
            try {
                Cache::forget($this->messagesCacheKey);
                Log::info('Cache cleared after job failure', [
                    'cache_key' => $this->messagesCacheKey
                ]);
            } catch (\Exception $e) {
                Log::error('Failed to clear cache after job failure', [
                    'cache_key' => $this->messagesCacheKey,
                    'error' => $e->getMessage()
                ]);
            }
        }
    }
}