<?php

declare(strict_types=1);

namespace Apexl\Tests\Core\UnitTests;

use apexl\Io\factories\EntityFactory;
use Apexl\Tests\Core\Infrastructure\TestEntity;
use Faker\Factory as FakerFactory;
use Faker\Generator;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;

/**
 * Every factory used to build its own Faker\Generator, and Faker keeps the
 * unique() seen-set on the generator, so ->unique() never accumulated anything
 * across Entity::factory() calls.
 */
final class EntityFactoryFakerTest extends TestCase
{
    protected function setUp(): void
    {
        // The generator is shared for the whole process by design, so clear the
        // unique() seen-set rather than inheriting whatever earlier tests drew.
        $this->fakerOf(new EntityFactory(TestEntity::class))->unique(true);
    }

    private function fakerOf(EntityFactory $factory): Generator
    {
        return new ReflectionProperty(EntityFactory::class, 'faker')->getValue($factory);
    }

    #[Test]
    public function separatelyBuiltFactoriesShareOneGenerator(): void
    {
        $first  = new EntityFactory(TestEntity::class);
        $second = new EntityFactory(TestEntity::class);

        $this->assertSame($this->fakerOf($first), $this->fakerOf($second));
    }

    /**
     * The property read by baseDefinition() when it fills untouched entity fields
     * is the same shared generator, so that path accumulates uniqueness too.
     */
    #[Test]
    public function theInstancePropertyBaseDefinitionUsesIsTheSharedGenerator(): void
    {
        $factory = new readonly class(TestEntity::class) extends EntityFactory {
            public function faker(): Generator
            {
                return $this->faker;
            }
        };

        $this->assertSame($this->fakerOf(new EntityFactory(TestEntity::class)), $factory->faker());
    }

    /**
     * Draw exactly as many unique values as the range is wide, one per factory.
     * With a generator per factory this collides; with a shared one it cannot.
     */
    #[Test]
    public function uniqueValuesDoNotCollideAcrossFactoryInstances(): void
    {
        $width = 12;
        $drawn = [];

        for ($i = 0; $i < $width; $i++) {
            $factory = new EntityFactory(TestEntity::class);
            $drawn[] = $this->fakerOf($factory)->unique()->numberBetween(1, $width);
        }

        $this->assertCount($width, array_unique($drawn));
    }

    #[Test]
    public function uniqueWordsDoNotRepeatAcrossFactoryInstances(): void
    {
        $drawn = [];

        for ($i = 0; $i < 40; $i++) {
            $factory = new EntityFactory(TestEntity::class);
            $drawn[] = $this->fakerOf($factory)->unique()->word();
        }

        $this->assertSame($drawn, array_values(array_unique($drawn)));
    }

    #[Test]
    public function sharedGeneratorIsStableAcrossRepeatedCalls(): void
    {
        $faker = $this->fakerOf(new EntityFactory(TestEntity::class));

        for ($i = 0; $i < 5; $i++) {
            $this->assertSame($faker, $this->fakerOf(new EntityFactory(TestEntity::class)));
        }
    }

    /**
     * The generator stays overridable so a consumer can inject a seeded one.
     */
    #[Test]
    public function subclassesCanSupplyTheirOwnGenerator(): void
    {
        $factory = new readonly class(TestEntity::class) extends EntityFactory {
            public function faker(): Generator
            {
                return $this->faker;
            }

            protected static function sharedFaker(): Generator
            {
                static $own = null;

                return $own ??= FakerFactory::create();
            }
        };

        $this->assertNotSame($this->fakerOf(new EntityFactory(TestEntity::class)), $factory->faker());
    }
}
