<?php

declare(strict_types=1);
use Composer\InstalledVersions;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Bootstrap\LoadConfiguration;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;
use Laravel\Mcp\Facades\Mcp;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\McpServiceProvider;
use Laravel\Mcp\Server\Tool;

/**
 * Inspect this file before executing it. Supply installed, trusted public dependencies:
 * php reproduce.php /absolute/path/to/dependency-directory > observed-output.json
 * That directory must contain vendor/autoload.php; no Orbit application source is needed.
 * This script creates and cleans its own temporary Laravel bootstrap/cache files.
 * It does not download packages or call external HTTP endpoints. This is NOT a sandbox:
 * dependency autoloaders/providers are executable PHP and must be trusted by the operator.
 */
if (PHP_SAPI !== 'cli' || $argc !== 2 || ! is_dir($argv[1])) {
    fwrite(STDERR, "Usage: php reproduce.php /absolute/path/to/trusted/dependency-directory\n");
    exit(2);
}

$dependencies = realpath($argv[1]);
if (! is_file($dependencies.'/vendor/autoload.php')) {
    fwrite(STDERR, "The selected directory must contain vendor/autoload.php.\n");
    exit(2);
}
require $dependencies.'/vendor/autoload.php';

$requiredPackages = [
    'laravel/mcp' => ['version' => 'v1.0.1', 'reference' => '92987a9d03847801299ff4abe909ba7686147dde'],
    'laravel/framework' => ['version' => 'v13.34.0', 'reference' => 'c829b4982d29344cbcf1d7ad78eb5d2bb8ae66c4'],
];
foreach ($requiredPackages as $package => $expected) {
    if (! InstalledVersions::isInstalled($package)
        || InstalledVersions::getPrettyVersion($package) !== $expected['version']
        || InstalledVersions::getReference($package) !== $expected['reference']) {
        fwrite(STDERR, "Refusing to run: installed {$package} does not match the recorded version and commit.\n");
        exit(2);
    }
}

final class ProtocolReproductionTool extends Tool
{
    protected string $name = 'reproduction_read_only';

    protected string $description = 'Read-only constant response; only its description is listed in this experiment.';

    public function annotations(): array
    {
        return ['readOnlyHint' => true, 'destructiveHint' => false];
    }

    public function handle(): Response
    {
        return Response::text('local reproduction');
    }
}

final class ProtocolReproductionServer extends Server
{
    protected string $name = 'public-protocol-reproduction';

    protected string $version = '1';

    protected string $instructions = 'Minimal package-level protocol reproduction; no Orbit application or database.';

    protected array $tools = [ProtocolReproductionTool::class];
}

$temporary = sys_get_temp_dir().'/orbit-mcp-protocol-'.bin2hex(random_bytes(12));
if (! mkdir($temporary, 0700)) {
    throw new RuntimeException('Could not create the temporary application directory.');
}
// Remove only the private directory created by this invocation, including bootstrap caches.
register_shutdown_function(function () use ($temporary): void {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($temporary, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST,
    );
    foreach ($iterator as $file) {
        $file->isDir() && ! $file->isLink() ? rmdir($file->getPathname()) : unlink($file->getPathname());
    }
    rmdir($temporary);
});
foreach (['bootstrap/cache', 'config', 'storage/framework', 'storage/logs'] as $directory) {
    mkdir($temporary.'/'.$directory, 0700, true);
}
file_put_contents($temporary.'/bootstrap/cache/packages.php', "<?php return [];\n");

// Framework caches/environment files belong to our empty temporary application.
// Do not discover providers/configuration from the supplied dependency directory.
foreach ([
    'APP_ENV' => 'testing',
    'APP_CONFIG_CACHE' => $temporary.'/bootstrap/cache/config.php',
    'APP_SERVICES_CACHE' => $temporary.'/bootstrap/cache/services.php',
    'APP_PACKAGES_CACHE' => $temporary.'/bootstrap/cache/packages.php',
    'APP_ROUTES_CACHE' => $temporary.'/bootstrap/cache/routes.php',
    'APP_EVENTS_CACHE' => $temporary.'/bootstrap/cache/events.php',
    'COMPOSER_VENDOR_DIR' => $temporary.'/vendor',
] as $key => $value) {
    putenv($key.'='.$value);
    $_ENV[$key] = $_SERVER[$key] = $value;
}

$app = Application::configure(basePath: $temporary)
    ->withProviders([McpServiceProvider::class], withBootstrapProviders: false)
    ->withExceptions()
    ->create();
$app->useStoragePath($temporary.'/storage');
$app->afterBootstrapping(LoadConfiguration::class, function ($app): void {
    $app['config']->set([
        'app.env' => 'testing',
        'app.debug' => false,
        'app.url' => 'http://protocol-reproduction.invalid',
        'app.key' => 'base64:'.base64_encode(str_repeat('r', 32)),
        'app.providers' => ServiceProvider::defaultProviders()->toArray(),
        'cache.default' => 'array',
        'cache.limiter' => 'array',
        'database.default' => 'sqlite',
        'database.connections.sqlite.database' => ':memory:',
        'database.connections.sqlite.url' => null,
        'session.driver' => 'array',
        'queue.default' => 'sync',
        'logging.default' => 'stderr',
    ]);
    // This blocks Laravel HTTP-client requests, not arbitrary PHP networking.
    $factory = new Factory;
    $factory->preventStrayRequests();
    $app->instance(Factory::class, $factory);
});
$app->booted(function (): void {
    Mcp::web('/mcp', ProtocolReproductionServer::class);
});
$kernel = $app->make(Kernel::class);
$kernel->bootstrap();
Http::preventStrayRequests();

