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

declare(strict_types=1);

$repoRoot = dirname(__DIR__);
$tmpDir = sys_get_temp_dir() . '/redmcp-http-test-' . getmypid();
if (!mkdir($tmpDir, 0775, true) && !is_dir($tmpDir)) {
    throw new RuntimeException('Could not create temp test dir.');
}

$router = $tmpDir . '/router.php';
file_put_contents($router, <<<'PHP'
<?php
declare(strict_types=1);

use RedMCP\McpDispatcher;
use RedMCP\McpHttpHandler;
use RedMCP\RedmineClient;

require '%AUTOLOAD%';

$handler = new McpHttpHandler(
    new McpDispatcher(RedmineClient::fromCredentials('http://127.0.0.1', 'test-key')),
    'test-token',
    '/mcp'
);
$handler->handle();
PHP);
file_put_contents($router, str_replace('%AUTOLOAD%', addslashes($repoRoot . '/vendor/autoload.php'), (string) file_get_contents($router)));

$port = 18765 + (getmypid() % 1000);
$command = [PHP_BINARY, '-S', '127.0.0.1:' . $port, $router];
$process = proc_open($command, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $repoRoot);
if (!is_resource($process)) {
    throw new RuntimeException('Could not start PHP built-in server.');
}

$assertions = 0;
try {
    waitForServer($port);

    $sse = httpRequest(
        'POST',
        'http://127.0.0.1:' . $port . '/mcp',
        [
            'Authorization: Bearer test-token',
            'Content-Type: application/json',
            'Accept: application/json, text/event-stream',
        ],
        '{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}'
    );
    assertContains('HTTP/1.1 200 OK', $sse['headers'], 'SSE POST returns 200', $assertions);
    assertContains('text/event-stream', $sse['headers'], 'SSE POST returns event stream content type', $assertions);
    assertContains('X-Accel-Buffering: no', $sse['headers'], 'SSE POST disables proxy buffering', $assertions);
    assertContains("event: message\n", $sse['body'], 'SSE POST emits a message event', $assertions);
    assertContains('data: {"jsonrpc":"2.0","id":1,"result":[]}', $sse['body'], 'SSE POST emits JSON-RPC response data', $assertions);

    $json = httpRequest(
        'POST',
        'http://127.0.0.1:' . $port . '/mcp',
        [
            'Authorization: Bearer test-token',
            'Content-Type: application/json',
            'Accept: application/json',
        ],
        '{"jsonrpc":"2.0","id":2,"method":"ping","params":{}}'
    );
    assertContains('application/json', $json['headers'], 'JSON POST preserves application/json content type', $assertions);
    assertContains('"id":2', $json['body'], 'JSON POST emits JSON-RPC response body', $assertions);

    $get = httpRequest(
        'GET',
        'http://127.0.0.1:' . $port . '/mcp',
        [
            'Authorization: Bearer test-token',
            'Accept: text/event-stream',
        ],
        null
    );
    assertContains('HTTP/1.1 405 Method Not Allowed', $get['headers'], 'GET returns method-not-allowed until standalone streams exist', $assertions);
    assertContains('Allow: POST', $get['headers'], 'GET advertises supported method', $assertions);

    $origin = httpRequest(
        'POST',
        'http://127.0.0.1:' . $port . '/mcp',
        [
            'Authorization: Bearer test-token',
            'Content-Type: application/json',
            'Accept: application/json',
            'Origin: https://example.invalid',
        ],
        '{"jsonrpc":"2.0","id":3,"method":"ping","params":{}}'
    );
    assertContains('HTTP/1.1 403 Forbidden', $origin['headers'], 'disallowed browser origin returns forbidden', $assertions);

    fwrite(STDOUT, "OK {$assertions} assertions\n");
} finally {
    proc_terminate($process);
    proc_close($process);
    foreach ($pipes as $pipe) {
        if (is_resource($pipe)) {
            fclose($pipe);
        }
    }
    @unlink($router);
    @rmdir($tmpDir);
}

function waitForServer(int $port): void
{
    $deadline = microtime(true) + 5;
    while (microtime(true) < $deadline) {
        $socket = @fsockopen('127.0.0.1', $port, $errno, $errstr, 0.1);
        if (is_resource($socket)) {
            fclose($socket);
            return;
        }
        usleep(100000);
    }

    throw new RuntimeException('Timed out waiting for test HTTP server.');
}

/**
 * @param array<int,string> $headers
 * @return array{headers:string,body:string}
 */
function httpRequest(string $method, string $url, array $headers, ?string $body): array
{
    $curl = curl_init($url);
    if ($curl === false) {
        throw new RuntimeException('Could not initialize curl.');
    }
    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_HEADER, true);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    if ($body !== null) {
        curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
    }
    $raw = curl_exec($curl);
    if (!is_string($raw)) {
        throw new RuntimeException('curl failed: ' . curl_error($curl));
    }
    $headerSize = (int) curl_getinfo($curl, CURLINFO_HEADER_SIZE);
    curl_close($curl);

    return [
        'headers' => substr($raw, 0, $headerSize),
        'body' => substr($raw, $headerSize),
    ];
}

function assertContains(string $needle, string $haystack, string $message, int &$assertions): void
{
    $assertions++;
    if (strpos($haystack, $needle) !== false) {
        return;
    }

    fwrite(STDERR, "FAIL: {$message}\nNeedle: {$needle}\nHaystack: {$haystack}\n");
    exit(1);
}
