<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\Messenger;

use App\Shared\Domain\CorrelationId;
use App\Shared\Infrastructure\Audit\CorrelationIdHolder;
use App\Shared\Infrastructure\Messenger\AddCorrelationIdStampMiddleware;
use App\Shared\Infrastructure\Messenger\CorrelationIdStamp;
use PHPUnit\Framework\TestCase;
use stdClass;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

/**
 * @see Story 1.7 - T3: Correlation ID
 */
final class AddCorrelationIdStampMiddlewareTest extends TestCase
{
    private AddCorrelationIdStampMiddleware $middleware;

    protected function setUp(): void
    {
        $this->middleware = new AddCorrelationIdStampMiddleware();
        CorrelationIdHolder::clear();
    }

    protected function tearDown(): void
    {
        CorrelationIdHolder::clear();
    }

    public function testAddsStampWhenCorrelationIdIsSet(): void
    {
        $correlationId = CorrelationId::generate();
        CorrelationIdHolder::set($correlationId);

        $envelope = new Envelope(new stdClass());
        $stack = $this->createCapturingStack();

        $result = $this->middleware->handle($envelope, $stack);

        $stamp = $result->last(CorrelationIdStamp::class);
        $this->assertInstanceOf(CorrelationIdStamp::class, $stamp);
        $this->assertSame($correlationId->value(), $stamp->correlationId);
    }

    public function testDoesNotAddStampWhenNoCorrelationId(): void
    {
        CorrelationIdHolder::clear();

        $envelope = new Envelope(new stdClass());
        $stack = $this->createCapturingStack();

        $result = $this->middleware->handle($envelope, $stack);

        $stamp = $result->last(CorrelationIdStamp::class);
        $this->assertNull($stamp);
    }

    public function testDoesNotOverwriteExistingStamp(): void
    {
        $existingId = '11111111-1111-1111-1111-111111111111';
        $currentId = CorrelationId::generate();
        CorrelationIdHolder::set($currentId);

        $envelope = new Envelope(new stdClass(), [new CorrelationIdStamp($existingId)]);
        $stack = $this->createCapturingStack();

        $result = $this->middleware->handle($envelope, $stack);

        $stamp = $result->last(CorrelationIdStamp::class);
        $this->assertInstanceOf(CorrelationIdStamp::class, $stamp);
        // Should keep the existing stamp, not overwrite with current
        $this->assertSame($existingId, $stamp->correlationId);
    }

    private function createCapturingStack(): StackInterface
    {
        return new class implements StackInterface {
            public function next(): MiddlewareInterface
            {
                return new class implements MiddlewareInterface {
                    public function handle(Envelope $envelope, StackInterface $stack): Envelope
                    {
                        return $envelope;
                    }
                };
            }
        };
    }
}
