<?php

declare(strict_types=1);

namespace App\Framework\Worker;

use App\Framework\CommandBus\CommandBus;
use App\Framework\Console\ConsoleColor;
use App\Framework\Console\ConsoleOutput;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\Timer;
use App\Framework\DI\Container;
use App\Framework\Queue\Queue;
use App\Framework\Scheduler\Services\SchedulerService;

final class Worker
{
    private int $processedJobs = 0;

    private Timestamp $startTime;

    private bool $running = false;

    private readonly Timer $timer;

    private readonly Clock $clock;

    private readonly ScheduleDiscoveryService $scheduleDiscovery;

    private readonly SchedulerService $scheduler;

    private Timestamp $lastSchedulerCheck;

    public function __construct(
        private readonly Container     $container,
        private readonly Queue     $queue,
        private readonly CommandBus    $commandBus,
        private readonly ConsoleOutput $output
    ) {
        $this->timer = $this->container->get(Timer::class);
        $this->clock = $this->container->get(Clock::class);
        $this->scheduler = $this->container->get(SchedulerService::class);
        $this->scheduleDiscovery = $this->container->get(ScheduleDiscoveryService::class);

        $this->startTime = $this->clock->time();
        $this->lastSchedulerCheck = $this->clock->time();

        // Discover and register scheduled tasks
        $registered = $this->scheduleDiscovery->discoverAndRegister();
        if ($registered > 0) {
            $this->output->writeLine("📅 {$registered} geplante Tasks registriert", ConsoleColor::BRIGHT_CYAN);
        }
    }

    public function start(): void
    {
        $this->running = true;
        $this->output->writeLine('🚀 Worker gestartet', ConsoleColor::BRIGHT_GREEN);

        while ($this->running) {
            $this->processJob();
        }
    }

    public function stop(): void
    {
        $this->running = false;
        $this->output->writeLine('🛑 Worker gestoppt', ConsoleColor::BRIGHT_RED);
    }

    private function processJob(): void
    {
        // Check scheduler every 10 seconds
        $this->checkScheduler();

        $job = $this->queue->pop();

        if ($job) {
            $this->handleJob($job);
        } else {
            $this->idle();
        }
    }

    private function checkScheduler(): void
    {
        $now = $this->clock->time();
        $secondsSinceLastCheck = $now->diff($this->lastSchedulerCheck)->toSeconds();

        // Check scheduler every 10 seconds
        if ($secondsSinceLastCheck >= 10) {
            $this->lastSchedulerCheck = $now;

            $dueTasks = $this->scheduler->getDueTasks($now);

            foreach ($dueTasks as $task) {
                try {
                    $this->output->writeLine("⏰ Führe geplanten Task aus: {$task->taskId}", ConsoleColor::BRIGHT_YELLOW);
                    $result = $this->scheduler->executeTask($task);

                    if ($result->success) {
                        $this->output->writeLine("✅ Task erfolgreich: {$task->taskId}", ConsoleColor::BRIGHT_GREEN);
                    } else {
                        $this->output->writeLine("❌ Task fehlgeschlagen: {$task->taskId}", ConsoleColor::BRIGHT_RED);
                    }
                } catch (\Throwable $e) {
                    $this->output->writeLine("❌ Fehler bei Task {$task->taskId}: {$e->getMessage()}", ConsoleColor::BRIGHT_RED);
                    error_log("Scheduled task error: {$e->getMessage()}");
                }
            }
        }
    }

    private array $processedJobHashes = [];

    private function handleJob(object $job): void
    {
        try {
            $this->processedJobs++;
            $jobClass = $job::class;

            // Job-Hash für Duplicate-Detection
            $jobHash = md5(serialize($job));

            if (isset($this->processedJobHashes[$jobHash])) {
                $this->output->writeLine("⚠️  DUPLICATE JOB erkannt #{$this->processedJobs}: " . $jobClass, ConsoleColor::BRIGHT_RED);
                $this->output->writeLine("   Bereits verarbeitet: " . $this->processedJobHashes[$jobHash] . " mal", ConsoleColor::RED);
                $this->processedJobHashes[$jobHash]++;

                // Nach 5 Duplikaten Worker stoppen
                if ($this->processedJobHashes[$jobHash] > 5) {
                    $this->output->writeLine("🛑 Zu viele Duplikate - Worker wird gestoppt", ConsoleColor::BRIGHT_RED);
                    $this->stop();

                    return;
                }
            } else {
                $this->processedJobHashes[$jobHash] = 1;
            }

            $this->output->writeLine("🔄 Verarbeite Job #{$this->processedJobs}: " . $jobClass, ConsoleColor::BRIGHT_YELLOW);

            $this->commandBus->dispatch($job);

            $this->output->writeLine("✅ Job #{$this->processedJobs} erfolgreich verarbeitet", ConsoleColor::BRIGHT_GREEN);
            $this->showStatistics();
        } catch (\Throwable $e) {
            $this->handleError($job, $e);
        }
    }

    private function handleError(object $job, \Throwable $e): void
    {
        $this->output->writeLine("❌ Fehler beim Verarbeiten von Job #{$this->processedJobs}:", ConsoleColor::BRIGHT_RED);
        $this->output->writeLine('   Typ: ' . $job::class, ConsoleColor::RED);
        $this->output->writeLine('   Fehler: ' . $e->getMessage(), ConsoleColor::RED);
        $this->output->writeLine('   Datei: ' . $e->getFile() . ':' . $e->getLine(), ConsoleColor::RED);

        error_log('Worker Error: ' . $e->getMessage());
    }

    private function showStatistics(): void
    {
        $runtime = $this->clock->time()->diff($this->startTime)->toSeconds();
        $avgJobsPerSecond = $runtime > 0 ? round($this->processedJobs / $runtime, 2) : 0;
        $this->output->writeLine("📊 Statistiken: {$this->processedJobs} Jobs | Laufzeit: {$runtime}s | ⌀ {$avgJobsPerSecond} Jobs/s", ConsoleColor::BRIGHT_CYAN);
        $this->output->newLine();
    }

    private function idle(): void
    {
        static $idleCycles = 0;
        $idleCycles++;

        if ($idleCycles % 100 === 0) {
            $runtime = $this->clock->time()->diff($this->startTime)->toSeconds();
            $this->output->writeLine("💓 Heartbeat: {$this->processedJobs} Jobs verarbeitet | Laufzeit: {$runtime}s", ConsoleColor::BRIGHT_MAGENTA);
        }

        $this->timer->sleep(Duration::fromMilliseconds(100));
    }
}
