<?php

declare(strict_types=1);

use App\Framework\DI\Container;
use App\Framework\Mcp\McpResourceRegistry;
use App\Framework\Mcp\McpServer;
use App\Framework\Mcp\McpToolRegistry;

beforeEach(function () {
    $this->container = Mockery::mock(Container::class);
    $this->toolRegistry = Mockery::mock(McpToolRegistry::class);
    $this->resourceRegistry = Mockery::mock(McpResourceRegistry::class);

    $this->mcpServer = new McpServer(
        $this->container,
        $this->toolRegistry,
        $this->resourceRegistry
    );
});

afterEach(function () {
    Mockery::close();
});

test('initialize returns correct protocol response', function () {
    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'initialize',
        'params' => [],
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('jsonrpc', '2.0')
        ->and($decoded)->toHaveKey('id', 1)
        ->and($decoded['result'])->toHaveKey('protocolVersion')
        ->and($decoded['result'])->toHaveKey('capabilities')
        ->and($decoded['result'])->toHaveKey('serverInfo')
        ->and($decoded['result']['serverInfo']['name'])->toBe('Custom PHP Framework MCP Server');
});

test('tools list returns registered tools', function () {
    $mockTools = [
        [
            'name' => 'test_tool',
            'description' => 'A test tool',
            'parameters' => [],
            'inputSchema' => null,
        ],
    ];

    $this->toolRegistry
        ->shouldReceive('getAllTools')
        ->andReturn($mockTools);

    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'tools/list',
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('result')
        ->and($decoded['result'])->toHaveKey('tools')
        ->and($decoded['result']['tools'])->toHaveCount(1)
        ->and($decoded['result']['tools'][0]['name'])->toBe('test_tool');
});

test('resources list returns registered resources', function () {
    $mockResources = [
        [
            'uri' => 'framework://test',
            'name' => 'Test Resource',
            'description' => 'A test resource',
            'mimeType' => 'application/json',
        ],
    ];

    $this->resourceRegistry
        ->shouldReceive('getAllResources')
        ->andReturn($mockResources);

    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'resources/list',
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('result')
        ->and($decoded['result'])->toHaveKey('resources')
        ->and($decoded['result']['resources'])->toHaveCount(1)
        ->and($decoded['result']['resources'][0]['uri'])->toBe('framework://test');
});

test('invalid json request returns error', function () {
    $invalidJson = '{"invalid": json}';

    $response = $this->mcpServer->handleRequest($invalidJson);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('error')
        ->and($decoded['error']['message'])->toContain('Invalid JSON');
});

test('missing method returns error', function () {
    $request = json_encode([
        'jsonrpc' => '2.0',
        'id' => 1,
        // Missing 'method' field
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('error')
        ->and($decoded['error']['message'])->toContain('method missing');
});

test('unknown method returns error', function () {
    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'unknown/method',
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('error')
        ->and($decoded['error']['message'])->toContain('Unknown method');
});

test('tool call executes registered tool', function () {
    $mockTool = [
        'name' => 'test_tool',
        'class' => 'TestToolClass',
        'method' => 'execute',
        'parameters' => [],
    ];

    $mockToolInstance = new class () {
        public function execute(array $params): array
        {
            return ['result' => 'success', 'data' => $params];
        }
    };

    $this->toolRegistry
        ->shouldReceive('getTool')
        ->with('test_tool')
        ->andReturn($mockTool);

    $this->container
        ->shouldReceive('get')
        ->with('TestToolClass')
        ->andReturn($mockToolInstance);

    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'tools/call',
        'params' => [
            'name' => 'test_tool',
            'arguments' => ['param1' => 'value1'],
        ],
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('result')
        ->and($decoded['result']['content'])->toHaveCount(1)
        ->and($decoded['result']['content'][0]['type'])->toBe('text');
});

test('resource read returns resource content', function () {
    $mockResource = [
        'uri' => 'framework://test',
        'class' => 'TestResourceClass',
        'method' => 'read',
    ];

    $mockResourceInstance = new class () {
        public function read(string $uri): array
        {
            return ['content' => 'test content', 'mimeType' => 'text/plain'];
        }
    };

    $this->resourceRegistry
        ->shouldReceive('getResource')
        ->with('framework://test')
        ->andReturn($mockResource);

    $this->container
        ->shouldReceive('get')
        ->with('TestResourceClass')
        ->andReturn($mockResourceInstance);

    $request = json_encode([
        'jsonrpc' => '2.0',
        'method' => 'resources/read',
        'params' => [
            'uri' => 'framework://test',
        ],
        'id' => 1,
    ]);

    $response = $this->mcpServer->handleRequest($request);
    $decoded = json_decode($response, true);

    expect($decoded)->toHaveKey('result')
        ->and($decoded['result']['contents'])->toHaveCount(1)
        ->and($decoded['result']['contents'][0]['uri'])->toBe('framework://test');
});
