<?php

// This script processes broadcasts in small batches and triggers itself to continue.
// It is designed to work on shared hosting with timeouts.

// Prevent browser/client disconnect from aborting the script
ignore_user_abort(true);
set_time_limit(120); // 2 minutes max (hosting usually kills at 60s or 120s)

require_once __DIR__ . '/vendor/autoload.php';

use Bot\Database;
use Bot\Bot;

// Load Config
$config = require __DIR__ . '/config.php';

// Security: Check for secret key
$secret = $config['cron_secret'] ?? null;
$inputKey = $_GET['key'] ?? null;

// Allow running from CLI without key (if user manages to run php command)
if (php_sapi_name() !== 'cli' && $inputKey !== $secret) {
    http_response_code(403);
    die("Access Denied");
}

// Initialize Database & Bot
$db = new Database($config['db_host'], $config['db_user'], $config['db_pass'], $config['db_name']);
$bot = new Bot($config['bot_token'], $db, $config['admins'], $config['owner_id']);

// Check for pending/processing broadcasts
$broadcast = $db->getActiveBroadcast();

if (!$broadcast) {
    echo "No active broadcasts.\n";
    exit;
}

// If status is 'completed' or 'failed', we shouldn't be here (getActiveBroadcast filters them out)
// But if it returns nothing, we exit.

// Attempt to acquire lock
// If already locked by another process (and not expired), this returns false.
if (!$db->acquireBroadcastLock($broadcast['id'])) {
    echo "Broadcast {$broadcast['id']} is currently locked by another process. Exiting.\n";
    exit;
}

// BATCH SETTINGS
$batchSize = 200; // Safe for shared hosting
$delayMs = 50000; // 50ms delay between messages
$batchDelay = 1;  // 1s delay between batches (if multiple iterations in one run)

$bcId = $broadcast['id'];
$targetType = $broadcast['target_type'];
$lastProcessedId = $broadcast['last_processed_id'] ?? 0;

echo "Processing Broadcast #$bcId (Last ID: $lastProcessedId)...\n";

// Fetch Batch
$batch = $db->getBroadcastBatch($targetType, $lastProcessedId, $batchSize);

if (empty($batch)) {
    // No more users/channels to process
    // Mark as completed
    $db->updateBroadcastProgress($bcId, $broadcast['sent_count'], $broadcast['failed_count'], $lastProcessedId);
    // Actually, we need a method to mark completed. Reuse existing `completeBroadcast` but update query?
    // Let's just update status manually here or add method.
    // Database::completeBroadcast updates status='completed'
    // But we need to make sure we don't mark it completed if we just finished a batch but there are more...
    // Wait, getBroadcastBatch returns empty array ONLY if no rows found > lastId.
    // So yes, we are done.
    
    // Check if we really reached the end or just gap?
    // With > lastId and ORDER BY id ASC, empty means we passed the highest ID.
    
    // Use public method to complete
    echo "Marking broadcast $bcId as completed...\n";
    
    if (!method_exists($db, 'completeBroadcast')) {
        echo "CRITICAL ERROR: method 'completeBroadcast' not found in Database.php!\n";
        echo "PLEASE UPLOAD src/Database.php AGAIN.\n";
        exit;
    }
    
    try {
        $db->completeBroadcast($bcId);
        echo "Broadcast Completed Successfully.\n";
        // Double check status?
        $check = $db->getBroadcastById($bcId);
        echo "Final Status: " . ($check['status'] ?? 'unknown') . "\n";
        
    } catch (\Throwable $e) {
        echo "Error marking complete: " . $e->getMessage() . "\n";
    }
    
    exit;
}

// Process Batch
$sent = $broadcast['sent_count'];
$failed = $broadcast['failed_count'];
$newLastId = $lastProcessedId;

$text = $broadcast['message_text'];
$contentType = $broadcast['content_type'];
$fileId = $broadcast['file_id'];
$buttons = isset($broadcast['buttons']) ? json_decode($broadcast['buttons'], true) : null;

