<?php

declare(strict_types=1);

namespace App\Framework\Mcp;

use App\Framework\DI\Container;
use JsonException;

final readonly class McpServer
{
    public function __construct(
        private Container $container,
        private McpToolRegistry $toolRegistry,
        private McpResourceRegistry $resourceRegistry
    ) {
    }

    public function handleRequest(string $jsonRequest): string
    {
        try {
            $request = json_decode($jsonRequest, true, 512, JSON_THROW_ON_ERROR);

            if (! isset($request['method'])) {
                return $this->createErrorResponse('Invalid request: method missing', $request['id'] ?? null);
            }

            $requestId = $request['id'] ?? null;

            return match ($request['method']) {
                'tools/list' => $this->listTools($requestId),
                'tools/call' => $this->callTool($request['params'] ?? [], $requestId),
                'resources/list' => $this->listResources($requestId),
                'resources/read' => $this->readResource($request['params'] ?? [], $requestId),
                'initialize' => $this->initialize($request['params'] ?? [], $requestId),
                default => $this->createErrorResponse("Unknown method: {$request['method']}", $requestId)
            };

        } catch (JsonException $e) {
            return $this->createErrorResponse('Invalid JSON: ' . $e->getMessage(), null);
        } catch (\Throwable $e) {
            return $this->createErrorResponse('Server error: ' . $e->getMessage(), null);
        }
    }

    private function initialize(array $params, $requestId = null): string
    {
        $response = [
            'jsonrpc' => '2.0',
            'result' => [
                'protocolVersion' => '2025-06-18', // Match client version
                'capabilities' => [
                    'tools' => [
                        'listChanged' => true,
                    ],
                    'resources' => [
                        'subscribe' => true,
                        'listChanged' => true,
                    ],
                ],
                'serverInfo' => [
                    'name' => 'Custom PHP Framework MCP Server',
                    'version' => '1.0.0',
                ],
            ],
            'id' => $requestId ?? 0,
        ];

        return json_encode($response, JSON_THROW_ON_ERROR);
    }

    private function listTools($requestId = null): string
    {
        $tools = [];

        foreach ($this->toolRegistry->getAllTools() as $tool) {
            $tools[] = [
                'name' => $tool['name'],
                'description' => $tool['description'],
                'inputSchema' => $tool['inputSchema'] ?: $this->generateInputSchema($tool['parameters']),
            ];
        }

        $response = [
            'jsonrpc' => '2.0',
            'result' => [
                'tools' => $tools,
            ],
            'id' => $requestId ?? 0,
        ];

        return json_encode($response, JSON_THROW_ON_ERROR);
    }

    private function callTool(array $params, $requestId = null): string
    {
        if (! isset($params['name'])) {
            return $this->createErrorResponse('Tool name is required', $requestId);
        }

        $tool = $this->toolRegistry->getTool($params['name']);
        if (! $tool) {
            return $this->createErrorResponse("Tool not found: {$params['name']}", $requestId);
        }

        try {
            $instance = $this->container->get($tool['class']);
            $method = $tool['method'];
            $arguments = $params['arguments'] ?? [];

            $result = $instance->$method(...$this->prepareArguments($tool['parameters'], $arguments));

            $response = [
                'jsonrpc' => '2.0',
                'result' => [
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => is_string($result) ? $result : json_encode($result, JSON_PRETTY_PRINT),
                        ],
                    ],
                ],
                'id' => $requestId ?? 0,
            ];

            return json_encode($response, JSON_THROW_ON_ERROR);

        } catch (\Throwable $e) {
            return $this->createErrorResponse("Tool execution failed: {$e->getMessage()}", $requestId);
        }
    }

    private function listResources($requestId = null): string
    {
        $resources = [];

        foreach ($this->resourceRegistry->getAllResources() as $resource) {
            $resources[] = [
                'uri' => $resource['uri'],
                'name' => $resource['name'],
                'description' => $resource['description'],
                'mimeType' => $resource['mimeType'],
            ];
        }

        $response = [
            'jsonrpc' => '2.0',
            'result' => [
                'resources' => $resources,
            ],
            'id' => $requestId ?? 0,
        ];

        return json_encode($response, JSON_THROW_ON_ERROR);
    }

    private function readResource(array $params, $requestId = null): string
    {
        if (! isset($params['uri'])) {
            return $this->createErrorResponse('Resource URI is required', $requestId);
        }

        $resource = $this->resourceRegistry->getResource($params['uri']);
        if (! $resource) {
            return $this->createErrorResponse("Resource not found: {$params['uri']}", $requestId);
        }

        try {
            $instance = $this->container->get($resource['class']);
            $method = $resource['method'];

            $content = $instance->$method();

            $response = [
                'jsonrpc' => '2.0',
                'result' => [
                    'contents' => [
                        [
                            'uri' => $resource['uri'],
                            'mimeType' => $resource['mimeType'],
                            'text' => is_string($content) ? $content : json_encode($content, JSON_PRETTY_PRINT),
                        ],
                    ],
                ],
                'id' => $requestId ?? 0,
            ];

            return json_encode($response, JSON_THROW_ON_ERROR);

        } catch (\Throwable $e) {
            return $this->createErrorResponse("Resource read failed: {$e->getMessage()}", $requestId);
        }
    }

    private function prepareArguments(array $parameters, array $arguments): array
    {
        $prepared = [];

        foreach ($parameters as $param) {
            $name = $param['name'];
            if (isset($arguments[$name])) {
                $prepared[] = $arguments[$name];
            } elseif ($param['required']) {
                throw new \InvalidArgumentException("Required parameter '{$name}' is missing");
            } else {
                $prepared[] = $param['default'];
            }
        }

        return $prepared;
    }

    private function generateInputSchema(array $parameters): array
    {
        $properties = [];
        $required = [];

        foreach ($parameters as $param) {
            $properties[$param['name']] = [
                'type' => $this->mapPhpTypeToJsonSchema($param['type']),
                'description' => "Parameter {$param['name']}",
            ];

            if ($param['required']) {
                $required[] = $param['name'];
            }
        }

        return [
            'type' => 'object',
            'properties' => $properties,
            'required' => $required,
        ];
    }

    private function mapPhpTypeToJsonSchema(string $phpType): string
    {
        return match ($phpType) {
            'int' => 'integer',
            'float' => 'number',
            'bool' => 'boolean',
            'array' => 'array',
            'string' => 'string',
            default => 'string',
        };
    }

    private function createErrorResponse(string $message, $requestId = null): string
    {
        $response = [
            'jsonrpc' => '2.0',
            'error' => [
                'code' => -32603, // Internal error code as per JSON-RPC 2.0
                'message' => $message,
            ],
        ];

        // Claude Desktop requires id to be string or number, never null
        if ($requestId !== null) {
            $response['id'] = $requestId;
        } else {
            $response['id'] = 0; // Default to 0 if no request ID
        }

        return json_encode($response, JSON_THROW_ON_ERROR);
    }
}
