<?php

use App\Http\Middleware\ApiKeyAuth;
use App\Http\Middleware\EnsureSaasAdmin;
use App\Http\Middleware\ImpersonationContext;
use App\Http\Middleware\SetTenantContext;
use App\Http\Middleware\UseAdminConnection;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // /api/auth/* размещены в web.php (см. routes/web.php) и используют
        // session-based Sanctum auth. Token-based mode (через api.php) пока
        // не нужен — добавим если понадобится для интеграций.

        $middleware->alias([
            'tenant' => SetTenantContext::class,
            'saas-admin' => EnsureSaasAdmin::class,
            'admin-db' => UseAdminConnection::class,
            'apikey' => ApiKeyAuth::class,
        ]);

        $middleware->web(append: [
            ImpersonationContext::class,
        ]);

        // FN-LOGIN-ROUTE (приёмка 22.06.2026): по умолчанию Laravel при неаутент.
        // не-JSON запросе зовёт route('login') для guest-редиректа. У SPA нет
        // backend-роута с именем 'login' (логин — клиентский Vue-роут), поэтому
        // дефолт бросал «Route [login] not defined» (500) на прямых заходах
        // браузера/бота к /api/*. Возвращаем строковый путь SPA-логина — никакого
        // route() вызова. Для /api/* финальный ответ — 401 JSON (render ниже во
        // withExceptions), сюда не доходит.
        $middleware->redirectGuestsTo(fn (): string => '/login');

        // Защитные HTTP-заголовки (CSP, X-Frame-Options, X-Content-Type-Options,
        // Referrer-Policy, HSTS, Permissions-Policy, COOP/CORP) ставит nginx —
        // единый источник: /etc/nginx/sites-available/liderra (add_header ... always).
        // App-уровневый middleware SecurityHeaders удалён 18.06.2026: он дублировал
        // те же заголовки, и на проде add_header always + PHP-заголовок давали дубль
        // в ответе. CSP в nginx — enforcing (был Report-Only в middleware).

        // Webhook receive endpoint (POST /api/webhook/{token}) не должен требовать
        // CSRF — запросы приходят от внешних CRM-систем без сессии браузера.
        // Авторизация — через webhook_token в URL + (на prod) HMAC.
        $middleware->validateCsrfTokens(except: [
            'api/webhook/*',
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        // FN-LOGIN-ROUTE (приёмка 22.06.2026): неаутентифицированный запрос к
        // auth:sanctum-роуту, который НЕ просит JSON (прямой заход браузером/ботом
        // без Accept: application/json), уводил Laravel в Authenticate::redirectTo()
        // → route('login'), которого в SPA нет (логин — клиентский Vue-роут) →
        // «Route [login] not defined» (500). Все защищённые маршруты тут — /api/*,
        // поэтому для них всегда отдаём 401 JSON вместо редиректа на несуществующий
        // именованный роут. Наблюдалось в проде 08.06 и 21.06.
        $exceptions->render(function (AuthenticationException $e, Request $request) {
            if ($request->is('api/*')) {
                return response()->json([
                    'message' => 'Требуется авторизация.',
                ], 401);
            }

            return null; // не-API: поведение по умолчанию
        });

        // Reduce verbosity of constraint-violation logging (SQLSTATE 23xxx):
        // data-validity errors do not need a full stack trace в laravel.log.
        // Incident 2026-05-29: 420k повторов B1+SMS check_violation накопили
        // 8.7 GB stack traces → disk full → 4h prod downtime.
        // Solution: log a warning summary с sqlstate, return false to stop
        // default reporting (which would write full stack trace).
        // Ref: docs/incidents/2026-05-29-disk-full-pg-recovery.md §5
        $exceptions->reportable(function (QueryException $e) {
            $sqlState = $e->errorInfo[0] ?? '';
            if (is_string($sqlState) && str_starts_with($sqlState, '23')) {
                Log::warning('db.constraint_violation', [
                    'sqlstate' => $sqlState,
                    'message' => mb_substr($e->getMessage(), 0, 200),
                ]);

                return false; // skip default reporting (no stack trace в laravel.log)
            }

            return null; // continue default reporting для non-constraint QueryExceptions
        });

        $exceptions->render(function (QueryException $e, Request $request) {
            $sqlState = $e->errorInfo[0] ?? '';
            $isConstraintViolation = is_string($sqlState) && str_starts_with($sqlState, '23');

            if (! $isConstraintViolation) {
                // Default verbose log для non-constraint QueryExceptions (table missing,
                // syntax error, etc. — these are bugs needing investigation).
                Log::error('db.query_exception', [
                    'message' => $e->getMessage(),
                    'sql' => $e->getSql(),
                    'path' => $request->path(),
                ]);
            }
            // Constraint violations уже залогированы в reportable() выше как warning,
            // дублировать не нужно.

            if ($request->expectsJson()) {
                return response()->json([
                    'message' => 'Не удалось сохранить. Проверьте данные или попробуйте ещё раз.',
                ], 422);
            }

            return null; // default render for non-JSON
        });

        // Supplier webhook always returns JSON, even when client omits Accept header.
        // Without this render, Laravel's default ValidationException handler returns
        // 302 redirect to /, which strips POST body — losing supplier leads.
        // Confirmed 2026-05-25: 76 of 234 webhook hits today got 302 instead of 422.
        $exceptions->render(function (ValidationException $e, Request $request) {
            if ($request->is('api/webhook/supplier/*')) {
                return response()->json([
                    'message' => 'Validation failed',
                    'errors' => $e->errors(),
                ], 422);
            }

            return null; // default render for other routes
        });
    })->create();
