<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\Persistence\Doctrine;

use App\Shared\Infrastructure\Persistence\Doctrine\TenantAwareConnection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;

#[CoversClass(TenantAwareConnection::class)]
final class TenantAwareConnectionTest extends TestCase
{
    #[Test]
    public function itSwitchesBetweenDefaultAndTenantConnectionParams(): void
    {
        $driverConnection = $this->createMock(DriverConnection::class);
        $driverConnection->method('getServerVersion')->willReturn('18.1');

        $capturedParams = [];
        $driver = $this->createDriver($driverConnection, $capturedParams);

        $connection = new TenantAwareConnection(
            [
                'driver' => 'pdo_pgsql',
                'host' => 'db',
                'port' => 5432,
                'user' => 'master',
                'password' => 'secret',
                'dbname' => 'classeo_master',
                'serverVersion' => '18',
            ],
            $driver,
        );

        self::assertSame('18.1', $connection->getServerVersion());
        self::assertSame('classeo_master', $capturedParams[0]['dbname']);
        self::assertNull($connection->currentDatabaseUrl());

        $tenantDatabaseUrl = 'postgresql://tenant:tenantpass@tenant-db:5432/classeo_tenant_alpha?serverVersion=18&charset=utf8';
        $connection->useTenantDatabase($tenantDatabaseUrl);

        self::assertSame('18.1', $connection->getServerVersion());
        self::assertSame('classeo_tenant_alpha', $capturedParams[1]['dbname']);
        self::assertSame('tenant-db', $capturedParams[1]['host']);
        self::assertSame('tenant', $capturedParams[1]['user']);
        self::assertSame($tenantDatabaseUrl, $connection->currentDatabaseUrl());

        $connection->useDefaultDatabase();

        self::assertSame('18.1', $connection->getServerVersion());
        self::assertSame('classeo_master', $capturedParams[2]['dbname']);
        self::assertNull($connection->currentDatabaseUrl());
    }

    #[Test]
    public function itRejectsDatabaseSwitchesWhileATransactionIsOpen(): void
    {
        $driverConnection = $this->createMock(DriverConnection::class);
        $driverConnection->method('getServerVersion')->willReturn('18.1');

        $capturedParams = [];
        $driver = $this->createDriver($driverConnection, $capturedParams);
        $connection = new TenantAwareConnection(
            [
                'driver' => 'pdo_pgsql',
                'host' => 'db',
                'port' => 5432,
                'user' => 'master',
                'password' => 'secret',
                'dbname' => 'classeo_master',
                'serverVersion' => '18',
            ],
            $driver,
        );

        $connection->beginTransaction();

        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Cannot switch database while a transaction is active.');

        $connection->useTenantDatabase('postgresql://tenant:tenantpass@tenant-db:5432/classeo_tenant_alpha?serverVersion=18&charset=utf8');
    }

    /**
     * @param array<int, array<string, mixed>> $capturedParams
     */
    private function createDriver(DriverConnection $driverConnection, array &$capturedParams): Driver
    {
        $driver = $this->createMock(Driver::class);
        $driver->method('getDatabasePlatform')->willReturn($this->createMock(AbstractPlatform::class));
        $driver->method('getExceptionConverter')->willReturn($this->createMock(ExceptionConverter::class));
        $driver->method('connect')->willReturnCallback(
            static function (array $params) use (&$capturedParams, $driverConnection): DriverConnection {
                $capturedParams[] = $params;

                return $driverConnection;
            },
        );

        return $driver;
    }
}
