<?php

declare(strict_types=1);

namespace App\Http\Middleware;

use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;

/**
 * Устанавливает app.current_tenant_id в текущей транзакции PG для RLS-фильтрации.
 *
 * Стратегия: оборачивает HTTP-запрос в транзакцию (DB::beginTransaction),
 * выполняет `SET LOCAL app.current_tenant_id = X`, после next() — commit/rollback.
 * SET LOCAL действует только в пределах транзакции — это PgBouncer-safe (Прил. И Г.1).
 *
 * Резолюция tenant_id (приоритет):
 *   1. authenticated user → `auth()->user()->tenant_id`
 *   2. subdomain (production) → tenants.subdomain
 *   3. header `X-Tenant-Id` → integer (только dev/testing)
 * Если ни одна стратегия не сработала — 403 Forbidden.
 */
class SetTenantContext
{
    public function handle(Request $request, Closure $next): Response
    {
        $tenantId = $this->resolveTenantId($request);

        if ($tenantId === null) {
            abort(403, 'No tenant context (no auth, subdomain, or X-Tenant-Id header).');
        }

        DB::beginTransaction();

        try {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
            $response = $next($request);
            DB::commit();

            return $response;
        } catch (\Throwable $e) {
            DB::rollBack();

            throw $e;
        }
    }

    private function resolveTenantId(Request $request): ?int
    {
        $user = $request->user();
        // Eloquent-атрибуты через __get не видны property_exists() — нужен isset()
        // (вызывает __isset, который проверяет наличие атрибута через getAttribute).
        if ($user !== null && isset($user->tenant_id) && $user->tenant_id !== null) {
            return (int) $user->tenant_id;
        }

        $host = $request->getHost();
        $parts = explode('.', $host);
        if (count($parts) >= 3) {
            $tenant = Tenant::query()->where('subdomain', $parts[0])->first();
            if ($tenant !== null) {
                return (int) $tenant->id;
            }
        }

        // Audit-fix A3: X-Tenant-Id принимается ТОЛЬКО на dev/testing. На prod
        // заголовок игнорируется — иначе на любом роуте с `tenant`, но без
        // auth-middleware возможен спуфинг тенанта произвольным значением.
        if (app()->environment('local', 'testing') && $request->hasHeader('X-Tenant-Id')) {
            $headerValue = $request->header('X-Tenant-Id');
            if (is_string($headerValue) && ctype_digit($headerValue)) {
                return (int) $headerValue;
            }
        }

        return null;
    }
}
