<?php

declare(strict_types=1);

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

use App\Shared\Infrastructure\Messenger\TenantDatabaseMiddleware;
use App\Shared\Infrastructure\Messenger\TenantMessageTenantIdResolver;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantDatabaseSwitcher;
use App\Shared\Infrastructure\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantRegistry;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

#[CoversClass(TenantDatabaseMiddleware::class)]
final class TenantDatabaseMiddlewareTest extends TestCase
{
    #[Test]
    public function itSwitchesToTheMessageTenantDatabaseAndRestoresThePreviousOne(): void
    {
        $tenantConfig = new TenantConfig(
            tenantId: TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890'),
            subdomain: 'ecole-alpha',
            databaseUrl: 'postgresql://tenant:secret@db:5432/classeo_tenant_alpha?serverVersion=18&charset=utf8',
        );
        $message = new readonly class('a1b2c3d4-e5f6-7890-abcd-ef1234567890') {
            public function __construct(
                public string $tenantId,
            ) {
            }
        };

        $registry = $this->createMock(TenantRegistry::class);
        $registry->expects(self::once())
            ->method('getConfig')
            ->with(self::callback(static fn (TenantId $tenantId): bool => (string) $tenantId === 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'))
            ->willReturn($tenantConfig);

        $databaseSwitcher = $this->createMock(TenantDatabaseSwitcher::class);
        $databaseSwitcher->expects(self::once())
            ->method('currentDatabaseUrl')
            ->willReturn('postgresql://tenant:secret@db:5432/classeo_tenant_previous?serverVersion=18&charset=utf8');
        $databaseSwitcher->expects(self::exactly(2))
            ->method('useTenantDatabase')
            ->with(
                self::logicalOr(
                    self::equalTo($tenantConfig->databaseUrl),
                    self::equalTo('postgresql://tenant:secret@db:5432/classeo_tenant_previous?serverVersion=18&charset=utf8'),
                ),
            );

        $middleware = new TenantDatabaseMiddleware(
            $registry,
            $databaseSwitcher,
            new TenantMessageTenantIdResolver(),
        );

        $envelope = new Envelope($message);
        $result = $middleware->handle($envelope, new TestStack(
            new class implements MiddlewareInterface {
                public function handle(Envelope $envelope, StackInterface $stack): Envelope
                {
                    return $envelope;
                }
            },
        ));

        self::assertSame($envelope, $result);
    }

    #[Test]
    public function itLeavesTheCurrentConnectionUntouchedWhenTheMessageHasNoTenantId(): void
    {
        $registry = $this->createMock(TenantRegistry::class);
        $registry->expects(self::never())->method('getConfig');

        $databaseSwitcher = $this->createMock(TenantDatabaseSwitcher::class);
        $databaseSwitcher->expects(self::never())->method('currentDatabaseUrl');
        $databaseSwitcher->expects(self::never())->method('useTenantDatabase');
        $databaseSwitcher->expects(self::never())->method('useDefaultDatabase');

        $middleware = new TenantDatabaseMiddleware(
            $registry,
            $databaseSwitcher,
            new TenantMessageTenantIdResolver(),
        );

        $message = new readonly class('batch-1') {
            public function __construct(
                public string $batchId,
            ) {
            }
        };

        $envelope = new Envelope($message);
        $result = $middleware->handle($envelope, new TestStack(
            new class implements MiddlewareInterface {
                public function handle(Envelope $envelope, StackInterface $stack): Envelope
                {
                    return $envelope;
                }
            },
        ));

        self::assertSame($envelope, $result);
    }
}

final readonly class TestStack implements StackInterface
{
    public function __construct(
        private MiddlewareInterface $next,
    ) {
    }

    public function next(): MiddlewareInterface
    {
        return $this->next;
    }
}
