#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
 * MCP stdio <-> HTTP bridge.
 *
 * Usage:
 *   php bin/mcp-http-bridge.php --endpoint=https://example.com/NGauge/mcp/rpc
 *
 * Optional:
 *   --access-token=<token>   OAuth bearer token (or env MCP_ACCESS_TOKEN)
 *   --timeout=<seconds>      HTTP timeout in seconds (default 60)
 *   --insecure               Disable TLS verification (dev only)
 *   --header=<Name: Value>   Additional HTTP header (repeatable)
 */

const JSON_RPC_INTERNAL_ERROR = -32603;

$options = getopt('', ['endpoint:', 'access-token::', 'timeout::', 'insecure', 'header::']);

$endpoint = $options['endpoint'] ?? getenv('MCP_HTTP_ENDPOINT');
if (!is_string($endpoint) || trim($endpoint) === '') {
    fwrite(STDERR, "Missing --endpoint (or MCP_HTTP_ENDPOINT env).\n");
    exit(1);
}

$endpoint = trim($endpoint);
$accessToken = trim((string)($options['access-token'] ?? getenv('MCP_ACCESS_TOKEN') ?? ''));
$timeout = (int)($options['timeout'] ?? getenv('MCP_HTTP_TIMEOUT') ?? 60);
$timeout = $timeout > 0 ? $timeout : 60;
$insecure = array_key_exists('insecure', $options);
$extraHeaders = normalizeHeaders($options['header'] ?? []);

while (true) {
    $payload = readFramedMessage(STDIN);

    if ($payload === null) {
        exit(0);
    }

    $decodedRequest = json_decode($payload, true);
    $requestIds = extractRequestIds($decodedRequest);

    if (json_last_error() !== JSON_ERROR_NONE) {
        writeFramedJson(STDOUT, createErrorResponse(
            null,
            JSON_RPC_INTERNAL_ERROR,
            'Invalid JSON payload sent to MCP bridge.',
            ['jsonError' => json_last_error_msg()]
        ));
        continue;
    }

    $httpResponse = forwardToHttp(
        $endpoint,
        $payload,
        $accessToken,
        $timeout,
        $insecure,
        $extraHeaders
    );

    if (($httpResponse['ok'] ?? false) !== true) {
        if ($requestIds === []) {
            continue;
        }

        $errorData = [
            'bridge' => 'mcp-http-bridge',
            'httpStatus' => $httpResponse['status'] ?? null,
            'curlError' => $httpResponse['curlError'] ?? null,
            'response' => $httpResponse['body'] ?? null,
        ];

        writeFramedJson(STDOUT, createErrorPayloadForIds(
            $requestIds,
            JSON_RPC_INTERNAL_ERROR,
            'Failed to reach MCP HTTP endpoint.',
            $errorData
        ));
        continue;
    }

    $body = (string)($httpResponse['body'] ?? '');
    $trimmedBody = trim($body);

    if ($trimmedBody === '') {
        continue;
    }

    json_decode($trimmedBody, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        if ($requestIds === []) {
            continue;
        }

        writeFramedJson(STDOUT, createErrorPayloadForIds(
            $requestIds,
            JSON_RPC_INTERNAL_ERROR,
            'MCP HTTP endpoint returned non-JSON response.',
            ['response' => $trimmedBody]
        ));
        continue;
    }

    writeFramedMessage(STDOUT, $trimmedBody);
}

function readFramedMessage($stream): ?string
{
    $contentLength = null;

    while (($line = fgets($stream)) !== false) {
        $headerLine = rtrim($line, "\r\n");

        if ($headerLine === '') {
            break;
        }

        if (stripos($headerLine, 'Content-Length:') === 0) {
            $value = trim(substr($headerLine, strlen('Content-Length:')));
            $contentLength = is_numeric($value) ? (int)$value : null;
        }
    }

    if ($line === false) {
        return feof($stream) ? null : null;
    }

    if ($contentLength === null || $contentLength < 0) {
        return null;
    }

    $remaining = $contentLength;
    $payload = '';

    while ($remaining > 0) {
        $chunk = fread($stream, $remaining);
        if ($chunk === false || $chunk === '') {
            break;
        }

        $payload .= $chunk;
        $remaining -= strlen($chunk);
    }

    if (strlen($payload) !== $contentLength) {
        return null;
    }

    return $payload;
}

function writeFramedJson($stream, $payload): void
{
    $json = json_encode($payload, JSON_UNESCAPED_SLASHES);
    if ($json === false) {
        return;
    }

    writeFramedMessage($stream, $json);
}

function writeFramedMessage($stream, string $json): void
{
    $data = "Content-Length: " . strlen($json) . "\r\n\r\n" . $json;
    fwrite($stream, $data);
    fflush($stream);
}

function forwardToHttp(
    string $endpoint,
    string $payload,
    string $accessToken,
    int $timeout,
    bool $insecure,
    array $extraHeaders
): array {
    if (!function_exists('curl_init')) {
        return [
            'ok' => false,
            'status' => 0,
            'curlError' => 'php-curl extension is not available.',
            'body' => null,
        ];
    }

    $curl = curl_init($endpoint);
    if ($curl === false) {
        return [
            'ok' => false,
            'status' => 0,
            'curlError' => 'Unable to initialize cURL.',
            'body' => null,
        ];
    }

    $headers = [
        'Content-Type: application/json',
        'Accept: application/json',
    ];

    if ($accessToken !== '') {
        $headers[] = 'Authorization: Bearer ' . $accessToken;
    }

    if ($extraHeaders !== []) {
        $headers = array_merge($headers, $extraHeaders);
    }

    curl_setopt_array($curl, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_TIMEOUT => $timeout,
    ]);

    if ($insecure) {
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
    }

    $responseBody = curl_exec($curl);
    $status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    $error = curl_error($curl);
    curl_close($curl);

    if ($responseBody === false) {
        return [
            'ok' => false,
            'status' => $status,
            'curlError' => $error !== '' ? $error : 'Unknown cURL error',
            'body' => null,
        ];
    }

    return [
        'ok' => $status >= 200 && $status < 300,
        'status' => $status,
        'curlError' => $error !== '' ? $error : null,
        'body' => (string)$responseBody,
    ];
}

function createErrorPayloadForIds(array $requestIds, int $code, string $message, array $data)
{
    $responses = [];

    foreach ($requestIds as $requestId) {
        $responses[] = createErrorResponse($requestId, $code, $message, $data);
    }

    if (count($responses) === 1) {
        return $responses[0];
    }

    return $responses;
}

function createErrorResponse($id, int $code, string $message, array $data): array
{
    return [
        'jsonrpc' => '2.0',
        'id' => $id,
        'error' => [
            'code' => $code,
            'message' => $message,
            'data' => $data,
        ],
    ];
}

function extractRequestIds($decoded): array
{
    if (!is_array($decoded)) {
        return [];
    }

    if (array_key_exists('jsonrpc', $decoded)) {
        return array_key_exists('id', $decoded) ? [$decoded['id']] : [];
    }

    $ids = [];
    foreach ($decoded as $item) {
        if (is_array($item) && array_key_exists('jsonrpc', $item) && array_key_exists('id', $item)) {
            $ids[] = $item['id'];
        }
    }

    return $ids;
}

function normalizeHeaders($headers): array
{
    if (!is_array($headers)) {
        $headers = [$headers];
    }

    $normalized = [];
    foreach ($headers as $header) {
        if (!is_string($header)) {
            continue;
        }

        $value = trim($header);
        if ($value === '' || strpos($value, ':') === false) {
            continue;
        }

        $normalized[] = $value;
    }

    return $normalized;
}
