<?php

namespace App\Tests\Unit\Service\Utils;

use App\Service\Utils\Reflection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ReflectionTestSubject {
    public function __construct($c) {}
    public function multiply(int $a, int $b): int { return $a * $b; }
    public static function add(int $a, int $b): int { return $a + $b; }
}

class ReflectionTest extends TestCase
{
    private ContainerInterface | MockObject $container;

    public function setUp(): void {
        $this->container = $this->getMockBuilder(ContainerInterface::class)->disableOriginalConstructor()->getMock();
    }

    /**
     * @see Reflection::dynamicInvokeServiceWithArgsAndMethod()
     */
    public function testDynamicInvokeServiceWithArgsAndMethod(): void {
        $reflection = $this->getMockBuilder(Reflection::class)
            ->setConstructorArgs([$this->container])
            ->setMethodsExcept(['dynamicInvokeServiceWithArgsAndMethod'])
            ->getMock();

        $subject = new ReflectionTestSubject(1);

        $this->container->method('get')->with('subject')->willReturn($subject);

        $this->assertEquals(
            6,
            $reflection->dynamicInvokeServiceWithArgsAndMethod('subject', 'multiply', [2, 3])
        );
    }

    /**
     * @see Reflection::dynamicInvokeServiceWithArgsAndMethod()
     */
    public function testDynamicInvokeServiceWithArgsAndClassNotFound(): void {
        $reflection = $this->getMockBuilder(Reflection::class)
            ->setConstructorArgs([$this->container])
            ->setMethodsExcept(['dynamicInvokeServiceWithArgsAndMethod'])
            ->getMock();

        $this->container->method('get')->with('subject')->willReturn(null);

        $this->expectException(\LogicException::class);

        $reflection->dynamicInvokeServiceWithArgsAndMethod('subject', 'invalid');
    }

    /**
     * @see Reflection::dynamicInvokeServiceWithArgsAndMethod()
     */
    public function testDynamicInvokeServiceWithArgsAndMethodInexistant(): void {
        $reflection = $this->getMockBuilder(Reflection::class)
            ->setConstructorArgs([$this->container])
            ->setMethodsExcept(['dynamicInvokeServiceWithArgsAndMethod'])
            ->getMock();

        $subject = $this->getMockBuilder(ReflectionTestSubject::class)->disableOriginalConstructor()->getMock();

        $this->container->method('get')->with('subject')->willReturn($subject);

        $this->expectException(\LogicException::class);

        $reflection->dynamicInvokeServiceWithArgsAndMethod('subject', 'invalid');
    }

    /**
     * @see Reflection::dynamicInvokeClassWithArgsAndMethod()
     */
    public function testDynamicInvokeClassWithArgsAndMethod(): void {
        $reflection = $this->getMockBuilder(Reflection::class)
            ->setConstructorArgs([$this->container])
            ->setMethodsExcept(['dynamicInvokeClassWithArgsAndMethod'])
            ->getMock();

        $this->assertEquals(
            6,
            $reflection->dynamicInvokeClassWithArgsAndMethod(
                ReflectionTestSubject::class,
                'multiply',
                [2, 3],
                [1]
            )
        );
    }

    /**
     * @see Reflection::dynamicInvokeClassWithArgsAndMethod()
     */
    public function testDynamicInvokeClassWithArgsAndMethodWithStatic(): void {
        $reflection = $this->getMockBuilder(Reflection::class)
            ->setConstructorArgs([$this->container])
            ->setMethodsExcept(['dynamicInvokeClassWithArgsAndMethod'])
            ->getMock();

        $this->assertEquals(
            5,
            $reflection->dynamicInvokeClassWithArgsAndMethod(ReflectionTestSubject::class, 'add', [2, 3])
        );
    }
}
