<?php

declare(strict_types=1);

namespace ApiModel\Infrastructure\Docs;

use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

/**
 * Construye un documento OpenAPI 3.0 a partir de las rutas registradas en Slim (en tiempo de petición).
 */
final class OpenApiFromRoutesGenerator
{
    private const OPENAPI_VERSION = '3.0.3';

    /** @return array<string, mixed> */
    public static function fromApp(App $app, ServerRequestInterface $request): array
    {
        $paths = [];
        foreach ($app->getRouteCollector()->getRoutes() as $route) {
            $openApiPath = self::slimPatternToOpenApiPath($route->getPattern());
            if ($openApiPath === null) {
                continue;
            }
            foreach ($route->getMethods() as $method) {
                $m = strtoupper($method);
                if (in_array($m, ['OPTIONS', 'HEAD', 'TRACE'], true)) {
                    continue;
                }
                if (! isset($paths[$openApiPath])) {
                    $paths[$openApiPath] = [];
                }
                $paths[$openApiPath][strtolower($m)] = self::operationFor($m, $openApiPath);
            }
        }

        ksort($paths);

        return [
            'openapi' => self::OPENAPI_VERSION,
            'info' => [
                'title' => (string) ($_ENV['OPENAPI_TITLE'] ?? 'API'),
                'version' => (string) ($_ENV['OPENAPI_VERSION'] ?? '1.0.0'),
                'description' => 'Generado automáticamente desde las rutas Slim al consultar este recurso.',
            ],
            'servers' => [
                ['url' => self::serverBaseUrl($request)],
            ],
            'paths' => $paths,
            'components' => [
                'securitySchemes' => [
                    'bearerAuth' => [
                        'type' => 'http',
                        'scheme' => 'bearer',
                        'bearerFormat' => 'JWT',
                    ],
                ],
            ],
        ];
    }

