<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Jobs\ProcessWebhookJob;
use App\Models\SystemSetting;
use App\Models\Tenant;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Schema;

/**
 * Receive endpoint для входящих webhook'ов от crm.bp-gr.ru (narrative §5.5).
 *
 * URL: POST /api/webhook/{token}
 * Token = `tenants.webhook_token` (UUID per tenant; ротация через
 * `webhook_token_rotated_at` — старый токен живёт 24ч после ротации).
 *
 * Шаги:
 *   1. Резолв tenant по token (404 если не найден).
 *   2. Per-token rate-limit (system_settings.webhook_rate_limit_rps × 60 ≈ per-minute).
 *      Превышение → 429 + Retry-After.
 *   3. Валидация payload (vid/project/phone/time).
 *   4. HMAC-валидация (опциональная) — если header `X-Webhook-Signature: sha256=<hex>`
 *      пришёл, проверяем `hash_hmac('sha256', raw_body, webhook_token)`.
 *      Невалидная подпись → 401. Отсутствие header — пропускаем (backward-compat
 *      для существующих интеграций; на prod через `system_settings.webhook_hmac_required`
 *      сделаем обязательной).
 *   5. INSERT в webhook_log (RLS-обёрнутый), dispatch ProcessWebhookJob → 202.
 */
class WebhookReceiveController extends Controller
{
    /** POST /api/webhook/{token} */
    public function receive(Request $request, string $token): JsonResponse
    {
        $tenant = Tenant::query()
            ->where('webhook_token', $token)
            ->first();

        if ($tenant === null) {
            return response()->json([
                'message' => 'Webhook token не найден или ротирован.',
            ], 404);
        }

        // Per-token rate-limit. Лимит из system_settings.webhook_rate_limit_rps
        // (RPS), приводим к per-minute через ×60. Decay 60 сек.
        $rpsLimit = $this->getRateLimitRps();
        $perMinuteLimit = $rpsLimit * 60;
        $rateKey = "webhook:{$tenant->id}";

        if (RateLimiter::tooManyAttempts($rateKey, $perMinuteLimit)) {
            $retryAfter = RateLimiter::availableIn($rateKey);

            return response()->json([
                'message' => "Превышен лимит ({$rpsLimit} RPS). Повтор через {$retryAfter} сек.",
                'retry_after' => $retryAfter,
            ], 429)->header('Retry-After', (string) $retryAfter);
        }

        RateLimiter::hit($rateKey, 60);

        // HMAC-валидация. Опциональная по умолчанию (backward-compat); при
        // `system_settings.webhook_hmac_required = true` — обязательная,
        // запросы без X-Webhook-Signature → 401.
        $signature = $request->header('X-Webhook-Signature');
        $hmacRequired = $this->isHmacRequired();

        if ($signature === null && $hmacRequired) {
            return response()->json([
                'message' => 'X-Webhook-Signature header требуется (HMAC обязателен в этой инсталляции).',
            ], 401);
        }
        if ($signature !== null) {
            $rawBody = $request->getContent();
            $expected = 'sha256='.hash_hmac('sha256', $rawBody, $token);
            if (! hash_equals($expected, $signature)) {
                return response()->json(['message' => 'Невалидная HMAC-подпись.'], 401);
            }
        }

        // Валидация payload (после tenant lookup чтобы посчитать rate-limit
        // даже на bad payload — иначе rate-limit можно обойти 422-ответами).
        $validated = $request->validate([
            'vid' => 'required|integer|min:1',
            'project' => 'required|string|max:255',
            'phone' => 'required|string|max:50',
            'time' => 'required|integer|min:1',
            'tag' => 'nullable|string|max:100',
            'phones' => 'nullable|array',
        ]);

        $logId = $this->insertWebhookLogStub($tenant->id, $validated);

        ProcessWebhookJob::dispatch($tenant->id, $validated, $logId);

        return response()->json([
            'status' => 'accepted',
            'tenant_id' => $tenant->id,
            'webhook_log_id' => $logId,
        ], 202);
    }

    private function getRateLimitRps(): int
    {
        $setting = SystemSetting::find('webhook_rate_limit_rps');
        if ($setting === null) {
            return 100; // sensible default из seed v8.7
        }

        return max(1, (int) $setting->value);
    }

    /**
     * HMAC-обязательность. Если ключ отсутствует в БД — default false
     * (backward-compat для существующих интеграций).
     */
    private function isHmacRequired(): bool
    {
        $setting = SystemSetting::find('webhook_hmac_required');
        if ($setting === null) {
            return false;
        }

        return in_array($setting->value, ['true', '1'], true);
    }

    /**
     * Минимальный INSERT-stub в webhook_log (если таблица существует).
     * На MVP webhook_log необязателен — возвращаем null если таблицы нет.
     *
     * @param  array<string,mixed>  $payload
     */
    private function insertWebhookLogStub(int $tenantId, array $payload): ?int
    {
        if (! Schema::hasTable('webhook_log')) {
            return null;
        }

        // RLS требует SET LOCAL — оборачиваем в транзакцию.
        return (int) DB::transaction(function () use ($tenantId, $payload) {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);

            return DB::table('webhook_log')->insertGetId([
                'tenant_id' => $tenantId,
                'received_at' => now(),
                'raw_payload' => json_encode($payload, JSON_UNESCAPED_UNICODE),
            ]);
        });
    }
}
