<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bridge\Doctrine\Tests\Types;

use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\AbstractUid;
use Symfony\Component\Uid\Uuid;

final class UuidTypeTest extends TestCase
{
    private const DUMMY_UUID = '9f755235-5a2d-4aba-9605-e9962b312e50';

    private UuidType $type;

    public static function setUpBeforeClass(): void
    {
        if (Type::hasType('uuid')) {
            Type::overrideType('uuid', UuidType::class);
        } else {
            Type::addType('uuid', UuidType::class);
        }
    }

    protected function setUp(): void
    {
        $this->type = Type::getType('uuid');
    }

    public function testUuidConvertsToDatabaseValue()
    {
        $uuid = Uuid::fromString(self::DUMMY_UUID);

        $expected = $uuid->__toString();
        $actual = $this->type->convertToDatabaseValue($uuid, new PostgreSQLPlatform());

        $this->assertEquals($expected, $actual);
    }

    public function testUuidInterfaceConvertsToNativeUidDatabaseValue()
    {
        $uuid = $this->createMock(AbstractUid::class);

        $uuid
            ->expects($this->once())
            ->method('toRfc4122')
            ->willReturn('foo');

        $actual = $this->type->convertToDatabaseValue($uuid, new PostgreSQLPlatform());

        $this->assertEquals('foo', $actual);
    }

    public function testUuidInterfaceConvertsToBinaryDatabaseValue()
    {
        $uuid = $this->createMock(AbstractUid::class);

        $uuid
            ->expects($this->once())
            ->method('toBinary')
            ->willReturn('foo');

        $actual = $this->type->convertToDatabaseValue($uuid, new MySQLPlatform());

        $this->assertEquals('foo', $actual);
    }

    public function testUuidStringConvertsToDatabaseValue()
    {
        $actual = $this->type->convertToDatabaseValue(self::DUMMY_UUID, new PostgreSQLPlatform());

        $this->assertEquals(self::DUMMY_UUID, $actual);
    }

    public function testNotSupportedTypeConversionForDatabaseValue()
    {
        $this->expectException(ConversionException::class);

        $this->type->convertToDatabaseValue(new \stdClass(), self::getSqlitePlatform());
    }

    public function testNullConversionForDatabaseValue()
    {
        $this->assertNull($this->type->convertToDatabaseValue(null, self::getSqlitePlatform()));
    }

    public function testUuidInterfaceConvertsToPHPValue()
    {
        $uuid = $this->createStub(AbstractUid::class);
        $actual = $this->type->convertToPHPValue($uuid, self::getSqlitePlatform());

        $this->assertSame($uuid, $actual);
    }

    public function testUuidConvertsToPHPValue()
    {
        $uuid = $this->type->convertToPHPValue(self::DUMMY_UUID, self::getSqlitePlatform());

        $this->assertInstanceOf(Uuid::class, $uuid);
        $this->assertEquals(self::DUMMY_UUID, $uuid->__toString());
    }

    public function testInvalidUuidConversionForPHPValue()
    {
        $this->expectException(ConversionException::class);

        $this->type->convertToPHPValue('abcdefg', self::getSqlitePlatform());
    }

    public function testNullConversionForPHPValue()
    {
        $this->assertNull($this->type->convertToPHPValue(null, self::getSqlitePlatform()));
    }

    public function testReturnValueIfUuidForPHPValue()
    {
        $uuid = Uuid::v4();

        $this->assertSame($uuid, $this->type->convertToPHPValue($uuid, self::getSqlitePlatform()));
    }

    public function testGetName()
    {
        $this->assertEquals('uuid', $this->type->getName());
    }

    #[DataProvider('provideSqlDeclarations')]
    public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string $expectedDeclaration)
    {
        $this->assertEquals($expectedDeclaration, $this->type->getSqlDeclaration(['length' => 36], $platform));
    }

    public static function provideSqlDeclarations(): \Generator
    {
        yield [new PostgreSQLPlatform(), 'UUID'];
        yield [self::getSqlitePlatform(), 'BLOB'];
        yield [new MySQLPlatform(), 'BINARY(16)'];
        yield [new MariaDBPlatform(), 'BINARY(16)'];
    }

    public function testRequiresSQLCommentHint()
    {
        $this->assertTrue($this->type->requiresSQLCommentHint(self::getSqlitePlatform()));
    }

    private static function getSqlitePlatform(): AbstractPlatform
    {
        if (interface_exists(Exception::class)) {
            // DBAL 4+
            return new SQLitePlatform();
        }

        return new \Doctrine\DBAL\Platforms\SqlitePlatform();
    }
}

// @php-cs-fixer-ignore fully_qualified_strict_types
