<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Distributed\Lease;

final class DistributedLeaseManager
{
    private static ?self $instance = null;

    private DistributedLeaseCollection $leases;
    private DistributedLeaseStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->leases = new DistributedLeaseCollection();
        $this->store = new DistributedLeaseStore($basePath);
        $this->hydrate();
    }

    public static function instance(?string $basePath = null): self
    {
        if (self::$instance === null) {
            $basePath ??= self::detectBasePath();
            self::$instance = new self($basePath);
        }

        return self::$instance;
    }

    public static function resetInstance(?string $basePath = null): self
    {
        $basePath ??= self::detectBasePath();
        self::$instance = new self($basePath);

        return self::$instance;
    }

    public function acquire(string $jobId, string $nodeId, string $workerId, int $ttlSeconds = 60): ?DistributedJobLease
    {
        $this->leases->expireDue();

        if ($this->leases->activeForJob($jobId) !== null) {
            $this->save();
            return null;
        }

        $lease = DistributedJobLease::create($jobId, $nodeId, $workerId, $ttlSeconds);
        $this->leases->put($lease);
        $this->save();

        return $lease;
    }

    public function renew(string $leaseId, int $ttlSeconds = 60): bool
    {
        $ok = $this->leases->renew($leaseId, $ttlSeconds);
        $this->save();

        return $ok;
    }

    public function release(string $leaseId): bool
    {
        $ok = $this->leases->release($leaseId);
        $this->save();

        return $ok;
    }

    public function expireDue(): int
    {
        $count = $this->leases->expireDue();
        $this->save();

        return $count;
    }

    public function hydrate(): void
    {
        $snapshot = $this->store->load();

        if ($snapshot->leases() !== []) {
            $this->leases = DistributedLeaseCollection::fromArray($snapshot->leases());
            $this->leases->expireDue();
        }
    }

    public function collection(): DistributedLeaseCollection
    {
        return $this->leases;
    }

    public function store(): DistributedLeaseStore
    {
        return $this->store;
    }

    public function snapshot(): DistributedLeaseSnapshot
    {
        return new DistributedLeaseSnapshot($this->leases->toArray(), $this->leases->statistics(), date('c'));
    }

    public function save(): DistributedLeaseSnapshot
    {
        return $this->store->save($this->snapshot());
    }

    public function clear(): void
    {
        $this->leases = new DistributedLeaseCollection();
        $this->store->reset();
    }

    public function summary(): array
    {
        $snapshot = $this->snapshot();

        return [
            'leases' => $snapshot->leases(),
            'statistics' => $snapshot->statistics(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

    private static function detectBasePath(): string
    {
        $candidate = dirname(__DIR__, 5);
        $real = realpath($candidate);

        return $real !== false ? $real : $candidate;
    }
}
