<?php

declare(strict_types=1);

namespace Tests\Framework\Database\Migration;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationCollection;
use App\Framework\Database\Migration\MigrationLoader;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\DI\Container;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;
use App\Framework\Discovery\ValueObjects\InterfaceMapping;
use App\Framework\Filesystem\ValueObjects\FilePath;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class MigrationLoaderTest extends TestCase
{
    private Container $container;

    protected function setUp(): void
    {
        $this->container = $this->createMock(Container::class);
    }

    #[Test]
    public function loadMigrations_returns_empty_collection_when_no_migrations_found(): void
    {
        // Setup interface registry with no migrations
        $interfaceRegistry = new InterfaceRegistry();

        $discoveryRegistry = new DiscoveryRegistry(
            new AttributeRegistry(),
            $interfaceRegistry,
            new TemplateRegistry()
        );

        $loader = new MigrationLoader($discoveryRegistry, $this->container);

        $result = $loader->loadMigrations();

        $this->assertInstanceOf(MigrationCollection::class, $result);
        $this->assertTrue($result->isEmpty());
        $this->assertSame(0, $result->count());
    }

    #[Test]
    public function loadMigrations_returns_sorted_collection_of_migrations(): void
    {
        // Create mock migration instances (in unsorted order)
        $migration1 = $this->createMockMigration('2024_01_03_120000', 'Third migration');
        $migration2 = $this->createMockMigration('2024_01_01_120000', 'First migration');
        $migration3 = $this->createMockMigration('2024_01_02_120000', 'Second migration');

        // Create interface mappings
        $mapping1 = new InterfaceMapping(
            ClassName::create(Migration::class),
            ClassName::create('App\\Migration1'),
            FilePath::create('/path/to/migration1.php')
        );
        $mapping2 = new InterfaceMapping(
            ClassName::create(Migration::class),
            ClassName::create('App\\Migration2'),
            FilePath::create('/path/to/migration2.php')
        );
        $mapping3 = new InterfaceMapping(
            ClassName::create(Migration::class),
            ClassName::create('App\\Migration3'),
            FilePath::create('/path/to/migration3.php')
        );

        // Setup interface registry
        $interfaceRegistry = new InterfaceRegistry();
        $interfaceRegistry->add($mapping1);
        $interfaceRegistry->add($mapping2);
        $interfaceRegistry->add($mapping3);

        $discoveryRegistry = new DiscoveryRegistry(
            new AttributeRegistry(),
            $interfaceRegistry,
            new TemplateRegistry()
        );

        $loader = new MigrationLoader($discoveryRegistry, $this->container);

        // Setup container to return migration instances
        $this->container->method('get')
            ->willReturnMap([
                ['App\\Migration1', $migration1],
                ['App\\Migration2', $migration2],
                ['App\\Migration3', $migration3],
            ]);

        $result = $loader->loadMigrations();

        $this->assertInstanceOf(MigrationCollection::class, $result);
        $this->assertSame(3, $result->count());

        // Verify migrations are sorted by version
        $migrations = $result->toArray();
        $this->assertSame($migration2, $migrations[0]); // 2024_01_01_120000
        $this->assertSame($migration3, $migrations[1]); // 2024_01_02_120000
        $this->assertSame($migration1, $migrations[2]); // 2024_01_03_120000
    }

    #[Test]
    public function loadMigrations_uses_discovery_registry_to_find_migration_implementations(): void
    {
        $interfaceRegistry = new InterfaceRegistry();

        $discoveryRegistry = new DiscoveryRegistry(
            new AttributeRegistry(),
            $interfaceRegistry,
            new TemplateRegistry()
        );

        $loader = new MigrationLoader($discoveryRegistry, $this->container);

        $result = $loader->loadMigrations();

        // Verify that the discovery registry was used by checking we get an empty collection
        $this->assertInstanceOf(MigrationCollection::class, $result);
        $this->assertTrue($result->isEmpty());
    }

    #[Test]
    public function loadMigrations_uses_container_to_instantiate_migrations(): void
    {
        $migration = $this->createMockMigration('2024_01_01_120000', 'Test migration');

        $mapping = new InterfaceMapping(
            ClassName::create(Migration::class),
            ClassName::create('App\\TestMigration'),
            FilePath::create('/path/to/test_migration.php')
        );

        $interfaceRegistry = new InterfaceRegistry();
        $interfaceRegistry->add($mapping);

        $discoveryRegistry = new DiscoveryRegistry(
            new AttributeRegistry(),
            $interfaceRegistry,
            new TemplateRegistry()
        );

        $loader = new MigrationLoader($discoveryRegistry, $this->container);

        $this->container->expects($this->once())
            ->method('get')
            ->with('App\\TestMigration')
            ->willReturn($migration);

        $result = $loader->loadMigrations();

        $this->assertSame(1, $result->count());
        $this->assertSame($migration, $result->toArray()[0]);
    }

    private function createMockMigration(string $version, string $description): Migration
    {
        $migration = $this->createMock(Migration::class);
        $migration->method('getVersion')
            ->willReturn(MigrationVersion::fromTimestamp($version));
        $migration->method('getDescription')
            ->willReturn($description);

        return $migration;
    }
}
