<?php

/**
 * Internal cron runner. Never blocks bot.php — background CLI only.
 */
class CronScheduler
{
    private const SETTING_TYPE = 'INTERNAL_CRON_STATE';
    private const WEBHOOK_COOLDOWN = 90;
    private const RUN_BUDGET_SECONDS = 12;

    /** @return array<string, array{interval:int,file:string,optional?:bool}> */
    private static function tasks(): array
    {
        $botRoot = dirname(__DIR__);
        $projectRoot = dirname($botRoot);

        return [
            'messagewizwiz' => [
                'interval' => 60,
                'file' => $botRoot . '/settings/messagewizwiz.php',
            ],
            'rewardReport' => [
                'interval' => 60,
                'file' => $botRoot . '/settings/rewardReport.php',
            ],
            'warnusers' => [
                'interval' => 60,
                'file' => $botRoot . '/settings/warnusers.php',
            ],
            'gift2all' => [
                'interval' => 60,
                'file' => $botRoot . '/settings/gift2all.php',
            ],
            'tronChecker' => [
                'interval' => 180,
                'file' => $botRoot . '/settings/tronChecker.php',
            ],
            'backupnutif' => [
                'interval' => 60,
                'file' => $projectRoot . '/wizpanel/backupnutif.php',
                'optional' => true,
            ],
        ];
    }

    public static function cronToken(): string
    {
        global $botToken;
        return substr(hash_hmac('sha256', 'wizwiz_internal_cron', (string) $botToken), 0, 32);
    }

    public static function isValidCronRequest(?string $token = null): bool
    {
        if ($token === null) {
            $token = $_GET['token'] ?? $_SERVER['HTTP_X_WIZWIZ_CRON'] ?? '';
        }
        return is_string($token) && $token !== '' && hash_equals(self::cronToken(), $token);
    }

    /** Called from bot webhook — spawns detached CLI worker, never HTTP self-call. */
    public static function tickFromWebhook(): void
    {
        if (defined('WIZWIZ_CRON') && WIZWIZ_CRON) {
            return;
        }
        if (!self::hasDueTask()) {
            return;
        }
        $state = self::loadState();
        if (time() - (int) ($state['_last_webhook_tick'] ?? 0) < self::WEBHOOK_COOLDOWN) {
            return;
        }
        $state['_last_webhook_tick'] = time();
        self::saveState($state);
        self::spawnBackgroundRunner();
    }

    public static function hasDueTask(): bool
    {
        $state = self::loadState();
        $now = time();
        foreach (self::tasks() as $name => $task) {
            if (!empty($task['optional']) && !is_file($task['file'])) {
                continue;
            }
            if (!is_file($task['file'])) {
                continue;
            }
            $lastRun = (int) ($state[$name] ?? 0);
            if ($now - $lastRun >= $task['interval']) {
                return true;
            }
        }
        return false;
    }

