<?php

declare(strict_types=1);

namespace App\Framework\Async;

use Fiber;

/**
 * Processor für Background Jobs
 */
final class BackgroundJobProcessor
{
    /** @var array<string, callable> */
    private array $handlers = [];

    private bool $running = false;

    /** @var array<string, BackgroundJob> */
    private array $activeJobs = [];

    public function __construct(
        private readonly AsyncQueue $jobQueue,
        private readonly FiberManager $fiberManager = new FiberManager(),
        private readonly int $maxConcurrentJobs = 10
    ) {
    }

    /**
     * Registriert einen Job-Handler
     */
    public function registerHandler(string $jobType, callable $handler): void
    {
        $this->handlers[$jobType] = $handler;
    }

    /**
     * Fügt einen Job zur Queue hinzu
     */
    public function enqueue(BackgroundJob $job): bool
    {
        return $this->jobQueue->enqueue($job);
    }

    /**
     * Startet den Job-Processor
     */
    public function start(): Fiber
    {
        return $this->fiberManager->async(function () {
            $this->running = true;

            while ($this->running) {
                if (count($this->activeJobs) < $this->maxConcurrentJobs) {
                    $job = $this->jobQueue->tryDequeue();

                    if ($job instanceof BackgroundJob && $job->isReady()) {
                        $this->processJob($job);
                    }
                }

                $this->cleanupCompletedJobs();
                usleep(100000); // 100ms
            }
        });
    }

    /**
     * Stoppt den Job-Processor
     */
    public function stop(): void
    {
        $this->running = false;

        // Warte bis alle aktiven Jobs beendet sind
        while (! empty($this->activeJobs)) {
            $this->cleanupCompletedJobs();
            usleep(100000);
        }
    }

    /**
     * Verarbeitet einen einzelnen Job
     */
    private function processJob(BackgroundJob $job): void
    {
        $fiber = $this->fiberManager->async(function () use ($job) {
            try {
                if (! isset($this->handlers[$job->type])) {
                    throw new \RuntimeException("No handler registered for job type: {$job->type}");
                }

                $handler = $this->handlers[$job->type];
                $startTime = microtime(true);

                // Timeout-Handling
                if ($job->timeout) {
                    $result = $this->fiberManager->withTimeout(
                        fn () => $handler($job->payload, $job),
                        $job->timeout
                    );
                } else {
                    $result = $handler($job->payload, $job);
                }

                $executionTime = microtime(true) - $startTime;

                $this->onJobCompleted($job, $result, $executionTime);

            } catch (\Throwable $e) {
                $this->onJobFailed($job, $e);
            }
        });

        $this->activeJobs[$job->id] = $job;
    }

    /**
     * Handler für erfolgreich abgeschlossene Jobs
     */
    private function onJobCompleted(BackgroundJob $job, mixed $result, float $executionTime): void
    {
        // Log successful completion
        error_log("Job {$job->id} completed successfully in {$executionTime}s");

        // Kann erweitert werden für Callbacks, Metrics, etc.
    }

    /**
     * Handler für fehlgeschlagene Jobs
     */
    private function onJobFailed(BackgroundJob $job, \Throwable $exception): void
    {
        error_log("Job {$job->id} failed: " . $exception->getMessage());

        if ($job->canRetry()) {
            $retryJob = $job->retry($exception);
            $this->enqueue($retryJob);
            error_log("Job {$job->id} scheduled for retry #{$retryJob->retryCount}");
        } else {
            error_log("Job {$job->id} failed permanently after {$job->retryCount} retries");
            $this->onJobDeadLetter($job, $exception);
        }
    }

    /**
     * Handler für permanent fehlgeschlagene Jobs
     */
    private function onJobDeadLetter(BackgroundJob $job, \Throwable $exception): void
    {
        // Hier können Dead Letter Queue, Alerting, etc. implementiert werden
        error_log("Job {$job->id} moved to dead letter queue");
    }

    /**
     * Räumt abgeschlossene Jobs auf
     */
    private function cleanupCompletedJobs(): void
    {
        foreach ($this->activeJobs as $jobId => $job) {
            // Job ist fertig wenn er nicht mehr in running fibers ist
            // (vereinfachte Implementierung)
            unset($this->activeJobs[$jobId]);
        }
    }

    /**
     * Gibt Processor-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'running' => $this->running,
            'active_jobs' => count($this->activeJobs),
            'max_concurrent_jobs' => $this->maxConcurrentJobs,
            'registered_handlers' => array_keys($this->handlers),
            'queue_stats' => $this->jobQueue->getStats(),
        ];
    }
}
