<?php
namespace App;

use App\DB;
use PDO;

final class Queue
{
    public static function push(string $type, array $payload, int $delaySec = 0, ?string $dedupeKey = null, int $maxAttempts = 5): int
    {
        $pdo = DB::pdo();
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($json === false) $json = '{}';
        if ($dedupeKey !== null && $dedupeKey !== '') {
            $stmt = $pdo->prepare('INSERT INTO jobs(type, payload, available_at, dedupe_key, max_attempts) VALUES(?,?, DATE_ADD(NOW(), INTERVAL ? SECOND), ?, ?) ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), payload=VALUES(payload), available_at=LEAST(available_at, VALUES(available_at))');
            $stmt->execute([$type, $json, $delaySec, $dedupeKey, $maxAttempts]);
            return (int)$pdo->lastInsertId();
        }
        $stmt = $pdo->prepare('INSERT INTO jobs(type, payload, available_at, max_attempts) VALUES(?,?, DATE_ADD(NOW(), INTERVAL ? SECOND), ?)');
        $stmt->execute([$type, $json, $delaySec, $maxAttempts]);
        return (int)$pdo->lastInsertId();
    }

    public static function reserveNext(int $timeoutMs = 250): ?array
    {
        return DB::tx(function(PDO $pdo) {
            $row = $pdo->query("SELECT * FROM jobs WHERE status IN ('PENDING','RETRY') AND available_at<=NOW() AND attempts < max_attempts ORDER BY id LIMIT 1 FOR UPDATE")->fetch(PDO::FETCH_ASSOC);
            if (!$row) return null;
            $pdo->prepare("UPDATE jobs SET status='RUNNING', attempts=attempts+1, reserved_at=NOW(), last_error=NULL WHERE id=?")->execute([(int)$row['id']]);
            $row['attempts'] = (int)$row['attempts'] + 1;
            return $row;
        });
    }

    public static function done(int $id): void
    {
        DB::pdo()->prepare("UPDATE jobs SET status='DONE', done_at=NOW() WHERE id=?")->execute([$id]);
    }

    public static function fail(int $id, string $error = '', int $retryDelaySec = 60): void
    {
        DB::pdo()->prepare("UPDATE jobs SET status=IF(attempts>=max_attempts,'FAILED','RETRY'), last_error=?, available_at=DATE_ADD(NOW(), INTERVAL ? SECOND), failed_at=IF(attempts>=max_attempts,NOW(),failed_at) WHERE id=?")->execute([mb_substr($error, 0, 900, 'UTF-8'), max(1, $retryDelaySec), $id]);
    }
}
