<?php

use App\Http\Middleware\AuthenticateModuleApi;
use App\Http\Middleware\AuthenticateOwntracks;
use App\Http\Middleware\EnsureAdmin;
use App\Http\Middleware\EnsureCustomer;
use App\Http\Middleware\EnsureDriver;
use App\Http\Middleware\EnsureDsgvoInformed;
use App\Http\Middleware\InstallerGuard;
use App\Http\Middleware\RedirectToInstaller;
use App\Http\Middleware\SecurityHeaders;
use App\Http\Middleware\SetInstallerLocale;
use App\Http\Middleware\SetUserLocale;
use App\Services\Diagnostic\DiagnosticManager;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use App\Services\Scheduler\ScheduledTaskRegistry;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Support\Facades\Log;
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
use Illuminate\Http\Middleware\TrustProxies;
use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
        then: function () {
            require base_path('routes/install.php');
        },
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // Trust proxies only when an operator explicitly opts in via
        // TRUSTED_PROXIES. Distributed installs on stock web hosting have no
        // known proxy in front, so the default trusts none: a client-supplied
        // X-Forwarded-For is ignored and the real peer IP is used (prevents
        // login-throttle bypass via header spoofing). Set TRUSTED_PROXIES=* or
        // a comma-separated CIDR list when behind a trusted CDN/load balancer.
        $trustedProxies = env('TRUSTED_PROXIES');
        TrustProxies::at(
            $trustedProxies === '*'
                ? '*'
                : array_values(array_filter(array_map('trim', explode(',', (string) $trustedProxies))))
        );
        TrustProxies::withHeaders(
            Request::HEADER_X_FORWARDED_FOR
            | Request::HEADER_X_FORWARDED_HOST
            | Request::HEADER_X_FORWARDED_PROTO
            | Request::HEADER_X_FORWARDED_PORT
        );

        // Reject Host-header injection by trusting only the APP_URL host (and
        // its subdomains). Laravel enforces this only outside local/testing and
        // skips it when APP_URL has no host, so it is safe on stock installs.
        $middleware->trustHosts();

        $middleware->redirectGuestsTo(function (Request $request) {
            if ($request->is('portal', 'portal/*')) {
                return route('portal.login');
            }
            return route('login');
        });

        $middleware->alias([
            'admin' => EnsureAdmin::class,
            'driver' => EnsureDriver::class,
            'dsgvo' => EnsureDsgvoInformed::class,
            'installer.guard' => InstallerGuard::class,
            'module.api' => AuthenticateModuleApi::class,
            'owntracks' => AuthenticateOwntracks::class,
            'portal' => EnsureCustomer::class,
        ]);

        $middleware->prependToGroup('web', RedirectToInstaller::class);

        // Apply the authenticated user's per-user locale on every web request,
        // after StartSession/auth has resolved the user. Overrides the boot-time
        // default_locale so the admin/users locale picker takes effect app-wide.
        $middleware->appendToGroup('web', SetUserLocale::class);

        // Baseline browser-hardening headers on every web response.
        $middleware->appendToGroup('web', SecurityHeaders::class);

        $middleware->group('installer', [
            TrustProxies::class,
            InstallerGuard::class,
            EncryptCookies::class,
            AddQueuedCookiesToResponse::class,
            StartSession::class,
            ShareErrorsFromSession::class,
            SetInstallerLocale::class,
            ValidateCsrfToken::class,
        ]);
    })
    ->withSchedule(function (Schedule $schedule): void {
        $registry = app(ScheduledTaskRegistry::class);

        foreach ($registry->enabledTasks() as $slug => $task) {
            $schedule->call(function () use ($registry, $slug, $task): void {
                $start = hrtime(true);
                try {
                    $task->handle();
                    $durationMs = (int) ((hrtime(true) - $start) / 1_000_000);
                    $registry->recordRun($slug, 'success', null, $durationMs);
                } catch (\Throwable $e) {
                    $durationMs = (int) ((hrtime(true) - $start) / 1_000_000);
                    $registry->recordRun($slug, 'failed', $e->getMessage(), $durationMs);
                    Log::warning("Scheduled task '{$slug}' failed: {$e->getMessage()}");

                    try {
                        app(DiagnosticManager::class)->report('scheduled_task_failed', [
                            'error' => $e->getMessage(),
                            'exception_class' => get_class($e),
                        ], [
                            'slug' => $slug,
                            'duration_ms' => $durationMs,
                            'source' => 'scheduler',
                        ]);
                    } catch (\Throwable) {
                        // Never let diagnostic reporting break the original flow
                    }
                }
            })->cron($task->schedule())->name($slug);
        }

        // Fallback: hardcoded entries in case registry is empty
        // $schedule->command('jobs:retention-delete')->daily()->at('03:00');
        // $schedule->command('schneespur:update-check')->daily()->at('04:17');
        // $schedule->command('queue:work', ['--stop-when-empty'])->everyMinute()->withoutOverlapping();
        // $schedule->call(fn () => cache()->put('cron.last_run', now()))->everyMinute();
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        $exceptions->reportable(function (\Throwable $e) {
            $reported = false;
            try {
                $manager = app(DiagnosticManager::class);
                if ($manager->hasEnabledReporters()) {
                    $manager->reportException($e);
                    $reported = true;
                }
            } catch (\Throwable) {
                // Never let diagnostic reporting break the application
            }

            return $reported ? false : null;
        });
    })->create();
