<?php

declare(strict_types=1);

class CronManager
{
    private static array $tasks = [];
    private static array $failures = [];

    

    public static function register(string $name, int $intervalSeconds, callable $callback): void
    {
        self::$tasks[$name] = [
            'interval' => $intervalSeconds,
            'callback' => $callback,
        ];
    }

    

    public static function run(): void
    {
        if (!App::isInstalled() || empty(self::$tasks)) {
            return;
        }

        foreach (self::$tasks as $name => $task) {
            self::executeTask($name, $task['interval'], $task['callback']);
        }
    }

    private static function executeTask(string $name, int $intervalSeconds, callable $callback): void
    {
        $key = 'cron_' . $name . '_last';
        $failKey = 'cron_' . $name . '_fails';
        $now = time();
        $lastRun = (int) Setting::get($key, '0');

         
        $consecutiveFails = (int) Setting::get($failKey, '0');
        if ($consecutiveFails > 0) {
             
            $backoff = min(3600, $intervalSeconds * (1 << min($consecutiveFails, 6)));
            if (($now - $lastRun) < $backoff) {
                return;
            }
        } elseif (($now - $lastRun) < $intervalSeconds) {
            return;
        }

         
        $affected = Database::query(
            "UPDATE settings SET setting_value = ? WHERE setting_key = ? AND setting_value = ?",
            [(string) $now, $key, (string) $lastRun]
        )->rowCount();

        if ($affected === 0) {
             
            if ($lastRun === 0) {
                try {
                    Database::insert('settings', [
                        'setting_key' => $key,
                        'setting_value' => (string) $now,
                        'autoload' => 0,
                    ]);
                } catch (\PDOException) {
                    return;  
                }
            } else {
                return;  
            }
        }

        try {
            $callback();
             
            if ($consecutiveFails > 0) {
                Setting::set($failKey, '0');
            }
        } catch (\Throwable $e) {
            error_log("CronManager [{$name}]: " . $e->getMessage());
             
            Setting::set($failKey, (string) ($consecutiveFails + 1));
        }
    }
}
