<?php

declare(strict_types=1);

namespace App\Tests\Unit\Mcp;

use App\Mcp\EventListener\CoerceJsonEncodedArgumentsListener;
use Mcp\Capability\Registry\ToolReference;
use Mcp\Capability\RegistryInterface;
use Mcp\Event\RequestEvent;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Tool;
use Mcp\Server\Session\SessionInterface;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 */
class CoerceJsonEncodedArgumentsListenerTest extends TestCase
{
    private const SCHEMA = [
        'type'       => 'object',
        'properties' => [
            'id'              => ['type' => ['integer', 'string']],
            'title'           => ['type' => 'string'],
            'tagIds'          => ['type' => ['array', 'null'], 'items' => ['type' => ['integer', 'string']]],
            'collaboratorIds' => ['type' => ['array', 'null'], 'items' => ['type' => ['integer', 'string']]],
        ],
    ];

    public function testDecodesJsonStringArrayForArrayTypedParam(): void
    {
        $result = $this->handle(['tagIds' => '[3]', 'collaboratorIds' => '[5,6]']);

        self::assertSame([3], $result->arguments['tagIds']);
        self::assertSame([5, 6], $result->arguments['collaboratorIds']);
    }

    public function testLeavesRealArrayUntouched(): void
    {
        $result = $this->handle(['tagIds' => [3]]);

        self::assertSame([3], $result->arguments['tagIds']);
    }

    public function testDoesNotTouchStringTypedParamEvenIfItLooksLikeJson(): void
    {
        $result = $this->handle(['title' => '[1,2]']);

        // title is schema-typed string -> must stay the literal string.
        self::assertSame('[1,2]', $result->arguments['title']);
    }

    public function testLeavesScalarTypedParamUntouched(): void
    {
        // id is integer/string typed -> not an array/object, handled by the schema
        // relaxation + SDK cast, not by this listener.
        $result = $this->handle(['id' => '463']);

        self::assertSame('463', $result->arguments['id']);
    }

    public function testPreservesRequestId(): void
    {
        $result = $this->handle(['tagIds' => '[3]']);

        self::assertSame(1, $result->getId());
    }

    /**
     * @param array<string, mixed> $arguments
     */
    private function handle(array $arguments): CallToolRequest
    {
        $tool      = new Tool('update-task', self::SCHEMA, null, null);
        $reference = new ToolReference($tool, static fn () => null);

        $registry = $this->createMock(RegistryInterface::class);
        $registry->method('getTool')->willReturn($reference);

        $request = new CallToolRequest('update-task', $arguments)->withId(1);
        $event   = new RequestEvent($request, $this->createMock(SessionInterface::class));

        (new CoerceJsonEncodedArgumentsListener($registry))($event);

        $result = $event->getRequest();
        self::assertInstanceOf(CallToolRequest::class, $result);

        return $result;
    }
}
