<?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\Migration;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Types\Type;
use Ferienpass\CoreBundle\Migration\UuidMigration;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class UuidMigrationTest extends TestCase
{
    private Connection&MockObject $db;
    private AbstractSchemaManager&MockObject $schemaManager;
    private UuidMigration $migration;

    protected function setUp(): void
    {
        $this->db = $this->createMock(Connection::class);
        $this->schemaManager = $this->createMock(AbstractSchemaManager::class);

        $this->db->method('createSchemaManager')->willReturn($this->schemaManager);

        $this->migration = new UuidMigration(
            $this->db,
            'Ferienpass\CoreBundle\Entity\Participant',
            'Ferienpass\CoreBundle\Entity\Offer\Base'
        );
    }

    public function testShouldRunReturnsFalseIfNoTablesExist(): void
    {
        $this->schemaManager->method('tablesExist')->willReturn(false);

        $this->assertFalse($this->migration->shouldRun());
    }

    public function testShouldRunReturnsTrueIfUuidColumnIsMissing(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array('Attendance', $tables, true));
        $this->schemaManager->method('listTableColumns')->willReturnCallback(static fn ($table) => 'Attendance' === $table ? [] : ['some' => 'cols']);

        $this->assertTrue($this->migration->shouldRun());
    }

    public function testShouldRunReturnsTrueIfUuidColumnHasWrongType(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array('Attendance', $tables, true));

        $column = $this->createMock(Column::class);
        $column->method('getLength')->willReturn(36);
        $column->method('getType')->willReturn(Type::getType('string'));

        $this->schemaManager->method('listTableColumns')->willReturnCallback(static fn ($table) => 'Attendance' === $table ? ['uuid' => $column] : []);

        $this->assertTrue($this->migration->shouldRun());
    }

    public function testShouldRunReturnsTrueIfUuidColumnHasNullValues(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array('Attendance', $tables, true));

        $column = $this->createMock(Column::class);
        $column->method('getLength')->willReturn(16);
        $column->method('getType')->willReturn(Type::getType('binary'));

        $this->schemaManager->method('listTableColumns')->willReturnCallback(fn ($table) => 'Attendance' === $table ? ['uuid' => $column, 'id' => $this->createMock(Column::class)] : []);

        $resultMock = $this->createMock(Result::class);
        $resultMock->method('fetchOne')->willReturn(1);
        $this->db->method('executeQuery')->willReturn($resultMock);

        $this->assertTrue($this->migration->shouldRun());
    }

    public function testRunCreatesUuidColumn(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array('Attendance', $tables, true));
        $this->schemaManager->method('listTableColumns')->willReturnCallback(fn ($table) => 'Attendance' === $table ? ['id' => $this->createMock(Column::class)] : []);

        $resultMock = $this->createMock(Result::class);
        $resultMock->method('fetchFirstColumn')->willReturn([]);
        $this->db->method('executeQuery')->willReturn($resultMock);

        $this->db->expects($this->atLeastOnce())
            ->method('executeStatement')
            ->with($this->callback(static fn ($sql) => str_contains((string) $sql, 'ALTER TABLE Attendance ADD COLUMN uuid BINARY(16)')));

        $result = $this->migration->run();
        $this->assertTrue($result->isSuccessful());
    }

    public function testRunConvertsVarcharUuidToBinary(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array('Attendance', $tables, true));

        $column = $this->createMock(Column::class);
        $column->method('getLength')->willReturn(36);
        $column->method('getType')->willReturn(Type::getType('string'));

        $this->schemaManager->method('listTableColumns')->willReturnCallback(fn ($table) => 'Attendance' === $table ? ['uuid' => $column, 'id' => $this->createMock(Column::class)] : []);

        $resultMock = $this->createMock(Result::class);
        $resultMock->method('fetchFirstColumn')->willReturn([]);
        $this->db->method('executeQuery')->willReturn($resultMock);

        $this->db->expects($this->any())
            ->method('executeStatement')
            ->with($this->callback(static function ($sql) {
                if (str_contains((string) $sql, 'UPDATE Attendance SET uuid = UNHEX(REPLACE(uuid, \'-\', \'\'))')) {
                    return true;
                }
                if (str_contains((string) $sql, 'ALTER TABLE Attendance MODIFY uuid BINARY(16)')) {
                    return true;
                }

                return false;
            }));

        $result = $this->migration->run();
        $this->assertTrue($result->isSuccessful());
    }

    public function testRunAddsUniqueIndexForSpecificTables(): void
    {
        $this->schemaManager->method('tablesExist')->willReturnCallback(static fn ($tables) => \in_array($tables[0], ['Offer', 'OfferDate', 'OfferAttachment', 'OfferMedia'], true));

        $column = $this->createMock(Column::class);
        $column->method('getLength')->willReturn(16);
        $column->method('getType')->willReturn(Type::getType('binary'));

        $this->schemaManager->method('listTableColumns')->willReturn(['uuid' => $column, 'id' => $this->createMock(Column::class)]);
        $this->schemaManager->method('listTableIndexes')->willReturn([]);

        $resultMock = $this->createMock(Result::class);
        $resultMock->method('fetchFirstColumn')->willReturn([]);
        $this->db->method('executeQuery')->willReturn($resultMock);

        $this->db->expects($this->exactly(4))
            ->method('executeStatement')
            ->with($this->callback(static fn ($sql) => str_contains((string) $sql, 'ADD UNIQUE (uuid)')));

        $result = $this->migration->run();
        $this->assertTrue($result->isSuccessful());
    }
}