    private static function serverBaseUrl(ServerRequestInterface $request): string
    {
        $uri = $request->getUri();
        $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();
        $authority = $host;
        if ($authority !== '' && $port !== null && ! in_array((int) $port, [80, 443], true)) {
            $authority .= ':' . $port;
        }
        $scriptName = str_replace('\\', '/', (string) ($_SERVER['SCRIPT_NAME'] ?? ''));
        $base = dirname($scriptName);
        if ($base === '/' || $base === '.' || $base === '') {
            $prefix = '';
        } else {
            $prefix = rtrim($base, '/');
        }

        if ($authority === '') {
            return $prefix === '' ? '/' : $prefix;
        }

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

    /** @return array<string, mixed> */
    private static function operationFor(string $method, string $openApiPath): array
    {
        $op = [
            'tags' => [self::inferTag($openApiPath)],
            'summary' => $method . ' ' . $openApiPath,
            'responses' => [
                '200' => ['description' => 'Respuesta exitosa (esquema depende del endpoint)'],
            ],
        ];

        $params = self::pathParameters($openApiPath);
        if ($params !== []) {
            $op['parameters'] = $params;
        }

        if (in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
            $schema = self::requestBodySchema($openApiPath, $method);
            if ($schema !== null) {
                $op['requestBody'] = [
                    'required' => true,
                    'content' => [
                        'application/json' => [
                            'schema' => $schema,
                        ],
                    ],
                ];
            }
        }

        if (self::suggestBearer($openApiPath, $method)) {
            $op['security'] = [['bearerAuth' => []]];
        }

        return $op;
    }

    /** @return array<string, mixed>|null */
    private static function requestBodySchema(string $openApiPath, string $method): ?array
    {
        $properties = [];
        $required = [];

        if ($openApiPath === '/auth/login') {
            $properties = [
                'email' => ['type' => 'string', 'format' => 'email', 'example' => 'wilmer78@gmail.com'],
                'password' => ['type' => 'string', 'example' => 'Secret123!'],
                'lang' => ['type' => 'string', 'enum' => ['es', 'en'], 'default' => 'es'],
            ];
            $required = ['email', 'password'];
        } elseif ($openApiPath === '/auth/refresh' || $openApiPath === '/auth/logout') {
            $properties = [
                'refresh_token' => ['type' => 'string', 'example' => 'some-refresh-token-value'],
            ];
            $required = ['refresh_token'];
        } elseif ($openApiPath === '/auth/forgot-password') {
            $properties = [
                'email' => ['type' => 'string', 'format' => 'email', 'example' => 'wilmer78@gmail.com'],
                'lang' => ['type' => 'string', 'enum' => ['es', 'en'], 'default' => 'es'],
            ];
            $required = ['email'];
        } elseif ($openApiPath === '/auth/reset-password') {
            $properties = [
                'email' => ['type' => 'string', 'format' => 'email', 'example' => 'wilmer78@gmail.com'],
                'code' => ['type' => 'string', 'pattern' => '^\d{6}$', 'example' => '123456'],
                'new_password' => ['type' => 'string', 'example' => 'NewSecret123!'],
                'lang' => ['type' => 'string', 'enum' => ['es', 'en'], 'default' => 'es'],
            ];
            $required = ['email', 'code', 'new_password'];
        } elseif ($openApiPath === '/security/roles' || $openApiPath === '/security/roles/{id}') {
            $properties = [
                'name_es' => ['type' => 'string', 'example' => 'Administrador'],
                'name_en' => ['type' => 'string', 'example' => 'Admin'],
                'description_es' => ['type' => 'string', 'example' => 'Permisos completos'],
                'description_en' => ['type' => 'string', 'example' => 'Full access'],
                'is_active' => ['type' => 'boolean', 'default' => true],
            ];
            $required = ['name_es', 'name_en'];
        } elseif ($openApiPath === '/security/roles/{id}/permissions') {
            $properties = [
                'page_id' => ['type' => 'string', 'format' => 'uuid', 'example' => 'f0a1b2c3-d4e5-4f5a-6b7c-8d9e0f1a2b3c'],
                'can_view' => ['type' => 'boolean', 'default' => false],
                'can_create' => ['type' => 'boolean', 'default' => false],
                'can_edit' => ['type' => 'boolean', 'default' => false],
                'can_delete' => ['type' => 'boolean', 'default' => false],
            ];
            $required = ['page_id'];
        } elseif ($openApiPath === '/security/modules' || $openApiPath === '/security/modules/{id}') {
            $properties = [
                'name_es' => ['type' => 'string', 'example' => 'Contabilidad'],
                'name_en' => ['type' => 'string', 'example' => 'Accounting'],
                'icon' => ['type' => 'string', 'example' => 'receipt'],
                'order_index' => ['type' => 'integer', 'default' => 0],
                'is_active' => ['type' => 'boolean', 'default' => true],
            ];
            $required = ['name_es', 'name_en'];
        } elseif ($openApiPath === '/security/pages' || $openApiPath === '/security/pages/{id}') {
            $properties = [
                'module_id' => ['type' => 'string', 'format' => 'uuid', 'example' => 'f0a1b2c3-d4e5-4f5a-6b7c-8d9e0f1a2b3c'],
                'name_es' => ['type' => 'string', 'example' => 'Plan de Cuentas'],
                'name_en' => ['type' => 'string', 'example' => 'Chart of Accounts'],
                'path' => ['type' => 'string', 'example' => '/accounting/chart'],
                'icon' => ['type' => 'string', 'example' => 'account_tree'],
                'order_index' => ['type' => 'integer', 'default' => 0],
                'is_active' => ['type' => 'boolean', 'default' => true],
            ];
            $required = ['module_id', 'name_es', 'name_en', 'path'];
        } elseif ($openApiPath === '/security/messages' || $openApiPath === '/security/messages/{id}') {
            $properties = [
                'code' => ['type' => 'string', 'example' => 'PASSWORD_RESET'],
                'subject_es' => ['type' => 'string', 'example' => 'Código de recuperación'],
                'subject_en' => ['type' => 'string', 'example' => 'Password reset code'],
                'body_html_es' => ['type' => 'string', 'example' => '<p>Código: {{code}}</p>'],
                'body_html_en' => ['type' => 'string', 'example' => '<p>Code: {{code}}</p>'],
                'is_active' => ['type' => 'boolean', 'default' => true],
            ];
            $required = ['code', 'subject_es', 'subject_en', 'body_html_es', 'body_html_en'];
        } elseif ($openApiPath === '/security/users' || $openApiPath === '/security/users/{id}') {
            $properties = [
                'name' => ['type' => 'string', 'example' => 'Juan Perez'],
                'email' => ['type' => 'string', 'format' => 'email', 'example' => 'juan@example.com'],
                'password' => ['type' => 'string', 'example' => 'Secret123!'],
                'activo' => ['type' => 'boolean', 'default' => true],
                'roles' => [
                    'type' => 'array',
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'id' => ['type' => 'string', 'format' => 'uuid'],
                            'activo' => ['type' => 'boolean'],
                        ],
                        'required' => ['id', 'activo'],
                    ],
                ],
            ];
            $required = ['name', 'email'];
        } elseif ($openApiPath === '/security/logs/detail') {
            $properties = [
                'id' => ['type' => 'string', 'format' => 'uuid', 'example' => 'f0a1b2c3-d4e5-4f5a-6b7c-8d9e0f1a2b3c'],
            ];
            $required = ['id'];
        } elseif ($openApiPath === '/platform/users' || $openApiPath === '/platform/users/{id}') {
            $properties = [
                'email' => ['type' => 'string', 'format' => 'email', 'example' => 'juan@example.com'],
                'name' => ['type' => 'string', 'example' => 'Juan Perez'],
                'password' => ['type' => 'string', 'example' => 'Secret123!'],
                'role' => ['type' => 'string', 'example' => 'PLATFORM_ADMIN'],
                'is_active' => ['type' => 'boolean', 'default' => true],
            ];
            $required = ['email', 'name', 'password', 'role'];
        } elseif ($openApiPath === '/users/me/language') {
            $properties = [
                'language' => ['type' => 'string', 'enum' => ['es', 'en'], 'default' => 'es'],
            ];
            $required = ['language'];
        } elseif ($openApiPath === '/notifications/read-all' || $openApiPath === '/notifications/{id}/read') {
            return null;
        }