    public static function spawnBackgroundRunner(): void
    {
        $script = dirname(__DIR__) . '/settings/cron_runner.php';
        if (!is_file($script)) {
            return;
        }
        $token = self::cronToken();
        $php = PHP_BINARY;
        if (!is_string($php) || $php === '') {
            $php = 'php';
        }
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            if (function_exists('popen')) {
                pclose(popen('start /B "" ' . escapeshellarg($php) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($token), 'r'));
            }
            return;
        }
        if (function_exists('exec')) {
            exec(escapeshellarg($php) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($token) . ' > /dev/null 2>&1 &');
        }
    }

    public static function runDueTasks(): void
    {
        if (defined('WIZWIZ_CRON_RUNNING')) {
            return;
        }
        define('WIZWIZ_CRON_RUNNING', true);

        if (!self::acquireRunnerLock()) {
            return;
        }

        try {
            @ignore_user_abort(true);
            @set_time_limit(max(25, self::RUN_BUDGET_SECONDS + 8));

            $deadline = time() + self::RUN_BUDGET_SECONDS;
            $state = self::loadState();
            $tasks = self::tasks();
            $now = time();
            $ran = false;

            $taskOrder = array_keys($tasks);
            usort($taskOrder, static function (string $nameA, string $nameB) use ($tasks, $state, $now): int {
                $overdueA = $now - (int) ($state[$nameA] ?? 0) - $tasks[$nameA]['interval'];
                $overdueB = $now - (int) ($state[$nameB] ?? 0) - $tasks[$nameB]['interval'];
                return $overdueB <=> $overdueA;
            });

            foreach ($taskOrder as $name) {
                if (time() >= $deadline || $ran) {
                    break;
                }
                $task = $tasks[$name];
                $file = $task['file'];
                if (!empty($task['optional']) && !is_file($file)) {
                    continue;
                }
                if (!is_file($file)) {
                    continue;
                }

                $lastRun = (int) ($state[$name] ?? 0);
                if ($now - $lastRun < $task['interval']) {
                    continue;
                }
                if (!self::acquireTaskLock($name)) {
                    continue;
                }

                try {
                    self::runTaskFile($file);
                    $state[$name] = time();
                    self::saveState($state);
                    $ran = true;
                } catch (Throwable $e) {
                    // retry later
                } finally {
                    self::releaseTaskLock($name);
                }
            }

            if ($ran && self::hasDueTask()) {
                self::spawnBackgroundRunner();
            }
        } finally {
            self::releaseRunnerLock();
        }
    }

    private static function runTaskFile(string $file): void
    {
        if (!defined('WIZWIZ_CRON')) {
            define('WIZWIZ_CRON', true);
        }
        include $file;
    }

    /** @return array<string, int> */
    private static function loadState(): array
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return [];
        }
        $stmt = $connection->prepare('SELECT `value` FROM `setting` WHERE `type` = ? LIMIT 1');
        $type = self::SETTING_TYPE;
        $stmt->bind_param('s', $type);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        if (!$row || $row['value'] === null || $row['value'] === '') {
            return [];
        }
        $decoded = json_decode((string) $row['value'], true);
        return is_array($decoded) ? $decoded : [];
    }

    /** @param array<string, int> $state */
    private static function saveState(array $state): void
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return;
        }
        $payload = json_encode($state, JSON_UNESCAPED_UNICODE);
        $type = self::SETTING_TYPE;
        $stmt = $connection->prepare('SELECT `id` FROM `setting` WHERE `type` = ? LIMIT 1');
        $stmt->bind_param('s', $type);
        $stmt->execute();
        $exists = $stmt->get_result()->num_rows > 0;
        $stmt->close();
        if ($exists) {
            $stmt = $connection->prepare('UPDATE `setting` SET `value` = ? WHERE `type` = ?');
            $stmt->bind_param('ss', $payload, $type);
        } else {
            $stmt = $connection->prepare('INSERT INTO `setting` (`value`, `type`) VALUES (?, ?)');
            $stmt->bind_param('ss', $payload, $type);
        }
        $stmt->execute();
        $stmt->close();
    }

    private static function acquireRunnerLock(): bool
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return true;
        }
        $res = $connection->query("SELECT GET_LOCK('wizwiz_cron_runner', 0) AS l");
        $row = $res ? $res->fetch_assoc() : null;
        return isset($row['l']) && (int) $row['l'] === 1;
    }

    private static function releaseRunnerLock(): void
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return;
        }
        $connection->query("SELECT RELEASE_LOCK('wizwiz_cron_runner')");
    }

    private static function acquireTaskLock(string $name): bool
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return true;
        }
        $lockName = 'wizwiz_icron_' . preg_replace('/[^a-z0-9_]/i', '_', $name);
        $res = $connection->query("SELECT GET_LOCK('{$lockName}', 0) AS l");
        $row = $res ? $res->fetch_assoc() : null;
        return isset($row['l']) && (int) $row['l'] === 1;
    }

    private static function releaseTaskLock(string $name): void
    {
        global $connection;
        if (!$connection instanceof mysqli) {
            return;
        }
        $lockName = 'wizwiz_icron_' . preg_replace('/[^a-z0-9_]/i', '_', $name);
        $connection->query("SELECT RELEASE_LOCK('{$lockName}')");
    }
}
