<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Messenger;

use Ferienpass\CoreBundle\Repository\TenantRepository;
use Ferienpass\CoreBundle\Tenant\TenantContext;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Stamp\ConsumedByWorkerStamp;

/**
 * Keeps async work inside the tenant that queued it.
 *
 * On dispatch the active tenant's slug is stamped onto the envelope; in the
 * worker that stamp becomes the active tenant for the duration of the handler,
 * and is restored afterwards so the next message starts clean.
 *
 * The worker is otherwise tenant-less, so without this every handler would run
 * unfiltered and, for instance, mail the wrong municipality's parents.
 * Mirrors {@see LocaleMiddleware}, which does the same for the locale.
 */
class TenantMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly TenantContext $tenantContext,
        private readonly TenantRepository $tenants,
    ) {
    }

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        if (!$this->tenantContext->isEnabled()) {
            return $stack->next()->handle($envelope, $stack);
        }

        $consumedByWorker = null !== $envelope->last(ConsumedByWorkerStamp::class);
        $stamp = $envelope->last(TenantStamp::class);

        if ($consumedByWorker && $stamp instanceof TenantStamp) {
            $tenant = $this->tenants->findOneBySlug($stamp->getSlug());

            return $this->tenantContext->runFor($tenant, static fn (): Envelope => $stack->next()->handle($envelope, $stack));
        }

        if (null === $stamp && !$consumedByWorker) {
            $slug = $this->tenantContext->getSlug();
            if (null !== $slug) {
                $envelope = $envelope->with(new TenantStamp($slug));
            }
        }

        return $stack->next()->handle($envelope, $stack);
    }
}