        // Para peticiones PATCH, ningún campo es obligatorio
        if ($method === 'PATCH') {
            $required = [];
        }

        $schema = ['type' => 'object'];
        if ($properties !== []) {
            $schema['properties'] = $properties;
        }
        if ($required !== []) {
            $schema['required'] = $required;
        }

        return $schema;
    }

    private static function suggestBearer(string $openApiPath, string $method): bool
    {
        if ($openApiPath === '/metrics') {
            return false;
        }
        if (str_starts_with($openApiPath, '/auth/login')
            || str_starts_with($openApiPath, '/auth/refresh')
            || str_starts_with($openApiPath, '/auth/forgot-password')
            || str_starts_with($openApiPath, '/auth/reset-password')) {
            return false;
        }
        if ($openApiPath === '/' || $openApiPath === '') {
            return false;
        }
        if (str_starts_with($openApiPath, '/docs')) {
            return false;
        }
        if (str_starts_with($openApiPath, '/broadcasting/config')) {
            return false;
        }

        return true;
    }

    /** @return list<array<string, mixed>> */
    private static function pathParameters(string $openApiPath): array
    {
        if (! preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', $openApiPath, $m)) {
            return [];
        }
        $out = [];
        foreach ($m[1] as $name) {
            $schema = ['type' => 'string'];
            if ($name === 'id') {
                $schema['format'] = 'uuid';
            }
            $out[] = [
                'name' => $name,
                'in' => 'path',
                'required' => true,
                'schema' => $schema,
            ];
        }

        return $out;
    }

    private static function inferTag(string $openApiPath): string
    {
        $trim = trim($openApiPath, '/');
        if ($trim === '') {
            return 'core';
        }
        $first = explode('/', $trim, 2)[0];

        return $first !== '' ? $first : 'core';
    }

    private static function slimPatternToOpenApiPath(string $pattern): ?string
    {
        if (preg_match('/\{routes:/', $pattern)) {
            return null;
        }
        if ($pattern === '') {
            return '/';
        }
        if ($pattern !== '/' && ! str_starts_with($pattern, '/')) {
            $pattern = '/' . $pattern;
        }
        if (str_starts_with($pattern, '/docs')) {
            return null;
        }

        $openApi = preg_replace_callback('/\{((?>[^{}]+|(?R))*)\}/', function($matches) {
            $parts = explode(':', $matches[1], 2);
            return '{' . $parts[0] . '}';
        }, $pattern);

        if (! is_string($openApi)) {
            return '/';
        }

        return $openApi;
    }
}
