<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\OutboundWebhookSubscription;
use App\Services\Audit\OperationsLogger;
use App\Support\WebhookUrlGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;

/**
 * Настройки исходящего webhook'а тенанта (audit D4/D5/J5).
 * Endpoints под auth:sanctum + tenant.
 *
 * Одна подписка-ряд на тенанта. Секрет генерируется при создании и
 * показывается ОДИН раз (в БД — bcrypt secret_hash + secret_prefix).
 *
 * test(): MVP делает unsigned connectivity-проверку (реальный POST на
 * target_url, отчёт по HTTP-статусу). HMAC-подписанная доставка событий —
 * отдельный пост-MVP эпик (outbound-pipeline пока не построен).
 */
class WebhookSettingsController extends Controller
{
    private const SECRET_PREFIX = 'whsec_';

    /** @var list<string> События по умолчанию для новой подписки. */
    private const DEFAULT_EVENTS = ['deal.created', 'deal.status_changed'];

    public function show(Request $request): JsonResponse
    {
        $sub = $this->currentSubscription($request);

        if ($sub === null) {
            return response()->json(['data' => null]);
        }

        return response()->json(['data' => [
            'target_url' => $sub->target_url,
            'secret_prefix' => $sub->secret_prefix,
            'events' => $sub->events,
            'is_active' => $sub->is_active,
        ]]);
    }

    public function update(Request $request, OperationsLogger $ops): JsonResponse
    {
        $validated = $request->validate([
            'target_url' => ['required', 'string', 'url', 'max:2048', 'starts_with:https://'],
        ]);

        // SSRF-гард на сохранении: не даём записать URL во внутреннюю/служебную
        // сеть — тогда любой будущий потребитель (test() + будущая outbound-доставка
        // событий) читает из БД только безопасные адреса. NB: будущая доставка
        // обязана ВДОБАВОК звать WebhookUrlGuard перед отправкой (защита от
        // DNS-rebinding: хост сохранён публичным, позже переразрешается в приватный).
        $blockReason = WebhookUrlGuard::blockReason($validated['target_url']);
        if ($blockReason !== null) {
            throw ValidationException::withMessages(['target_url' => [$blockReason]]);
        }

        $tenantId = (int) $request->user()->tenant_id;
        $sub = $this->currentSubscription($request);
        $plainSecret = null;

        // Capture before-state (null on first-time creation)
        $payloadBefore = $sub !== null
            ? ['target_url' => $sub->target_url, 'is_active' => (bool) $sub->is_active]
            : null;

        if ($sub === null) {
            $plainSecret = self::SECRET_PREFIX.Str::random(40);
            $sub = OutboundWebhookSubscription::query()->create([
                'tenant_id' => $tenantId,
                'user_id' => (int) $request->user()->id,
                'name' => 'Webhook',
                'target_url' => $validated['target_url'],
                'secret_hash' => Hash::make($plainSecret),
                'secret_prefix' => substr($plainSecret, 0, 10),
                'events' => self::DEFAULT_EVENTS,
                'is_active' => true,
            ]);
        } else {
            $sub->update(['target_url' => $validated['target_url']]);
        }

        // Audit: log target_url change (no secret_hash or plaintext secret)
        $ops->record(
            tenantId: $tenantId,
            userId: (int) $request->user()->id,
            entityType: 'webhook_settings',
            entityId: $sub->id,
            event: 'webhook_settings.updated',
            payloadBefore: $payloadBefore,
            payloadAfter: ['target_url' => $sub->target_url, 'is_active' => (bool) $sub->is_active],
            ip: $request->ip(),
            userAgent: $request->userAgent(),
        );

        $payload = [
            'target_url' => $sub->target_url,
            'secret_prefix' => $sub->secret_prefix,
            'events' => $sub->events,
            'is_active' => $sub->is_active,
        ];
        if ($plainSecret !== null) {
            $payload['secret'] = $plainSecret;
        }

        return response()->json(['data' => $payload]);
    }

    public function test(Request $request): JsonResponse
    {
        $sub = $this->currentSubscription($request);

        if ($sub === null) {
            return response()->json([
                'message' => 'Сначала сохраните URL webhook.',
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        // SSRF-гард + DNS-rebind пиннинг: ОДИН резолв target_url даёт причину
        // блокировки И безопасный IP. Блокируем адреса во внутренней/зарезервированной
        // сети (cloud-metadata 169.254.169.254, loopback, RFC1918), которые
        // https://-валидация на сохранении не ловит.
        $delivery = WebhookUrlGuard::safeDeliveryIp($sub->target_url);
        if ($delivery['blockReason'] !== null) {
            return response()->json([
                'ok' => false,
                'status' => null,
                'message' => $delivery['blockReason'],
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        $testPayload = [
            'event' => 'webhook.test',
            'sent_at' => now()->toIso8601String(),
            'message' => 'Тестовая доставка webhook от Лидерра.',
        ];

        // DNS-rebind пиннинг: подключаемся к УЖЕ проверенному IP, не давая
        // HTTP-клиенту резолвить хост повторно (TOCTOU). Host/SNI — исходный хост.
        $httpOptions = [];
        if ($delivery['ip'] !== null) {
            $host = trim((string) parse_url($sub->target_url, PHP_URL_HOST), '[]');
            $port = parse_url($sub->target_url, PHP_URL_PORT) ?? 443;
            $httpOptions['curl'] = [CURLOPT_RESOLVE => ["{$host}:{$port}:{$delivery['ip']}"]];
        }

        // Unsigned connectivity-проверка (HMAC-подписанная доставка — отдельный эпик).
        try {
            $response = Http::withOptions($httpOptions)
                ->timeout(10)
                ->withHeaders(['X-Webhook-Event' => 'webhook.test'])
                ->post($sub->target_url, $testPayload);

            return response()->json([
                'ok' => $response->successful(),
                'status' => $response->status(),
                'message' => $response->successful()
                    ? "Тестовый запрос доставлен (HTTP {$response->status()})."
                    : "Endpoint ответил HTTP {$response->status()}.",
            ]);
        } catch (\Throwable $e) {
            return response()->json([
                'ok' => false,
                'status' => null,
                'message' => 'Не удалось доставить тестовый запрос: '.$e->getMessage(),
            ]);
        }
    }

    private function currentSubscription(Request $request): ?OutboundWebhookSubscription
    {
        $tenantId = (int) $request->user()->tenant_id;

        // Defense-in-depth: явный where даже при RLS — в тестах PG superuser BYPASSRLS.
        return OutboundWebhookSubscription::query()
            ->where('tenant_id', $tenantId)
            ->orderByDesc('id')
            ->first();
    }
}
