<?php

declare(strict_types=1);

namespace Tests\Framework\Database\Migration;

use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationCollection;
use App\Framework\Database\Migration\MigrationVersion;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class MigrationCollectionTest extends TestCase
{
    private Migration $migration1;

    private Migration $migration2;

    private Migration $migration3;

    protected function setUp(): void
    {
        $this->migration1 = $this->createMockMigration('2024_01_01_120000', 'First migration');
        $this->migration2 = $this->createMockMigration('2024_01_02_120000', 'Second migration');
        $this->migration3 = $this->createMockMigration('2024_01_03_120000', 'Third migration');
    }

    #[Test]
    public function constructor_creates_empty_collection(): void
    {
        $collection = new MigrationCollection();

        $this->assertTrue($collection->isEmpty());
        $this->assertSame(0, $collection->count());
    }

    #[Test]
    public function constructor_accepts_variadic_migrations(): void
    {
        $collection = new MigrationCollection(
            $this->migration1,
            $this->migration2,
            $this->migration3
        );

        $this->assertFalse($collection->isEmpty());
        $this->assertSame(3, $collection->count());
    }

    #[Test]
    public function fromArray_creates_collection_from_array(): void
    {
        $migrations = [$this->migration1, $this->migration2, $this->migration3];
        $collection = MigrationCollection::fromArray($migrations);

        $this->assertSame(3, $collection->count());
    }

    #[Test]
    public function getIterator_allows_foreach_iteration(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $iterations = 0;
        foreach ($collection as $migration) {
            $this->assertInstanceOf(Migration::class, $migration);
            $iterations++;
        }

        $this->assertSame(2, $iterations);
    }

    #[Test]
    public function getByVersion_returns_migration_with_matching_version(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $version = MigrationVersion::fromTimestamp('2024_01_01_120000');
        $result = $collection->getByVersion($version);

        $this->assertSame($this->migration1, $result);
    }

    #[Test]
    public function getByVersion_returns_null_when_not_found(): void
    {
        $collection = new MigrationCollection($this->migration1);

        $version = MigrationVersion::fromTimestamp('2024_01_99_999999');
        $result = $collection->getByVersion($version);

        $this->assertNull($result);
    }

    #[Test]
    public function getByVersionString_returns_migration_with_matching_version_string(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $result = $collection->getByVersionString('2024_01_02_120000');

        $this->assertSame($this->migration2, $result);
    }

    #[Test]
    public function getByVersionString_returns_null_when_not_found(): void
    {
        $collection = new MigrationCollection($this->migration1);

        $result = $collection->getByVersionString('2024_01_99_999999');

        $this->assertNull($result);
    }

    #[Test]
    public function hasVersion_returns_true_when_version_exists(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $version = MigrationVersion::fromTimestamp('2024_01_01_120000');
        $result = $collection->hasVersion($version);

        $this->assertTrue($result);
    }

    #[Test]
    public function hasVersion_returns_false_when_version_does_not_exist(): void
    {
        $collection = new MigrationCollection($this->migration1);

        $version = MigrationVersion::fromTimestamp('2024_01_99_999999');
        $result = $collection->hasVersion($version);

        $this->assertFalse($result);
    }

    #[Test]
    public function hasVersionString_returns_true_when_version_string_exists(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $result = $collection->hasVersionString('2024_01_02_120000');

        $this->assertTrue($result);
    }

    #[Test]
    public function hasVersionString_returns_false_when_version_string_does_not_exist(): void
    {
        $collection = new MigrationCollection($this->migration1);

        $result = $collection->hasVersionString('2024_01_99_999999');

        $this->assertFalse($result);
    }

    #[Test]
    public function getVersions_returns_migration_version_collection(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $versions = $collection->getVersions();

        $this->assertSame(2, $versions->count());
        $this->assertTrue($versions->containsString('2024_01_01_120000'));
        $this->assertTrue($versions->containsString('2024_01_02_120000'));
    }

    #[Test]
    public function sorted_returns_collection_sorted_by_version_ascending(): void
    {
        // Create collection in reverse order
        $collection = new MigrationCollection($this->migration3, $this->migration1, $this->migration2);

        $sorted = $collection->sorted();
        $migrations = $sorted->toArray();

        $this->assertSame($this->migration1, $migrations[0]);
        $this->assertSame($this->migration2, $migrations[1]);
        $this->assertSame($this->migration3, $migrations[2]);
    }

    #[Test]
    public function sortedDescending_returns_collection_sorted_by_version_descending(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2, $this->migration3);

        $sorted = $collection->sortedDescending();
        $migrations = $sorted->toArray();

        $this->assertSame($this->migration3, $migrations[0]);
        $this->assertSame($this->migration2, $migrations[1]);
        $this->assertSame($this->migration1, $migrations[2]);
    }

    #[Test]
    public function filter_returns_collection_with_filtered_migrations(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2, $this->migration3);

        $filtered = $collection->filter(function (Migration $migration) {
            return str_contains($migration->getDescription(), 'Second');
        });

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

    #[Test]
    public function after_returns_migrations_after_specified_version(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2, $this->migration3);

        $version = MigrationVersion::fromTimestamp('2024_01_01_120000');
        $filtered = $collection->after($version);

        $this->assertSame(2, $filtered->count());
        $migrations = $filtered->toArray();
        $this->assertSame($this->migration2, $migrations[0]);
        $this->assertSame($this->migration3, $migrations[1]);
    }

    #[Test]
    public function upTo_returns_migrations_up_to_and_including_specified_version(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2, $this->migration3);

        $version = MigrationVersion::fromTimestamp('2024_01_02_120000');
        $filtered = $collection->upTo($version);

        $this->assertSame(2, $filtered->count());
        $migrations = $filtered->toArray();
        $this->assertSame($this->migration1, $migrations[0]);
        $this->assertSame($this->migration2, $migrations[1]);
    }

    #[Test]
    public function first_returns_first_migration_by_version(): void
    {
        $collection = new MigrationCollection($this->migration3, $this->migration1, $this->migration2);

        $first = $collection->first();

        $this->assertSame($this->migration1, $first);
    }

    #[Test]
    public function first_returns_null_for_empty_collection(): void
    {
        $collection = new MigrationCollection();

        $first = $collection->first();

        $this->assertNull($first);
    }

    #[Test]
    public function last_returns_last_migration_by_version(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration3, $this->migration2);

        $last = $collection->last();

        $this->assertSame($this->migration3, $last);
    }

    #[Test]
    public function last_returns_null_for_empty_collection(): void
    {
        $collection = new MigrationCollection();

        $last = $collection->last();

        $this->assertNull($last);
    }

    #[Test]
    public function toArray_returns_migrations_as_array(): void
    {
        $collection = new MigrationCollection($this->migration1, $this->migration2);

        $array = $collection->toArray();

        $this->assertIsArray($array);
        $this->assertSame(2, count($array));
        $this->assertSame($this->migration1, $array[0]);
        $this->assertSame($this->migration2, $array[1]);
    }

    #[Test]
    public function collection_is_immutable(): void
    {
        $collection = new MigrationCollection($this->migration1);

        $filtered = $collection->filter(fn () => true);
        $sorted = $collection->sorted();

        // Original collection should be unchanged
        $this->assertSame(1, $collection->count());
        $this->assertNotSame($collection, $filtered);
        $this->assertNotSame($collection, $sorted);
    }

    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;
    }
}
