<?php

declare(strict_types=1);

namespace App\Application\Public;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
 * Serves the OpenAPI document and a viewer for it.
 *
 *  - `GET /api/v1/openapi.yaml` — the raw spec, `application/yaml`.
 *  - `GET /api/docs`           — single-page HTML loading RapiDoc.
 *
 * RapiDoc is loaded from jsDelivr at a specific version; the
 * `<script>` tag carries an SRI `integrity="sha384-…"` and
 * `crossorigin="anonymous"` so the browser refuses to execute the
 * JS if a CDN compromise (or an inflight content modification)
 * delivers different bytes (SEC_REVIEW F58). We deliberately don't
 * ship a vendored copy in `public/` because the asset path would
 * need to coexist with FrankenPHP's static-file serving rules and
 * the M01 Caddyfile is configured to route everything through PHP.
 * The CDN URL is locked to a specific version, so an outage there
 * is the only failure mode.
 *
 * Bumping the RapiDoc version is a two-line change:
 *   1. Update {@see RAPIDOC_URL} to the new version path.
 *   2. Update {@see RAPIDOC_INTEGRITY} to:
 *        sha384- + base64(openssl dgst -sha384 -binary
 *                          rapidoc-min.js)
 *      You can compute it locally via:
 *        curl -sSL <url> | openssl dgst -sha384 -binary
 *          | base64 -w 0
 */
final class DocsController
{
    private const RAPIDOC_URL = 'https://cdn.jsdelivr.net/npm/rapidoc@9.3.4/dist/rapidoc-min.js';

    /**
     * SEC_REVIEW F58: SRI hash for rapidoc-min.js@9.3.4 served by
     * jsDelivr. Computed from the actual upstream bytes via
     * `openssl dgst -sha384 -binary | base64`. The browser refuses
     * to execute the script if the bytes don't match.
     */
    private const RAPIDOC_INTEGRITY = 'sha384-MDSxszbIJtK/9YakZ3tvi2bK6LaaHnB8+Hd2/fCfih0tLa+Mqlv6HO0bZdrICjjG';

    public function __construct(private readonly string $openapiPath)
    {
    }

    public function spec(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        if (!is_file($this->openapiPath) || !is_readable($this->openapiPath)) {
            $response->getBody()->write((string) json_encode(['error' => 'spec_unavailable']));

            return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
        }
        $body = (string) file_get_contents($this->openapiPath);
        $response->getBody()->write($body);

        return $response
            ->withStatus(200)
            ->withHeader('Content-Type', 'application/yaml; charset=utf-8')
            ->withHeader('Cache-Control', 'public, max-age=300');
    }

    public function viewer(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $rapidoc = self::RAPIDOC_URL;
        $integrity = self::RAPIDOC_INTEGRITY;
        $html = <<<HTML
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="utf-8">
                <title>IRDB — API Reference</title>
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <script type="module" src="{$rapidoc}"
                        integrity="{$integrity}"
                        crossorigin="anonymous"></script>
                <style>html, body { margin: 0; padding: 0; height: 100%; }</style>
            </head>
            <body>
                <rapi-doc
                    spec-url="/api/v1/openapi.yaml"
                    theme="dark"
                    bg-color="#0f172a"
                    text-color="#e2e8f0"
                    primary-color="#6366f1"
                    render-style="read"
                    show-header="false"
                    show-info="true"
                    allow-try="true"
                    allow-server-selection="true"
                    allow-authentication="true"
                    schema-style="table"
                    style="height: 100vh;">
                </rapi-doc>
            </body>
            </html>
            HTML;

        $response->getBody()->write($html);

        return $response
            ->withStatus(200)
            ->withHeader('Content-Type', 'text/html; charset=utf-8');
    }
}
