<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Messenger;

use App\Shared\Domain\CorrelationId;
use App\Shared\Infrastructure\Audit\CorrelationIdHolder;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

/**
 * Messenger middleware for correlation ID propagation in async workers.
 *
 * - Extracts correlation ID from message stamp if present (propagated from HTTP request)
 * - Otherwise uses existing ID (synchronous dispatch) or generates new one (async worker)
 * - Only clears after handling in async context to prevent leakage between messages
 *
 * IMPORTANT: We must NOT clear the correlation ID during synchronous dispatch
 * within an HTTP request, as it would break traceability for the rest of the request.
 *
 * @see Story 1.7 - T3: Correlation ID
 */
final readonly class CorrelationIdMiddleware implements MiddlewareInterface
{
    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        // Check if we already have a correlation ID (from HTTP middleware)
        $existingId = CorrelationIdHolder::get();

        // Extract correlation ID from stamp if present (async message from HTTP)
        $stamp = $envelope->last(CorrelationIdStamp::class);

        if ($stamp instanceof CorrelationIdStamp) {
            // Async worker receiving message from HTTP - use stamp's ID
            $correlationId = CorrelationId::fromString($stamp->correlationId);
            CorrelationIdHolder::set($correlationId);
            $shouldClear = true; // Clear after async handling
        } elseif ($existingId !== null) {
            // Synchronous dispatch within HTTP request - keep existing ID
            $shouldClear = false; // Don't clear - HTTP middleware will handle it
        } else {
            // Async worker with no stamp - generate new ID for this message
            $correlationId = CorrelationId::generate();
            CorrelationIdHolder::set($correlationId);
            $shouldClear = true; // Clear after handling
        }

        try {
            return $stack->next()->handle($envelope, $stack);
        } finally {
            // Only clear in async context to prevent leakage between messages
            // Don't clear during synchronous dispatch - HTTP middleware handles that
            if ($shouldClear) {
                CorrelationIdHolder::clear();
            }
        }
    }
}