$meta = fn (string $version): array => [
    'io.modelcontextprotocol/protocolVersion' => $version,
    'io.modelcontextprotocol/clientCapabilities' => (object) [],
];
$initialize = fn (string $version): array => [
    'protocolVersion' => $version,
    'capabilities' => (object) [],
    'clientInfo' => ['name' => 'orbit-local-protocol-reproduction', 'version' => '1'],
];
$cases = [
    ['legacy_initialize_with_modern_meta', 'initialize', '2025-06-18', array_merge($initialize('2025-06-18'), ['_meta' => $meta('2025-06-18')]), 400, -32022],
    ['legacy_list_with_modern_meta', 'tools/list', '2025-06-18', ['_meta' => $meta('2025-06-18')], 400, -32022],
    ['changing_legacy_version_keeps_failure', 'tools/list', '2025-11-25', ['_meta' => $meta('2025-11-25')], 400, -32022],
    ['corrected_legacy_initialize', 'initialize', '2025-06-18', $initialize('2025-06-18'), 200, null],
    ['corrected_legacy_list', 'tools/list', '2025-06-18', (object) [], 200, null],
    ['modern_discovery', 'server/discover', '2026-07-28', ['_meta' => $meta('2026-07-28')], 200, null],
    ['modern_list', 'tools/list', '2026-07-28', ['_meta' => $meta('2026-07-28')], 200, null],
];

$observations = [];
$passed = true;
foreach ($cases as $index => [$name, $method, $version, $params, $expectedHttp, $expectedRpc]) {
    $headers = [
        'Content-Type' => 'application/json',
        'Accept' => 'application/json, text/event-stream',
        'MCP-Protocol-Version' => $version,
        'Mcp-Method' => $method,
        'X-Orbit-Traffic' => 'test',
    ];
    $body = ['jsonrpc' => '2.0', 'id' => $index + 1, 'method' => $method, 'params' => $params];
    $server = ['REMOTE_ADDR' => '127.0.0.1'];
    foreach ($headers as $key => $value) {
        $server[($key === 'Content-Type' ? '' : 'HTTP_').strtoupper(str_replace('-', '_', $key))] = $value;
    }
    $request = Request::create('http://protocol-reproduction.invalid/mcp', 'POST', [], [], [], $server, json_encode($body, JSON_THROW_ON_ERROR));
    $response = $kernel->handle($request);
    $json = json_decode($response->getContent(), false, 512, JSON_THROW_ON_ERROR);
    $matches = $response->getStatusCode() === $expectedHttp
        && ($json->error->code ?? null) === $expectedRpc
        && $json->id === $index + 1;
    if ($expectedHttp === 200) {
        $matches = $matches && isset($json->result);
        if ($method === 'tools/list') {
            $matches = $matches && count($json->result->tools ?? []) > 0;
        } elseif ($method === 'initialize') {
            $matches = $matches && ($json->result->protocolVersion ?? null) === $version;
        } elseif ($method === 'server/discover') {
            $matches = $matches && in_array($version, $json->result->supportedVersions ?? [], true);
        }
    }
    $observations[] = [
        'case' => $name,
        'request' => ['method' => 'POST', 'path' => '/mcp', 'headers' => $headers, 'body' => $body],
        'response' => ['http_status' => $response->getStatusCode(), 'content_type' => $response->headers->get('Content-Type'), 'body' => $json],
        'expected' => ['http_status' => $expectedHttp, 'rpc_error_code' => $expectedRpc],
        'matched_expectation' => $matches,
    ];
    $passed = $passed && $matches;
    $kernel->terminate($request, $response);
}

$sourceHashes = ['reproduce.php' => hash_file('sha256', __FILE__)];
foreach ([
    'src/Transport/JsonRpcRequest.php',
    'src/Server.php',
    'src/Enums/ProtocolVersion.php',
    'src/Server/Methods/Initialize.php',
    'src/Server/Middleware/ValidateMcpHeaders.php',
] as $file) {
    $sourceHashes['laravel/mcp/'.$file] = hash_file('sha256', InstalledVersions::getInstallPath('laravel/mcp').'/'.$file);
}

echo json_encode([
    'schema' => 'orbit.public-laravel-mcp-protocol-reproduction.v1',
    'generated_at_utc' => gmdate('Y-m-d\TH:i:s\Z'),
    'environment' => ['php_version' => PHP_VERSION, 'php_sapi' => PHP_SAPI, 'os_family' => PHP_OS_FAMILY, 'packages' => $requiredPackages],
    'execution' => ['transport' => 'minimal public-package in-process Laravel HTTP kernel; no network server', 'application_source' => 'inline in reproduce.php; no Orbit checkout loaded', 'temporary_files' => 'isolated application/bootstrap cache; removed on process shutdown', 'request_cache' => 'array', 'telemetry' => 'no telemetry provider registered', 'database' => 'empty in-memory SQLite configuration; no migrations or queries', 'requests' => 'initialize, tools/list, server/discover only; tools/call is not exercised', 'network_scope' => 'harness issues no external requests; Laravel HTTP-client stray requests blocked; trusted executable dependencies, not a sandbox'],
    'source_sha256' => $sourceHashes,
    'cases' => $observations,
    'all_expectations_matched' => $passed,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)."\n";
exit($passed ? 0 : 1);
