<?php
declare(strict_types=1);

/**
 * Queue processor — runs a job in batches with retry, progress updates, and export.
 */
class QueueProcessor
{
    private int     $jobId;
    private array   $job;
    private Scraper $scraper;
    private Logger  $logger;

    public function __construct(int $jobId)
    {
        $this->jobId   = $jobId;
        $job           = Db::getJob($jobId);
        if (!$job) throw new RuntimeException("Job {$jobId} not found");
        $this->job     = $job;
        $this->scraper = new Scraper(
            (int)$job['timeout_s'],
            $job['user_agent'] ?: DEFAULT_USER_AGENT,
            (int)$job['max_retries'],
            (int)$job['delay_ms']
        );
        $this->logger = new Logger($jobId);
    }

    public function run(): void
    {
        Db::updateJobStatus($this->jobId, 'running');
        $this->logger->info("Job #{$this->jobId} started");

        try {
            $results = Db::getResultsForJob($this->jobId);
            $total   = count($results);

            if ($total === 0) {
                throw new RuntimeException('No URLs to process');
            }

            $processed = 0;
            $success   = 0;
            $failed    = 0;

            foreach ($results as $row) {
                if ($this->jobStopped()) {
                    $this->logger->warning('Job stopped externally');
                    Db::updateJobStatus($this->jobId, 'stopped');
                    return;
                }

                $url = $row['product_url'];
                $this->logger->info("Processing row {$row['row_number']}: {$url}");

                $extracted = $this->scraper->extract($url);

                if ($extracted['image_url']) {
                    Db::updateResult($this->jobId, $row['row_number'], $extracted['image_url'], 'success', null, $extracted['fallback_level']);
                    $this->logger->success("Row {$row['row_number']} ✓ level={$extracted['fallback_level']} → {$extracted['image_url']}");
                    $success++;
                } else {
                    $err = $extracted['error'] ?? 'No image found';
                    Db::updateResult($this->jobId, $row['row_number'], '', 'failed', $err, null);
                    $this->logger->error("Row {$row['row_number']} ✗ {$err}");
                    $failed++;
                }

                $processed++;
                Db::updateJobProgress($this->jobId, $processed, $success, $failed);

                // Delay between requests to avoid rate limiting
                if ($this->job['delay_ms'] > 0) {
                    usleep((int)($this->job['delay_ms'] * 1000));
                }
            }

            // Generate export
            $this->logger->info('Generating Excel export...');
            $allResults = Db::getResultsForJob($this->jobId);
            $filename   = 'product_images_results_' . date('Ymd_His') . '_job' . $this->jobId . '.xlsx';
            $outPath    = EXPORTS_DIR . '/' . $filename;
            ExcelWriter::write($allResults, $outPath);

            // Store export filename in job
            Db::get()->prepare("UPDATE jobs SET filename = ? WHERE id = ?")
                ->execute([$filename, $this->jobId]);

            Db::updateJobStatus($this->jobId, 'done');
            $this->logger->success("Job #{$this->jobId} completed. Success: {$success}, Failed: {$failed}");

        } catch (Throwable $e) {
            Db::updateJobStatus($this->jobId, 'failed', $e->getMessage());
            $this->logger->error('Job failed: ' . $e->getMessage());
            throw $e;
        }
    }

    private function jobStopped(): bool
    {
        $job = Db::getJob($this->jobId);
        return $job && $job['status'] === 'stopped';
    }
}
