<?php

namespace App\Shared\Event\Service;

use App\Shared\Event\Message\DomainEventMessage;
use App\Shared\Event\Repository\DomainEventLogRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * تخلیهٔ صندوق خروجی — ردیف‌های `published_at IS NULL` به messenger می‌روند.
 *
 * منطق اینجاست نه در Command، چون دو فراخوان دارد: دستور دستی برای وقتی که صف عقب
 * افتاده، و زمان‌بند برای اجرای همیشگی. اگر در Command می‌ماند، زمان‌بند مجبور بود
 * پروسهٔ کنسول اجرا کند و خطاهایش را از exit code حدس بزند.
 */
final class OutboxPublisher
{
    public function __construct(
        private readonly DomainEventLogRepository $events,
        private readonly MessageBusInterface      $bus,
        private readonly EntityManagerInterface   $em,
    ) {}

    /**
     * شکستِ یک ردیف بقیه را متوقف نمی‌کند؛ `attempts` بالا می‌رود و خطا روی خودِ ردیف
     * می‌نشیند تا بعد از سقف تلاش، با دلیلش قابل دیدن بماند.
     *
     * @return array{published: int, failed: int}
     */
    public function publish(int $limit = 100): array
    {
        $pending   = $this->events->findPending(max(1, $limit));
        $published = 0;
        $failed    = 0;

        foreach ($pending as $event) {
            try {
                $this->bus->dispatch(new DomainEventMessage(
                    $event->getUuid(),
                    $event->getName(),
                    $event->getEntityType(),
                    $event->getEntityId(),
                    $event->getPayload(),
                    $event->getOccurredAt(),
                ));

                $event->markPublished();
                $published++;
            } catch (\Throwable $e) {
                $event->markFailed($e->getMessage());
                $failed++;
            }
        }

        if ($pending !== []) {
            $this->em->flush();
        }

        return ['published' => $published, 'failed' => $failed];
    }
}
