<?php

declare(strict_types=1);

namespace ApiModel\Route;

use ApiModel\Infrastructure\Docs\OpenApiFromRoutesGenerator;
use JsonException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
use Slim\Psr7\Response;
use Slim\Routing\RouteCollectorProxy;

final class DocsRoutes
{
    /** Interfaz Swagger UI (CDN); la spec OpenAPI se genera al vuelo en `/docs/openapi.json`. */
    private const SWAGGER_UI_CSS = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.11.0/swagger-ui.css';
    private const SWAGGER_UI_BUNDLE = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.11.0/swagger-ui-bundle.js';
    private const SWAGGER_UI_STANDALONE = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js';

    public static function register(App $app, RouteCollectorProxy $g): void
    {
        $g->get('/docs/openapi.json', function (ServerRequestInterface $request) use ($app): ResponseInterface {
            try {
                $spec = OpenApiFromRoutesGenerator::fromApp($app, $request);
                $r = new Response(200);
                $r->getBody()->write(json_encode($spec, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));

                return $r
                    ->withHeader('Content-Type', 'application/json; charset=utf-8')
                    ->withHeader('Cache-Control', 'no-store');
            } catch (\Throwable $e) {
                $r = new Response(500);
                $r->getBody()->write(json_encode([
                    'error' => $e->getMessage(),
                    'file' => $e->getFile(),
                    'line' => $e->getLine(),
                    'trace' => $e->getTraceAsString()
                ], JSON_PRETTY_PRINT));
                return $r
                    ->withHeader('Content-Type', 'application/json; charset=utf-8')
                    ->withHeader('Cache-Control', 'no-store');
            }
        });

        // No usar `static function`: Slim intenta enlazar el contenedor y provoca Warning en CallableResolver.
        $swagger = function (ServerRequestInterface $request, ResponseInterface $_response): ResponseInterface {
            return self::swaggerUi($request);
        };
        $g->get('/docs', $swagger);
        $g->get('/docs/', $swagger);
    }

    private static function swaggerUi(ServerRequestInterface $request): ResponseInterface
    {
        try {
            $specUrlJson = json_encode(self::absoluteOpenApiUrl($request), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP);
        } catch (JsonException) {
            $specUrlJson = '"/docs/openapi.json"';
        }

        $css = htmlspecialchars(self::SWAGGER_UI_CSS, ENT_QUOTES, 'UTF-8');
        $bundle = htmlspecialchars(self::SWAGGER_UI_BUNDLE, ENT_QUOTES, 'UTF-8');
        $preset = htmlspecialchars(self::SWAGGER_UI_STANDALONE, ENT_QUOTES, 'UTF-8');

        $html = <<<HTML
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>API — documentación</title>
  <link rel="stylesheet" href="{$css}"/>
  <style>body{margin:0} #swagger-ui .topbar{display:none}</style>
</head>
<body>
 <div id="swagger-ui"></div>
  <script src="{$bundle}" crossorigin="anonymous"></script>
  <script src="{$preset}" crossorigin="anonymous"></script>
  <script>
    window.onload = function () {
      window.ui = SwaggerUIBundle({
        url: {$specUrlJson},
        dom_id: '#swagger-ui',
        deepLinking: true,
        presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
        layout: 'StandaloneLayout',
        tryItOutEnabled: true
      });
    };
  </script>
</body>
</html>
HTML;

        $r = new Response(200);
        $r->getBody()->write($html);

        return $r
            ->withHeader('Content-Type', 'text/html; charset=utf-8')
            ->withHeader('Cache-Control', 'no-cache');
    }

    private static function absoluteOpenApiUrl(ServerRequestInterface $request): string
    {
        $uri = $request->getUri();

        $scriptName = str_replace('\\', '/', (string) ($_SERVER['SCRIPT_NAME'] ?? ''));
        $base = dirname($scriptName);
        if ($base === '/' || $base === '.' || $base === '') {
            $base = '';
        }
        $specPath = ($base !== '' ? rtrim($base, '/') : '') . '/docs/openapi.json';

        $scheme = $uri->getScheme() !== '' ? $uri->getScheme() : 'http';
        if (! empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
            $scheme = strtolower(trim((string) $_SERVER['HTTP_X_FORWARDED_PROTO']));
        }
        $host = $uri->getHost();
        if ($host === '' && ! empty($_SERVER['HTTP_HOST'])) {
            $host = strtolower(trim((string) $_SERVER['HTTP_HOST']));
            if (str_contains($host, ':')) {
                $host = explode(':', $host, 2)[0];
            }
        }
        $port = $uri->getPort();

        if ($host === '') {
            return $specPath;
        }

        $authority = $host;
        if ($port !== null && ! in_array((int) $port, [80, 443], true)) {
            $authority .= ':' . $port;
        }

        return $scheme . '://' . $authority . $specPath;
    }
}
