<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Providers;

use App\Auth\Passport\DberpPassportSigningKeyBootstrap;
use App\Http\Middleware\CompleteDberpMcpOAuthConsent;
use App\Http\Middleware\EnsureDberpMcpOAuthAuthorizationAvailable;
use App\Http\Middleware\ForceDberpMcpOAuthConsentPrompt;
use App\Http\Middleware\RejectNonS256OAuthPkce;
use App\Http\Middleware\RejectUnsafeOAuthRedirectUri;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Mcp\Server\Registrar;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\Http\Controllers\ApproveAuthorizationController;
use Laravel\Passport\Http\Controllers\AuthorizationController;
use Laravel\Passport\Http\Controllers\DenyAuthorizationController;
use Laravel\Passport\Passport;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\ResourceServer;

/**
 * AUTH-1B：Passport 最小 HTTP 面。保持 ignoreRoutes，只暴露授权码 / 同意 / 拒绝 / token。
 */
final class DberpPassportServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // 在线更新 ReplaceFiles 早于 ReplaceVendor：本 Provider 可能先于 laravel/passport 落盘。
        if (! class_exists(Passport::class)) {
            return;
        }

        // 不得打开 Passport 默认全量路由（device、JSON API、PAT UI 等）。
        Passport::ignoreRoutes();
        $this->prepareSigningKeysBeforeCryptKey();
    }

    public function boot(): void
    {
        if (! class_exists(Passport::class) || ! class_exists(Registrar::class)) {
            return;
        }

        Passport::$deviceCodeGrantEnabled = false;

        $this->hardenMcpOAuthConfig();
        Registrar::ensureMcpScope();
        Passport::defaultScopes([Registrar::OAUTH_SCOPE]);
        $this->registerAuthorizationCodeRoutes();
    }

    /**
     * CryptKey 在 ResourceServer / AuthorizationServer 解析时才构造。
     * 必须在此之前收紧 644/664，否则 Linux 上 NOTICE 会被升级成 500。
     */
    private function prepareSigningKeysBeforeCryptKey(): void
    {
        $prepare = function (): void {
            $this->app->make(DberpPassportSigningKeyBootstrap::class)->prepareForCryptKeyUse();
        };

        if (class_exists(ResourceServer::class)) {
            $this->app->beforeResolving(ResourceServer::class, $prepare);
        }

        if (class_exists(AuthorizationServer::class)) {
            $this->app->beforeResolving(AuthorizationServer::class, $prepare);
        }
    }

    /**
     * laravel/mcp 0.9.4 默认 redirect_domains 含 '*'。array_merge 无法用空数组覆盖，必须强制剥离。
     */
    private function hardenMcpOAuthConfig(): void
    {
        $domains = config('mcp.redirect_domains');
        $domains = is_array($domains) ? $domains : [];

        config([
            'mcp.redirect_domains' => array_values(array_filter(
                $domains,
                static fn (mixed $domain): bool => is_string($domain) && $domain !== '' && $domain !== '*',
            )),
        ]);

        $schemes = config('mcp.custom_schemes');
        config([
            'mcp.custom_schemes' => is_array($schemes) ? array_values($schemes) : [],
        ]);
    }

    private function registerAuthorizationCodeRoutes(): void
    {
        $prefix = trim((string) config('passport.path', 'oauth'), '/');
        $guard = (string) config('passport.guard', 'web');
        $authMiddleware = $guard !== '' ? 'auth:'.$guard : 'auth';

        Route::prefix($prefix)
            ->as('passport.')
            ->middleware(config('passport.middleware', []))
            ->group(function () use ($authMiddleware): void {
                Route::post('/token', [AccessTokenController::class, 'issueToken'])
                    ->middleware('throttle')
                    ->name('token');

                Route::get('/authorize', [AuthorizationController::class, 'authorize'])
                    ->middleware([
                        'web',
                        'throttle:60,1',
                        EnsureDberpMcpOAuthAuthorizationAvailable::class,
                        ForceDberpMcpOAuthConsentPrompt::class,
                        RejectNonS256OAuthPkce::class,
                        RejectUnsafeOAuthRedirectUri::class,
                    ])
                    ->name('authorizations.authorize');

                Route::middleware([
                    'web',
                    $authMiddleware,
                    EnsureDberpMcpOAuthAuthorizationAvailable::class,
                    CompleteDberpMcpOAuthConsent::class,
                ])
                    ->group(function (): void {
                        Route::post('/authorize', [ApproveAuthorizationController::class, 'approve'])
                            ->name('authorizations.approve');

                        Route::delete('/authorize', [DenyAuthorizationController::class, 'deny'])
                            ->name('authorizations.deny');
                    });
            });
    }
}
