<?php
declare(strict_types=1);

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

use PpPhp\Core\Platform\Event\Distributed\DistributedRuntimeCoordinator;
use PpPhp\Core\Platform\Event\Distributed\Lease\DistributedLeaseManager;

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

    private DistributedQueueRegistry $registry;
    /** @var array<string, DistributedQueuePartition> */
    private array $partitions = [];
    private DistributedQueueStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->registry = new DistributedQueueRegistry();
        $this->store = new DistributedQueueStore($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 registerQueue(string $queueName, int $priority = 0): DistributedQueueRecord
    {
        $record = $this->registry->register($queueName, $priority);
        $this->save();

        return $record;
    }

    public function updateCounters(string $queueName, int $pending, int $reserved = 0): bool
    {
        $ok = $this->registry->updateCounters($queueName, $pending, $reserved);
        $this->save();

        return $ok;
    }

    public function assignPartition(string $queueName, string $nodeId, string $workerId, int $weight = 1): DistributedQueuePartition
    {
        $partition = DistributedQueuePartition::assign($queueName, $nodeId, $workerId, $weight);
        $this->partitions[$partition->partitionId()] = $partition;
        $this->save();

        return $partition;
    }

    public function dispatch(string $queueName, string $jobId, int $ttlSeconds = 60): array
    {
        $dispatcher = new DistributedQueueDispatcher(
            DistributedRuntimeCoordinator::instance($this->basePath),
            DistributedLeaseManager::instance($this->basePath)
        );

        return $dispatcher->dispatch($queueName, $jobId, $ttlSeconds);
    }

    public function registry(): DistributedQueueRegistry
    {
        return $this->registry;
    }

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

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

        if ($snapshot->queues() !== []) {
            $this->registry = DistributedQueueRegistry::fromArray($snapshot->queues());
        }

        $this->partitions = [];
        foreach ($snapshot->partitions() as $partition) {
            if (is_array($partition)) {
                $part = DistributedQueuePartition::fromArray($partition);
                $this->partitions[$part->partitionId()] = $part;
            }
        }
    }

    public function snapshot(): DistributedQueueSnapshot
    {
        $stats = $this->registry->statistics();
        $stats['partitions'] = count($this->partitions);

        return new DistributedQueueSnapshot(
            $this->registry->toArray(),
            array_map(static fn (DistributedQueuePartition $partition): array => $partition->toArray(), array_values($this->partitions)),
            $stats,
            date('c')
        );
    }

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

    public function clear(): void
    {
        $this->registry = new DistributedQueueRegistry();
        $this->partitions = [];
        $this->store->reset();
    }

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

        return [
            'queues' => $snapshot->queues(),
            'partitions' => $snapshot->partitions(),
            '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;
    }
}
