<?php

/*
|--------------------------------------------------------------------------
| 【应用入口】路由与中间件
|--------------------------------------------------------------------------
| api 分组 prepend了 NegotiateLotteryLocale：
| 保证任意 API 在未进入控制器前已确定 lottery_locale / app locale，便于统一翻译 msg。
|--------------------------------------------------------------------------
*/

use App\Lottery\ErrorCode;
use App\Support\ApiResponse;
use App\Support\ApiMessage;
use App\Support\ApiValidationErrors;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Support\LotteryLocale;
use Illuminate\Foundation\Application;
use App\Http\Middleware\EnsureAdminApi;
use App\Http\Middleware\EnsurePlayerApi;
use App\Http\Middleware\RecordAdminApiAudit;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Auth\AuthenticationException;
use App\Http\Middleware\EnsureAdminApiResourcePermission;
use Illuminate\Validation\ValidationException;
use App\Http\Middleware\NegotiateLotteryLocale;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        // 自动加前缀 `api` + middleware `api`，见 routes/api.php
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        channels: __DIR__.'/../routes/channels.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // 多语言：必须在其他 api 中间件之前执行，以便鉴权失败时也能按语言返回 msg
        $middleware->api(prepend: [
            NegotiateLotteryLocale::class,
        ]);
        $middleware->redirectGuestsTo(static function (Request $request): ?string {
            if ($request->is('api/*')) {
                return null;
            }

            return '/login';
        });
        $middleware->convertEmptyStringsToNull([
            static fn (Request $request): bool => $request->is('api/v1/admin/settings')
                || $request->is('api/v1/admin/settings/*'),
        ]);
        $middleware->alias([
            // 玩家端需登录路由使用；解析 Bearer → Player
            'lottery.player' => EnsurePlayerApi::class,
            // 后台 API 预留：Sanctum / RBAC
            'lottery.admin' => EnsureAdminApi::class,
            'admin.api-resource' => EnsureAdminApiResourcePermission::class,
            'admin.audit' => RecordAdminApiAudit::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        /**
         * 统一 JSON：与 {@see ApiResponse} 一致，供三端消费；多语言与 {@see LotteryLocale} 对齐。
         * 覆盖：校验失败、模型/路由 404、限流、未处理异常（生产不泄露堆栈文案）。
         */
        $locale = static function (Request $request): string {
            return (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
        };

        $exceptions->render(function (AuthenticationException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            return ApiResponse::error(
                trans('admin.unauthenticated', [], $locale($request)),
                ErrorCode::AdminUnauthenticated->value,
                null,
                401,
            );
        });

        $exceptions->render(function (ValidationException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            $loc = $locale($request);
            $errors = ApiValidationErrors::normalize($e->errors(), $loc);
            $msg = ApiValidationErrors::summary($errors)
                ?? trans('api.validation_failed', [], $loc);

            return ApiResponse::error(
                $msg,
                ErrorCode::ValidationFailed->value,
                ['errors' => $errors],
                422,
            );
        });

        $exceptions->render(function (\InvalidArgumentException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            $reason = trim($e->getMessage());
            if ($reason === '' || str_contains($reason, ' ')) {
                return null;
            }

            $loc = $locale($request);
            $msg = ApiMessage::reason($request, $reason);
            if ($msg === $reason) {
                $business = trans('validation.business.'.$reason, [], $loc);
                if ($business === 'validation.business.'.$reason) {
                    return null;
                }
                $msg = $business;
            }

            return ApiResponse::error(
                $msg,
                ErrorCode::ValidationFailed->value,
                null,
                422,
            );
        });

        $exceptions->render(function (ModelNotFoundException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            return ApiResponse::error(
                trans('api.not_found', [], $locale($request)),
                ErrorCode::NotFound->value,
                null,
                404,
            );
        });

        $exceptions->render(function (NotFoundHttpException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            return ApiResponse::error(
                trans('api.not_found', [], $locale($request)),
                ErrorCode::NotFound->value,
                null,
                404,
            );
        });

        $exceptions->render(function (TooManyRequestsHttpException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }

            return ApiResponse::error(
                trans('api.too_many_requests', [], $locale($request)),
                ErrorCode::TooManyRequests->value,
                null,
                429,
            );
        });

        $exceptions->render(function (HttpException $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }
            if ($e instanceof NotFoundHttpException || $e instanceof TooManyRequestsHttpException) {
                return null;
            }

            $status = $e->getStatusCode();
            $msg = ApiMessage::httpExceptionMessage($request, $status, (string) $e->getMessage());
            $code = $status >= 500 ? ErrorCode::InternalError->value : ErrorCode::ClientHttpError->value;

            return ApiResponse::error($msg, $code, null, $status);
        });

        $exceptions->render(function (Throwable $e, Request $request) use ($locale) {
            if (! $request->is('api/*')) {
                return null;
            }
            if ($e instanceof ValidationException
                || $e instanceof ModelNotFoundException
                || $e instanceof NotFoundHttpException
                || $e instanceof TooManyRequestsHttpException
                || $e instanceof HttpException
            ) {
                return null;
            }

            $showDetails = (bool) config('app.debug')
                || (bool) env('API_ERROR_DETAILS', false)
                || app()->environment(['local', 'testing']);
            $errorId = (string) Str::uuid();
            report($e);

            $msg = $showDetails ? $e->getMessage() : trans('api.server_error', [], $locale($request));
            $details = [
                'error_id' => $errorId,
            ];

            if ($showDetails) {
                $details['exception'] = $e::class;
                $details['message'] = $e->getMessage();
                $details['file'] = $e->getFile();
                $details['line'] = $e->getLine();
                $details['trace'] = collect($e->getTrace())
                    ->take(10)
                    ->map(static function (array $frame): array {
                        return [
                            'file' => $frame['file'] ?? null,
                            'line' => $frame['line'] ?? null,
                            'class' => $frame['class'] ?? null,
                            'function' => $frame['function'] ?? null,
                        ];
                    })
                    ->values()
                    ->all();
            }

            return ApiResponse::error(
                $msg !== '' ? $msg : trans('api.server_error', [], $locale($request)),
                ErrorCode::InternalError->value,
                $details,
                500,
            );
        });
    })
    ->withSchedule(function (Schedule $schedule): void {
        /** 开奖时刻后尽快跑 RNG/冷静期，避免大厅在 0:00 卡住最多 1 分钟 */
        $schedule->command('lottery:draw-tick')
            ->everyTenSeconds()
            ->withoutOverlapping()
            ->onOneServer();
        $schedule->command('lottery:wallet-transfer-reconcile --lookback-hours=24 --stale-minutes=15 --limit=1000')
            ->everyTenMinutes()
            ->withoutOverlapping()
            ->onOneServer();
        $schedule->command('lottery:ticket-pending-confirm-reconcile --stale-minutes=5 --limit=500')
            ->everyMinute()
            ->withoutOverlapping()
            ->onOneServer();
        $schedule->command('settlement:mark-overdue-bills --days=7')
            ->dailyAt('02:00')
            ->withoutOverlapping()
            ->onOneServer();
        /** @see docs/01-界面文档.md §2.1 `draw.countdown` */
        if (config('lottery.realtime_hall_countdown', true)) {
            $schedule->command('lottery:hall-countdown')
                ->everySecond()
                ->withoutOverlapping()
                ->onOneServer();
        }
    })
    ->create();