foreach ($batch as $row) {
    // IMPORTANT: Use the internal ID for cursor
    $newLastId = $row['id'];
    $targetId = ($targetType === 'all_users') ? $row['user_id'] : $row['chat_id'];
    
    // Personalize
    $personalizedText = $text;
    if ($targetType === 'all_users' && $text) {
        $firstName = $row['first_name'] ?? '';
        $username = $row['username'] ?? '';
        
        // Case-insensitive replacement
        $personalizedText = str_ireplace(
            ['{name}', '{fullname}', '{username}', '{first_name}'],
            [$firstName, $firstName, $username ? "@$username" : '', $firstName],
            $text
        );
    }
    
    try {
        // We reuse the private sendTemplateMessage or logic?
        // Bot::sendTemplateMessage is public now? No, outline said it is.
        // But broadcast needs specific types support.
        // Let's reuse logic from cron.php or duplicate simple send logic.
        // Better: Duplicate simple send logic to avoid dependency on private methods if any.
        // Bot has sendPhoto, sendMessage etc. public.
        
        $replyMarkup = $buttons ? ['inline_keyboard' => $buttons] : null;
        
        switch ($contentType) {
            case 'photo': $bot->sendPhoto($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'video': $bot->sendVideo($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'animation': $bot->sendAnimation($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'document': $bot->sendDocument($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'audio': $bot->sendAudio($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'voice': $bot->sendVoice($targetId, $fileId, $personalizedText, $replyMarkup); break;
            case 'sticker': $bot->sendSticker($targetId, $fileId); break;
            case 'text': 
            default:
                $bot->sendMessage($targetId, $personalizedText, $replyMarkup); 
                break;
        }
        $sent++;
    } catch (\Exception $e) {
        $failed++;
        // Rate limit handling
        if (strpos($e->getMessage(), '429') !== false) {
             preg_match('/retry after (\d+)/i', $e->getMessage(), $matches);
             $sleep = isset($matches[1]) ? (int)$matches[1] : 5;
             sleep($sleep);
        }
    }
    
    usleep($delayMs);
}

// Update Progress
$db->updateBroadcastProgress($bcId, $sent, $failed, $newLastId);

// FAIL-SAFE: Check if we reached the total targets
// This prevents "stuck at 100%" if the next batch doesn't trigger or if calculation is off
$totalTargets = $broadcast['total_targets'];
$processedCount = $sent + $failed;

echo "Status: $processedCount / $totalTargets processed.\n";

if ($processedCount >= $totalTargets) {
    echo "Fail-safe: Processed count ($processedCount) >= Total targets ($totalTargets). Forcing completion.\n";
    try {
        $db->completeBroadcast($bcId);
        echo "Broadcast marked as completed successfully.\n";
    } catch (\Throwable $e) {
        echo "Error marking broadcast as complete: " . $e->getMessage() . "\n";
    }
    // Release lock just in case, though completeBroadcast clears it (or sets locked_at=NULL)
    // But completeBroadcast sets status='completed', so lock doesn't matter much.
    // We exit here to stop chaining.
    exit;
}

// Release Lock immediately so the next chained request can pick it up
$db->releaseBroadcastLock($bcId);

// SELF-CHAIN: Trigger next batch
// We use non-blocking request if possible, or just a timeout-limited request.
// fsockopen is best for fire-and-forget
$siteUrl = $config['site_url'];
// Parse URL
$parts = parse_url($siteUrl);
$host = $parts['host'];
$scheme = $parts['scheme'] ?? 'http';
$port = $parts['port'] ?? ($scheme === 'https' ? 443 : 80);
$path = $parts['path'] ?? '/';
// Ensure path ends with / or is correct directory
// We assume site_url points to the folder where process_broadcast.php is.
// If site_url is "http://example.com/bot", path is "/bot"
// We need "/bot/process_broadcast.php"
$scriptPath = rtrim($path, '/') . '/process_broadcast.php';
$query = "?key=" . $secret;

echo "Triggering next batch...\n";

try {
    if ($scheme === 'https') $host = 'ssl://' . $host;
    
    $fp = fsockopen($host, $port, $errno, $errstr, 5);
    if ($fp) {
        $out = "GET " . $scriptPath . $query . " HTTP/1.1\r\n";
        $out .= "Host: " . $parts['host'] . "\r\n";
        $out .= "Connection: Close\r\n\r\n";
        fwrite($fp, $out);
        fclose($fp);
        echo "Chained successfully.\n";
    } else {
        echo "Failed to chain: $errstr ($errno)\n";
    }
} catch (\Exception $e) {
    echo "Chain exception: " . $e->getMessage() . "\n";
}

exit;
