<?php
declare(strict_types=1);

namespace App\Services;

use RuntimeException;

final class UpdateLockService
{
    private const DEFAULT_TTL_SECONDS = 900;

    private string $path;
    private string $token = '';

    public function __construct()
    {
        $dir = BASE_PATH . '/storage/updates';
        if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
            throw new RuntimeException('Impossible de créer le dossier des mises à jour.');
        }
        $this->path = $dir . '/install.lock';
    }

    /** @return array{locked:bool,stale:bool,age_seconds:int,data:array<string,mixed>} */
    public function status(int $ttlSeconds = self::DEFAULT_TTL_SECONDS): array
    {
        if (!is_file($this->path)) {
            return ['locked' => false, 'stale' => false, 'age_seconds' => 0, 'data' => []];
        }

        $data = json_decode((string) @file_get_contents($this->path), true);
        if (!is_array($data)) {
            return ['locked' => true, 'stale' => true, 'age_seconds' => PHP_INT_MAX, 'data' => []];
        }

        $created = (int) ($data['created_at_ts'] ?? 0);
        $age = $created > 0 ? max(0, time() - $created) : PHP_INT_MAX;

        return [
            'locked' => true,
            'stale' => $created <= 0 || $age >= $ttlSeconds,
            'age_seconds' => $age,
            'data' => $data,
        ];
    }

    public function acquire(int $packageId, int $userId, int $ttlSeconds = self::DEFAULT_TTL_SECONDS): string
    {
        $state = $this->status($ttlSeconds);
        if ($state['locked']) {
            if (!$state['stale']) {
                throw new RuntimeException('Une autre installation est déjà en cours.');
            }
            $this->forceRelease();
        }

        $this->token = bin2hex(random_bytes(24));
        $payload = json_encode([
            'token' => $this->token,
            'package_id' => $packageId,
            'user_id' => $userId,
            'created_at' => date(DATE_ATOM),
            'created_at_ts' => time(),
            'pid' => getmypid(),
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

        $handle = @fopen($this->path, 'x');
        if ($handle === false) {
            throw new RuntimeException('Une autre installation est déjà en cours.');
        }
        fwrite($handle, (string) $payload);
        fclose($handle);

        return $this->token;
    }

    public function release(): void
    {
        if (!is_file($this->path)) {
            return;
        }

        $data = json_decode((string) @file_get_contents($this->path), true);
        if (is_array($data) && ($data['token'] ?? '') === $this->token) {
            @unlink($this->path);
        }
    }

    public function forceRelease(): bool
    {
        if (!is_file($this->path)) {
            return true;
        }
        return @unlink($this->path) || !is_file($this->path);
    }
}
