#!/usr/bin/env php
<?php

// Separate upload worker for processing backup uploads asynchronously
// This allows backup creation to continue while uploads happen in the background

// Load configuration first
require_once(__DIR__ . '/../configs/config.php');
require_once('/docker/whp/web/libs/mysqlmgmt.php');
require_once(__DIR__ . '/../libs/BackupTarget.php');
require_once(__DIR__ . '/../libs/BackupStorage.php');
require_once(__DIR__ . '/../libs/BackupHistory.php');
require_once(__DIR__ . '/../libs/BackupLocalStorage.php');

use WHPBackup\BackupTarget;
use WHPBackup\BackupStorage;
use WHPBackup\BackupHistory;
use WHPBackup\BackupLocalStorage;

function log_message($message) {
    $timestamp = date('Y-m-d H:i:s');
    $message = str_replace(["\r", "\n"], ' ', $message);
    $formatted = "[$timestamp] [UploadWorker] $message";
    error_log($formatted);
    @file_put_contents('/var/log/whp-backup.log', $formatted . "\n", FILE_APPEND | LOCK_EX);
}

function process_upload_queue() {
    try {
        $mysql = new mysqlmgmt();
        $db = $mysql->getMySQLConnection();
        $db->exec("USE whp");

        $localStorage = new BackupLocalStorage($db);

        // Get maximum concurrent uploads from settings
        $max_concurrent = 2; // Default value
        if (file_exists('/docker/whp/settings.json')) {
            $settings = json_decode(file_get_contents('/docker/whp/settings.json'), true);
            if ($settings && isset($settings['upload_max_concurrent'])) {
                $max_concurrent = intval($settings['upload_max_concurrent']);
                if ($max_concurrent < 1 || $max_concurrent > 5) {
                    $max_concurrent = 2; // Fallback to default if invalid
                }
            }
        }

        log_message("Using maximum concurrent uploads: $max_concurrent");

        // Count currently processing uploads
        $stmt = $db->prepare("SELECT COUNT(*) FROM backup_upload_queue WHERE status = 'processing'");
        $stmt->execute();
        $processing_count = $stmt->fetchColumn();

        log_message("Currently processing uploads: $processing_count");

        // Calculate how many new uploads we can start
        $available_slots = $max_concurrent - $processing_count;

        if ($available_slots <= 0) {
            log_message("Maximum concurrent uploads reached ($max_concurrent). No new uploads can be started.");
            return;
        }

        log_message("Available upload slots: $available_slots");

        // Get queued uploads (highest priority first, then oldest)
        $db->beginTransaction();
        try {
            $stmt = $db->prepare("
                SELECT buq.id as queue_id, buq.backup_id, buq.priority, buq.retry_count,
                       bh.backup_name, bh.local_path, bh.backup_path, bh.target_id
                FROM backup_upload_queue buq
                JOIN backup_history bh ON buq.backup_id = bh.id
                WHERE buq.status = 'queued'
                AND (buq.next_retry_at IS NULL OR buq.next_retry_at <= NOW())
                AND bh.creation_status = 'created'
                AND bh.local_path IS NOT NULL
                ORDER BY buq.priority ASC, buq.created_at ASC
                LIMIT ? FOR UPDATE
            ");
            $stmt->execute([$available_slots]);
            $uploads = $stmt->fetchAll(PDO::FETCH_ASSOC);

            // Immediately mark selected uploads as 'processing' to prevent other workers from taking them
            if (!empty($uploads)) {
                $queueIds = array_column($uploads, 'queue_id');
                $placeholders = str_repeat('?,', count($queueIds) - 1) . '?';
                $workerId = gethostname() . '_' . getmypid();

                $stmt = $db->prepare("
                    UPDATE backup_upload_queue
                    SET status = 'processing', worker_id = ?, started_at = NOW()
                    WHERE id IN ($placeholders)
                ");
                $stmt->execute(array_merge([$workerId], $queueIds));

                // Update backup history upload status
                $backupIds = array_column($uploads, 'backup_id');
                $stmt = $db->prepare("
                    UPDATE backup_history
                    SET upload_status = 'uploading', upload_started_at = NOW()
                    WHERE id IN ($placeholders)
                ");
                $stmt->execute($backupIds);
            }

            $db->commit();
        } catch (Exception $e) {
            $db->rollback();
            throw $e;
        }

        if (empty($uploads)) {
            log_message("No queued uploads found");
            return;
        }

        log_message("Found " . count($uploads) . " queued uploads");

        foreach ($uploads as $upload) {
            try {
                log_message("Processing upload queue ID: {$upload['queue_id']}, backup ID: {$upload['backup_id']}");

                $success = process_single_upload($db, $localStorage, $upload);

                if ($success) {
                    // Mark as completed
                    mark_upload_completed($db, $upload['queue_id'], $upload['backup_id']);
                    log_message("Upload completed successfully: {$upload['backup_name']}");
                } else {
                    // Mark as failed and schedule retry if applicable
                    handle_upload_failure($db, $upload, "Upload failed");
                }

            } catch (Exception $e) {
                log_message("Upload failed: {$upload['backup_name']} - " . $e->getMessage());
                handle_upload_failure($db, $upload, $e->getMessage());
            }
        }

    } catch (Exception $e) {
        log_message("Error in upload worker: " . $e->getMessage());
    }
}

function process_single_upload($db, $localStorage, $upload) {
    // Verify local file exists
    if (!file_exists($upload['local_path'])) {
        throw new Exception("Local backup file not found: {$upload['local_path']}");
    }

    // Load target configuration
    $target = new BackupTarget($db, $upload['target_id']);
    $credentials = $target->getCredentials();
    $credentials['backup_id'] = $upload['backup_id'];

    $storage = new BackupStorage($credentials);

    // Log upload start
    $fileSize = filesize($upload['local_path']);
    log_message("Starting upload of {$upload['backup_name']} (" . number_format($fileSize/1024/1024, 2) . " MB)");

    // Perform upload
    $startTime = microtime(true);
    $result = $storage->uploadFileViaRclone($upload['local_path'], $upload['backup_path']);
    $uploadTime = microtime(true) - $startTime;

    if (!$result['success']) {
        throw new Exception("Upload failed: " . $result['error']);
    }

    // Log successful upload
    $speed = ($fileSize / 1024 / 1024) / $uploadTime;
    log_message("Successfully uploaded {$upload['backup_name']} in " . number_format($uploadTime, 2) .
               "s (" . number_format($speed, 2) . " MB/s)");

    // Update backup history with upload details
    $stmt = $db->prepare("
        UPDATE backup_history
        SET backup_size = ?, upload_completed_at = NOW()
        WHERE id = ?
    ");
    $stmt->execute([$result['size'], $upload['backup_id']]);

    // Clean up local file
    $localStorage->deleteAfterUpload($upload['local_path'], $upload['backup_id']);

    return true;
}

function mark_upload_completed($db, $queueId, $backupId) {
    // Mark queue item as completed
    $stmt = $db->prepare("
        UPDATE backup_upload_queue
        SET status = 'completed', completed_at = NOW()
        WHERE id = ?
    ");
    $stmt->execute([$queueId]);

    // Mark backup as uploaded
    $stmt = $db->prepare("
        UPDATE backup_history
        SET upload_status = 'uploaded'
        WHERE id = ?
    ");
    $stmt->execute([$backupId]);
}

function handle_upload_failure($db, $upload, $errorMessage) {
    $maxRetries = 3;
    $retryCount = $upload['retry_count'] + 1;

    if ($retryCount <= $maxRetries) {
        // Schedule retry with exponential backoff
        $retryDelay = min(300, 60 * pow(2, $retryCount - 1)); // Max 5 minutes
        $nextRetryAt = date('Y-m-d H:i:s', time() + $retryDelay);

        $stmt = $db->prepare("
            UPDATE backup_upload_queue
            SET status = 'queued', retry_count = ?, next_retry_at = ?, error_message = ?
            WHERE id = ?
        ");
        $stmt->execute([$retryCount, $nextRetryAt, $errorMessage, $upload['queue_id']]);

        // Update backup history
        $stmt = $db->prepare("
            UPDATE backup_history
            SET upload_status = 'pending', upload_attempts = ?
            WHERE id = ?
        ");
        $stmt->execute([$retryCount, $upload['backup_id']]);

        log_message("Upload scheduled for retry {$retryCount}/{$maxRetries} in {$retryDelay}s: {$upload['backup_name']}");
    } else {
        // Mark as permanently failed
        $stmt = $db->prepare("
            UPDATE backup_upload_queue
            SET status = 'failed', completed_at = NOW(), error_message = ?
            WHERE id = ?
        ");
        $stmt->execute([$errorMessage, $upload['queue_id']]);

        $stmt = $db->prepare("
            UPDATE backup_history
            SET upload_status = 'failed', upload_attempts = ?
            WHERE id = ?
        ");
        $stmt->execute([$retryCount, $upload['backup_id']]);

        log_message("Upload permanently failed after {$maxRetries} attempts: {$upload['backup_name']}");
    }
}

function cleanup_stale_uploads($db) {
    try {
        // Ensure database is selected
        $db->exec("USE whp");

        // Find uploads that have been processing for more than 2 hours
        $stmt = $db->prepare("
            UPDATE backup_upload_queue
            SET status = 'queued', worker_id = NULL, error_message = 'Worker timeout - reset for retry'
            WHERE status = 'processing'
            AND started_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)
        ");
        $stmt->execute();
        $affectedRows = $stmt->rowCount();

        if ($affectedRows > 0) {
            log_message("Reset {$affectedRows} stale upload(s) for retry");
        }

        // Reset backup history upload status for reset uploads
        $stmt = $db->prepare("
            UPDATE backup_history bh
            JOIN backup_upload_queue buq ON bh.id = buq.backup_id
            SET bh.upload_status = 'pending'
            WHERE buq.status = 'queued'
            AND bh.upload_status = 'uploading'
        ");
        $stmt->execute();

        return $affectedRows;
    } catch (Exception $e) {
        log_message("Error cleaning up stale uploads: " . $e->getMessage());
        return 0;
    }
}

// Main execution
if (php_sapi_name() === 'cli') {
    $daemonMode = in_array('--daemon', $argv ?? []);
    $pid = getmypid();

    // Graceful shutdown flag
    $shutdownRequested = false;

    // Set up signal handlers for clean shutdown
    if (function_exists('pcntl_signal')) {
        $signalHandler = function($signo) use (&$shutdownRequested, $pid) {
            $sigName = ($signo === SIGTERM) ? 'SIGTERM' : 'SIGINT';
            log_message("Upload worker received {$sigName}, finishing current work...");
            $shutdownRequested = true;
        };
        pcntl_signal(SIGTERM, $signalHandler);
        pcntl_signal(SIGINT, $signalHandler);
    }

    if ($daemonMode) {
        // Daemon mode: continuous loop managed by systemd
        log_message("Backup upload worker started in daemon mode (PID: $pid)");

        // Read poll interval from settings (default 15 seconds)
        $pollInterval = 15;
        if (file_exists('/docker/whp/settings.json')) {
            $settings = json_decode(file_get_contents('/docker/whp/settings.json'), true);
            if ($settings && isset($settings['upload_poll_interval'])) {
                $pollInterval = max(5, min(300, intval($settings['upload_poll_interval'])));
            }
        }

        while (!$shutdownRequested) {
            try {
                $mysql = new mysqlmgmt();
                $db = $mysql->getMySQLConnection();
                $db->exec("USE whp");
                cleanup_stale_uploads($db);
                process_upload_queue();
            } catch (Exception $e) {
                log_message("Error in upload worker loop: " . $e->getMessage());
            }

            // Sleep in small increments to allow signal handling
            for ($i = 0; $i < $pollInterval && !$shutdownRequested; $i++) {
                sleep(1);
                if (function_exists('pcntl_signal_dispatch')) {
                    pcntl_signal_dispatch();
                }
            }
        }

        log_message("Backup upload worker daemon shutting down gracefully");
    } else {
        // Single-pass mode: backwards compatible with cron
        $lockFile = '/tmp/backup-upload-worker.lock';

        // Check if lock file exists
        if (file_exists($lockFile)) {
            $existingPid = trim(file_get_contents($lockFile));

            if ($existingPid && file_exists("/proc/$existingPid")) {
                exit(0);
            } else {
                $lockAge = time() - filemtime($lockFile);
                log_message("Found stale upload worker lock file (age: {$lockAge} seconds, PID: $existingPid), removing it");
                unlink($lockFile);
            }
        }

        if (file_put_contents($lockFile, $pid, LOCK_EX) === false) {
            log_message("Failed to create lock file, exiting");
            exit(1);
        }

        register_shutdown_function(function() use ($lockFile, $pid) {
            if (file_exists($lockFile)) {
                $currentPid = trim(file_get_contents($lockFile));
                if ($currentPid == $pid) {
                    unlink($lockFile);
                }
            }
        });

        log_message("Backup upload worker started (PID: $pid)");

        try {
            $mysql = new mysqlmgmt();
            $db = $mysql->getMySQLConnection();
            $db->exec("USE whp");
            cleanup_stale_uploads($db);
            process_upload_queue();
        } catch (Exception $e) {
            log_message("Fatal error in upload worker: " . $e->getMessage());
        }

        log_message("Backup upload worker finished");

        if (file_exists($lockFile)) {
            $currentPid = trim(file_get_contents($lockFile));
            if ($currentPid == $pid) {
                unlink($lockFile);
            }
        }
    }
} else {
    echo "This script can only be run from command line\n";
    exit(1);
}