<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionMethod;

class ContainerFactoryContractTest extends TestCase
{
    /**
     * @return array<int, array{0: class-string}>
     */
    public static function factoryClassProvider(): array
    {
        return [
            [\MintyPHP\Service\Access\AccessServicesFactory::class],
            [\MintyPHP\Service\Auth\AuthServicesFactory::class],
            [\MintyPHP\Service\Branding\BrandingServicesFactory::class],
            [\MintyPHP\Service\Directory\DirectoryServicesFactory::class],
            [\MintyPHP\Service\Import\ImportServicesFactory::class],
            [\MintyPHP\Service\Mail\MailServicesFactory::class],
            [\MintyPHP\Service\Scheduler\SchedulerServicesFactory::class],
            [\MintyPHP\Service\Security\SecurityServicesFactory::class],
            [\MintyPHP\Service\Settings\SettingServicesFactory::class],
            [\MintyPHP\Service\Tenant\TenantServicesFactory::class],
            [\MintyPHP\Service\User\UserServicesFactory::class],
        ];
    }

    #[DataProvider('factoryClassProvider')]
    public function testContainerFactoriesCachePublicNoArgCreateMethods(string $factoryClass): void
    {
        $factory = app($factoryClass);
        $this->assertInstanceOf($factoryClass, $factory);

        $reflection = new ReflectionClass($factoryClass);
        $methods = array_filter(
            $reflection->getMethods(ReflectionMethod::IS_PUBLIC),
            static fn (ReflectionMethod $method): bool =>
                !$method->isStatic()
                && str_starts_with($method->getName(), 'create')
                && $method->getNumberOfRequiredParameters() === 0
        );
        usort($methods, static fn (ReflectionMethod $a, ReflectionMethod $b): int => strcmp($a->getName(), $b->getName()));

        foreach ($methods as $method) {
            $methodName = $method->getName();
            $first = $factory->{$methodName}();
            $second = $factory->{$methodName}();

            $this->assertIsObject($first, sprintf('%s::%s() must return an object', $factoryClass, $methodName));
            $this->assertInstanceOf($first::class, $second, sprintf('%s::%s() must be type-stable', $factoryClass, $methodName));
            $this->assertSame($first, $second, sprintf('%s::%s() must return the cached instance', $factoryClass, $methodName));
        }
    }
}
