<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

return Application::configure(basePath: dirname(__DIR__))
    // No `api:` route file. The member-facing surface is the session-authenticated
    // web portal; a second token-authenticated surface meant every feature was built
    // twice and each new controller re-implemented its own authorization by hand.
    // The Sanctum token guard stays available for a future native shell, but nothing
    // is routed to it until something actually needs it.
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->trustProxies(at: '*');
        $middleware->redirectGuestsTo('/login');
        $middleware->redirectUsersTo('/dashboard');
        // Order matters: SetCurrentAcademy binds `current_academy`, which
        // ResolveBranchContext needs to know which branches exist, and
        // RequireBranchSelection needs a resolved context to judge.
        $middleware->web(append: [
            // SetLocale has existed since early on and was registered nowhere,
            // so `app()->getLocale()` returned the config default on every
            // request and the bilingual half of an Arabic-first product was
            // dead code. It must run before anything renders a `lang` or `dir`
            // attribute.
            \App\Http\Middleware\SetLocale::class,
            \App\Http\Middleware\SetCurrentAcademy::class,
            \App\Http\Middleware\ResolveBranchContext::class,
            \App\Http\Middleware\RequireBranchSelection::class,
        ]);
        $middleware->alias([
            // Sanctum's ability guards are not aliased by default. The native
            // shell's token carries exactly `portal:session`, and without these
            // the ability would be minted and never checked.
            'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
            'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
            'permission' => \App\Http\Middleware\CheckPermission::class,
            // Self-registration is the one route group where a stranger writes
            // into people and participants. It stays unreachable until an
            // academy turns it on, so a deploy never opens it.
            'self_registration' => \App\Http\Middleware\EnsureSelfRegistrationEnabled::class,
            'super_admin' => \App\Http\Middleware\EnsureSuperAdmin::class,
            'branch' => \App\Http\Middleware\RequireBranchSelection::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        $exceptions->shouldRenderJsonWhen(
            fn (Request $request) => $request->is('api/*') || $request->expectsJson(),
        );

        $exceptions->respond(function (\Symfony\Component\HttpFoundation\Response $response, \Throwable $e, Request $request) {
            $statusCode = $response->getStatusCode();

            if ($request->is('api/*') || $request->expectsJson()) {
                $errorId = Str::uuid()->toString();

                Log::error('[' . $errorId . '] API Error', [
                    'error_id' => $errorId,
                    'exception' => get_class($e),
                    'message' => $e->getMessage(),
                    'file' => $e->getFile(),
                    'line' => $e->getLine(),
                    'url' => $request->fullUrl(),
                    'method' => $request->method(),
                    'input' => $request->except(['password', 'password_confirmation', '_token']),
                    'user_id' => $request->user()?->id,
                ]);

                $code = $statusCode >= 400 ? $statusCode : 500;

                $body = [
                    'error' => true,
                    'error_id' => $errorId,
                    'message' => $code >= 500 && !config('app.debug')
                        ? 'حدث خطأ في النظام، يرجى المحاولة لاحقاً'
                        : $e->getMessage(),
                    'timestamp' => now()->toIso8601String(),
                ];

                if (config('app.debug')) {
                    $body['exception'] = get_class($e);
                    $body['file'] = $e->getFile();
                    $body['line'] = $e->getLine();
                    $body['trace'] = collect($e->getTrace())->take(20)->map(fn ($frame) => [
                        'file' => $frame['file'] ?? null,
                        'line' => $frame['line'] ?? null,
                        'function' => ($frame['class'] ?? '') . ($frame['type'] ?? '') . ($frame['function'] ?? ''),
                    ])->toArray();
                }

                return response()->json($body, $code);
            }

            if ($statusCode === 500 || (!$e instanceof HttpExceptionInterface && $statusCode >= 500)) {
                $errorId = Str::uuid()->toString();

                // Everything below is a diagnostic payload for a developer, and it is
                // rendered into the browser by errors/500.blade.php. It must never leave
                // the server outside local debugging: the session alone carries
                // `password_hash_web` — the signed-in user's bcrypt hash — and Laravel
                // only strips `_token`/`_previous`/`_flash`, not the auth keys. The
                // request input, headers and last queries are the same class of leak.
                // The full detail still reaches storage/logs via Log::error below, which
                // is where an operator should be reading it from.
                $debug = (bool) config('app.debug');

                $queries = [];
                if ($debug) {
                    try {
                        $queryLog = DB::getQueryLog();
                        $queries = collect($queryLog)->takeRight(10)->map(fn ($q) => [
                            'query' => $q['query'] ?? '',
                            'time' => $q['time'] ?? null,
                        ])->toArray();
                    } catch (\Throwable $ignored) {
                    }
                }

                $trace = [];
                if ($debug) {
                    $trace = collect($e->getTrace())->take(30)->map(fn ($frame) => [
                        'file' => $frame['file'] ?? '(internal)',
                        'line' => $frame['line'] ?? null,
                        'class' => $frame['class'] ?? '',
                        'type' => $frame['type'] ?? '',
                        'function' => $frame['function'] ?? '',
                    ])->toArray();
                }

                $inputData = '';
                if ($debug) {
                    try {
                        $filtered = $request->except(['password', 'password_confirmation', '_token']);
                        $inputData = !empty($filtered) ? json_encode($filtered, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : '';
                    } catch (\Throwable $ignored) {
                    }
                }

                $headers = '';
                if ($debug) {
                    try {
                        $headerBag = $request->headers->all();
                        unset($headerBag['cookie'], $headerBag['authorization']);
                        $headers = json_encode($headerBag, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
                    } catch (\Throwable $ignored) {
                    }
                }

                $sessionData = '';
                if ($debug) {
                    try {
                        if ($request->hasSession()) {
                            $sess = $request->session()->all();
                            unset($sess['_token'], $sess['_previous'], $sess['_flash']);
                            // Auth keys are `password_hash_<guard>` and `login_<guard>_<sha1>`;
                            // strip by prefix so a new guard cannot reintroduce the leak.
                            foreach (array_keys($sess) as $key) {
                                if (str_starts_with($key, 'password_hash_') || str_starts_with($key, 'login_')) {
                                    unset($sess[$key]);
                                }
                            }
                            $sessionData = !empty($sess) ? json_encode($sess, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : '';
                        }
                    } catch (\Throwable $ignored) {
                    }
                }

                Log::error('[' . $errorId . '] Unhandled Exception', [
                    'error_id' => $errorId,
                    'exception' => get_class($e),
                    'message' => $e->getMessage(),
                    'file' => $e->getFile(),
                    'line' => $e->getLine(),
                    'url' => $request->fullUrl(),
                    'method' => $request->method(),
                    'route' => $request->route()?->getName(),
                    'input' => $request->except(['password', 'password_confirmation', '_token']),
                    'user_id' => $request->user()?->id,
                    'user_email' => $request->user()?->email,
                    'ip' => $request->ip(),
                    'user_agent' => $request->userAgent(),
                    'trace' => $e->getTraceAsString(),
                ]);

                // The exception message, file and line are diagnostics too — a query
                // exception message embeds the SQL and its bindings. Outside debug the
                // page shows only the error id, which is what a user should quote to
                // support and what ties their report to the log line above.
                return response()->view('errors.500', [
                    'errorId' => $errorId,
                    'debug' => $debug,
                    'exceptionClass' => $debug ? get_class($e) : null,
                    'message' => $debug ? $e->getMessage() : null,
                    'file' => $debug ? $e->getFile() : null,
                    'line' => $debug ? $e->getLine() : null,
                    'url' => $debug ? $request->fullUrl() : null,
                    'method' => $debug ? $request->method() : null,
                    'routeName' => $debug ? ($request->route()?->getName() ?? $request->route()?->uri()) : null,
                    'userName' => $debug ? ($request->user()?->name_ar ?? $request->user()?->name) : null,
                    'userId' => $debug ? $request->user()?->id : null,
                    'ip' => $debug ? $request->ip() : null,
                    'trace' => $trace,
                    'inputData' => $inputData,
                    'headers' => $headers,
                    'sessionData' => $sessionData,
                    'queries' => $queries,
                ], 500);
            }

            return $response;
        });
    })->create();
