<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Consumer;

use Throwable;
use PpPhp\Core\Platform\Event\Consumer\Batch\QueueConsumerBatchEngine;
use PpPhp\Core\Platform\Event\Consumer\Batch\QueueConsumerBatchOptions;
use PpPhp\Core\Platform\Event\Consumer\Batch\QueueConsumerBatchResult;
use PpPhp\Core\Platform\Event\Consumer\Contracts\QueueConsumerInterface;
use PpPhp\Core\Platform\Event\Queue\DeferredQueueManager;
use PpPhp\Core\Platform\Event\Worker\Pool\WorkerPoolManager;

final class QueueConsumer implements QueueConsumerInterface
{
    private QueueConsumerStatistics $statistics;
    private ?QueueConsumerBatchEngine $batchEngine = null;

    public function __construct(
        private readonly DeferredQueueManager $queue = new DeferredQueueManager(),
        private readonly ?WorkerPoolManager $pool = null
    ) {
        $this->statistics = new QueueConsumerStatistics();
    }

    public function queue(): DeferredQueueManager { return $this->queue; }
    public function pool(): WorkerPoolManager { return $this->pool ?? WorkerPoolManager::instance(); }

    public function consume(?callable $handler = null): QueueConsumerResult
    {
        $this->pool()->hydrate();
        $this->pool()->ensureDefaultPool();

        $event = $this->queue->driver()->dequeue();

        if ($event === null) {
            $this->statistics = $this->statistics->withPending(0);
            return QueueConsumerResult::createEmpty();
        }

        $worker = $this->pool()->pool()->acquire();

        if ($worker === null) {
            $this->queue->driver()->enqueue($event);
            $this->statistics = $this->statistics->withPending($this->queue->statistics()->size());
            return QueueConsumerResult::createFailed($event->eventName(), '', 'No available worker.');
        }

        $start = microtime(true);

        try {
            $workerRuntime = $worker->worker();
            $result = $workerRuntime->handle($event, $handler ?? static function (): void {});

            $this->pool()->pool()->release($worker->id());
            $this->pool()->save();

            $consumerResult = $result->ok()
                ? QueueConsumerResult::createAcknowledged($event->eventName(), $worker->id())
                : QueueConsumerResult::createFailed($event->eventName(), $worker->id(), 'Worker failed.');

            $this->statistics = $this->statistics
                ->record($consumerResult, (microtime(true) - $start) * 1000)
                ->withPending($this->queue->statistics()->size())
                ->withActiveWorkers((int) ($this->pool()->pool()->statistics()->toArray()['busy'] ?? 0));

            return $consumerResult;
        } catch (Throwable $e) {
            $this->pool()->pool()->release($worker->id());
            $this->pool()->save();

            $consumerResult = QueueConsumerResult::createFailed($event->eventName(), $worker->id(), $e->getMessage());

            $this->statistics = $this->statistics
                ->record($consumerResult, (microtime(true) - $start) * 1000)
                ->withPending($this->queue->statistics()->size());

            return $consumerResult;
        }
    }

    public function consumeBatch(int $limit = 10, ?callable $handler = null): int
    {
        return $this->consumeBatchResult(QueueConsumerBatchOptions::limit($limit), $handler)->acknowledged();
    }

    public function consumeBatchResult(QueueConsumerBatchOptions $options, ?callable $handler = null): QueueConsumerBatchResult
    {
        return $this->batchEngine()->consume($options, $handler);
    }

    public function batchEngine(): QueueConsumerBatchEngine
    {
        if ($this->batchEngine === null) {
            $this->batchEngine = new QueueConsumerBatchEngine($this);
        }

        return $this->batchEngine;
    }

    public function statistics(): QueueConsumerStatistics
    {
        return $this->statistics
            ->withPending($this->queue->statistics()->size())
            ->withActiveWorkers((int) ($this->pool()->pool()->statistics()->toArray()['busy'] ?? 0));
    }

    public function snapshot(): QueueConsumerSnapshot
    {
        return new QueueConsumerSnapshot(
            $this->statistics()->toArray(),
            $this->queue->snapshot()->toArray(),
            $this->pool()->summary()
        );
    }

    public function clear(): void
    {
        $this->queue->clear();
        $this->statistics = new QueueConsumerStatistics();
        $this->batchEngine = null;
    }
}
