<?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\Tests\Messenger;

use Ferienpass\CoreBundle\Message\LoggableMessageInterface;
use Ferienpass\CoreBundle\Messenger\MessageLogMiddleware;
use Ferienpass\CoreBundle\Messenger\UuidStamp;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

final class MessageLogMiddlewareTest extends TestCase
{
    public function testPassesThroughNonLoggableMessageWithoutLogging(): void
    {
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects(self::never())->method('info');

        $next = $this->createMock(MiddlewareInterface::class);
        $next->expects(self::once())->method('handle')->willReturnCallback(static fn (Envelope $env, $stack) => $env);

        $stack = $this->createMock(StackInterface::class);
        $stack->method('next')->willReturn($next);

        $middleware = new MessageLogMiddleware($logger);
        $envelope = new Envelope(new \stdClass());

        $middleware->handle($envelope, $stack);
    }

    public function testLogsLoggableMessages(): void
    {
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects(self::once())->method('info');

        $next = $this->createMock(MiddlewareInterface::class);
        $next->method('handle')->willReturnCallback(static fn (Envelope $env, $stack) => $env);

        $stack = $this->createMock(StackInterface::class);
        $stack->method('next')->willReturn($next);

        $middleware = new MessageLogMiddleware($logger);

        $loggableMessage = new class implements LoggableMessageInterface {
            public function getRelated(): array
            {
                return [];
            }
        };

        $envelope = new Envelope($loggableMessage);
        $middleware->handle($envelope, $stack);
    }

    public function testAddsUuidStampWhenMissing(): void
    {
        $logger = $this->createMock(LoggerInterface::class);

        $capturedEnvelope = null;
        $next = $this->createMock(MiddlewareInterface::class);
        $next->method('handle')->willReturnCallback(static function (Envelope $env, $stack) use (&$capturedEnvelope) {
            $capturedEnvelope = $env;

            return $env;
        });

        $stack = $this->createMock(StackInterface::class);
        $stack->method('next')->willReturn($next);

        $middleware = new MessageLogMiddleware($logger);
        $envelope = new Envelope(new \stdClass());

        $middleware->handle($envelope, $stack);

        self::assertInstanceOf(UuidStamp::class, $capturedEnvelope->last(UuidStamp::class));
    }

    public function testReusesExistingUuidStamp(): void
    {
        $logger = $this->createMock(LoggerInterface::class);

        $existingStamp = new UuidStamp();

        $capturedEnvelope = null;
        $next = $this->createMock(MiddlewareInterface::class);
        $next->method('handle')->willReturnCallback(static function (Envelope $env, $stack) use (&$capturedEnvelope) {
            $capturedEnvelope = $env;

            return $env;
        });

        $stack = $this->createMock(StackInterface::class);
        $stack->method('next')->willReturn($next);

        $middleware = new MessageLogMiddleware($logger);
        $envelope = new Envelope(new \stdClass(), [$existingStamp]);

        $middleware->handle($envelope, $stack);

        // The existing stamp should be reused (same UUID)
        self::assertSame($existingStamp->getUuid(), $capturedEnvelope->last(UuidStamp::class)->getUuid());
    }
}
