<?php

declare(strict_types=1);

namespace App\Framework\Async;

use DateTime;
use DateTimeInterface;
use Fiber;

/**
 * Scheduler für zeitbasierte asynchrone Aufgaben
 */
final class AsyncScheduler
{
    /** @var array<string, array> */
    private array $jobs = [];

    private bool $running = false;

    public function __construct(
        private readonly FiberManager $fiberManager = new FiberManager()
    ) {
    }

    /**
     * Plant eine täglich wiederkehrende Aufgabe
     */
    public function daily(string $id, callable $task, string $time = '00:00'): void
    {
        $this->jobs[$id] = [
            'task' => $task,
            'type' => 'daily',
            'time' => $time,
            'last_run' => null,
        ];
    }

    /**
     * Plant eine stündlich wiederkehrende Aufgabe
     */
    public function hourly(string $id, callable $task, int $minute = 0): void
    {
        $this->jobs[$id] = [
            'task' => $task,
            'type' => 'hourly',
            'minute' => $minute,
            'last_run' => null,
        ];
    }

    /**
     * Plant eine minütlich wiederkehrende Aufgabe
     */
    public function everyMinute(string $id, callable $task): void
    {
        $this->jobs[$id] = [
            'task' => $task,
            'type' => 'minute',
            'last_run' => null,
        ];
    }

    /**
     * Plant eine Aufgabe mit Intervall in Sekunden
     */
    public function every(string $id, callable $task, int $seconds): void
    {
        $this->jobs[$id] = [
            'task' => $task,
            'type' => 'interval',
            'interval' => $seconds,
            'last_run' => null,
        ];
    }

    /**
     * Plant eine einmalige Aufgabe zu bestimmter Zeit
     */
    public function at(string $id, callable $task, DateTimeInterface $when): void
    {
        $this->jobs[$id] = [
            'task' => $task,
            'type' => 'once',
            'when' => $when,
            'executed' => false,
        ];
    }

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

            while ($this->running) {
                $now = new DateTime();

                foreach ($this->jobs as $id => $job) {
                    if ($this->shouldRun($job, $now)) {
                        $this->executeJob($id, $job);
                    }
                }

                // Prüfe jede Sekunde
                sleep(1);
            }
        });
    }

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

    /**
     * Entfernt einen Job
     */
    public function unschedule(string $id): void
    {
        unset($this->jobs[$id]);
    }

    private function shouldRun(array $job, DateTime $now): bool
    {
        switch ($job['type']) {
            case 'daily':
                $lastRun = $job['last_run'] ? new DateTime($job['last_run']) : null;
                $targetTime = DateTime::createFromFormat('H:i', $job['time']);

                return $now->format('H:i') === $job['time'] &&
                       (! $lastRun || $lastRun->format('Y-m-d') !== $now->format('Y-m-d'));

            case 'hourly':
                $lastRun = $job['last_run'] ? new DateTime($job['last_run']) : null;

                return $now->format('i') == sprintf('%02d', $job['minute']) &&
                       (! $lastRun || $lastRun->format('Y-m-d H') !== $now->format('Y-m-d H'));

            case 'minute':
                $lastRun = $job['last_run'] ? new DateTime($job['last_run']) : null;

                return ! $lastRun || $lastRun->format('Y-m-d H:i') !== $now->format('Y-m-d H:i');

            case 'interval':
                $lastRun = $job['last_run'] ? new DateTime($job['last_run']) : null;

                return ! $lastRun || ($now->getTimestamp() - $lastRun->getTimestamp()) >= $job['interval'];

            case 'once':
                return ! $job['executed'] && $now >= $job['when'];

            default:
                return false;
        }
    }

    private function executeJob(string $id, array &$job): void
    {
        $this->fiberManager->async(function () use ($job) {
            try {
                $job['task']();
            } catch (\Throwable $e) {
                // Log error
                error_log("Scheduled job failed: " . $e->getMessage());
            }
        });

        // Update last run time
        if ($job['type'] === 'once') {
            $this->jobs[$id]['executed'] = true;
        } else {
            $this->jobs[$id]['last_run'] = (new DateTime())->format('Y-m-d H:i:s');
        }
    }

    /**
     * Gibt Scheduler-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'running' => $this->running,
            'total_jobs' => count($this->jobs),
            'job_types' => array_count_values(array_column($this->jobs, 'type')),
        ];
    }
}
