<?php

declare(strict_types=1);

namespace RedMCP;

final class McpHttpHandler
{
    private McpDispatcher $dispatcher;
    private string $token;
    private string $path;

    public function __construct(McpDispatcher $dispatcher, string $token, string $path = '/mcp')
    {
        $this->dispatcher = $dispatcher;
        $this->token = $token;
        $this->path = '/' . trim($path, '/');
    }

    public function handle(): void
    {
        if (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) !== $this->path) {
            $this->sendJson(404, ['error' => 'not found']);
            return;
        }
        if (!$this->originAllowed()) {
            $this->sendJson(403, ['error' => 'origin not allowed']);
            return;
        }
        if (!$this->authorized()) {
            header('WWW-Authenticate: Bearer');
            $this->sendJson(401, ['error' => 'unauthorized']);
            return;
        }
        if (($_SERVER['REQUEST_METHOD'] ?? '') === 'GET') {
            header('Allow: POST');
            $this->sendJson(405, ['error' => 'method not allowed']);
            return;
        }
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            header('Allow: POST');
            $this->sendJson(405, ['error' => 'method not allowed']);
            return;
        }

        $raw = file_get_contents('php://input');
        $decoded = json_decode(is_string($raw) ? $raw : '', true);
        if (!is_array($decoded)) {
            $this->sendJson(400, $this->errorResponse(null, -32700, 'Invalid JSON.'));
            return;
        }

        if (array_is_list($decoded)) {
            $responses = [];
            foreach ($decoded as $message) {
                if (!is_array($message)) {
                    $responses[] = $this->errorResponse(null, -32600, 'Invalid request.');
                    continue;
                }
                $response = $this->dispatcher->handleMessage($message, $this->logContext());
                if ($response !== null) {
                    $responses[] = $response;
                }
            }
            if ($responses === []) {
                http_response_code(202);
                return;
            }
            if ($this->acceptsEventStream()) {
                $this->sendEventStream($responses);
                return;
            }
            $this->sendJson(200, $responses);
            return;
        }

        $response = $this->dispatcher->handleMessage($decoded, $this->logContext());
        if ($response === null) {
            http_response_code(202);
            return;
        }

        if ($this->acceptsEventStream()) {
            $this->sendEventStream([$response]);
            return;
        }
        $this->sendJson(200, $response);
    }

    private function authorized(): bool
    {
        $header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        if (!is_string($header) || !str_starts_with($header, 'Bearer ')) {
            return false;
        }

        return hash_equals($this->token, substr($header, 7));
    }

    private function originAllowed(): bool
    {
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
        if (!is_string($origin) || trim($origin) === '') {
            return true;
        }

        $origin = trim($origin);
        foreach ($this->allowedOrigins() as $allowedOrigin) {
            if (hash_equals($allowedOrigin, $origin)) {
                return true;
            }
        }

        $host = parse_url($origin, PHP_URL_HOST);
        return is_string($host) && in_array(strtolower($host), ['localhost', '127.0.0.1', '::1'], true);
    }

    /**
     * @return array<int,string>
     */
    private function allowedOrigins(): array
    {
        $raw = getenv('MCP_ALLOWED_ORIGINS') ?: '';
        if (!is_string($raw) || trim($raw) === '') {
            return [];
        }

        return array_values(array_filter(array_map('trim', explode(',', $raw))));
    }

    private function acceptsEventStream(): bool
    {
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        return is_string($accept) && stripos($accept, 'text/event-stream') !== false;
    }

    /**
     * @return array<string,mixed>
     */
    private function logContext(): array
    {
        return [
            'transport' => 'http',
            'client_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
        ];
    }

    /**
     * @param mixed $payload
     */
    private function sendJson(int $status, $payload): void
    {
        http_response_code($status);
        header('Content-Type: application/json');
        echo json_encode($payload, JSON_UNESCAPED_SLASHES);
    }

    /**
     * @param array<int,array<string,mixed>> $messages
     */
    private function sendEventStream(array $messages): void
    {
        http_response_code(200);
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('X-Accel-Buffering: no');

        foreach ($messages as $message) {
            $encoded = json_encode($message, JSON_UNESCAPED_SLASHES);
            if ($encoded === false) {
                continue;
            }
            echo "event: message\n";
            echo 'data: ' . $encoded . "\n\n";
            flush();
        }
    }

    /**
     * @return array<string,mixed>
     */
    private function errorResponse(mixed $id, int $code, string $message): array
    {
        return [
            'jsonrpc' => '2.0',
            'id' => $id,
            'error' => [
                'code' => $code,
                'message' => $message,
            ],
        ];
    }
}
