<?php

namespace App\Modules\Integrations\Platform\Services;

use App\Modules\Admin\Tenancy\Services\TenantContext;
use App\Modules\Integrations\Platform\Jobs\DeliverWebhookJob;
use App\Modules\Integrations\Platform\Models\IntegrationEvent;
use App\Modules\Integrations\Platform\Models\IntegrationWebhook;
use App\Modules\Integrations\Platform\Models\IntegrationWebhookDelivery;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

final class WebhookDeliveryExecutor
{
    public function __construct(
        private readonly TenantContext $tenantContext,
        private readonly WebhookSigner $signer,
        private readonly WebhookDeliveryOutcomeHandler $outcomes,
    ) {}

    public function execute(int $deliveryId, DeliverWebhookJob $job): void
    {
        $delivery = IntegrationWebhookDelivery::query()->whereKey($deliveryId)->first();
        if (! $delivery instanceof IntegrationWebhookDelivery) {
            return;
        }
        if ($delivery->delivered_at !== null || $delivery->status === 'dead_letter') {
            return;
        }

        $this->tenantContext->setTenantId((int) $delivery->tenant_id);

        $webhook = IntegrationWebhook::query()->whereKey((int) $delivery->webhook_id)->first();
        $event = IntegrationEvent::query()->whereKey((string) $delivery->event_id)->first();
        if (! $webhook instanceof IntegrationWebhook || ! $event instanceof IntegrationEvent || ! $webhook->is_active) {
            $delivery->forceFill(['status' => 'cancelled'])->save();

            return;
        }

        $this->markAttemptStart($delivery);

        $payload = [
            'id' => (string) $event->id,
            'name' => (string) $event->name,
            'version' => (int) $event->version,
            'occurred_at' => $event->occurred_at?->toISOString(),
            'payload' => $event->payload,
        ];
        $sig = $this->signer->sign((string) $webhook->secret_enc, (string) $event->id, $payload);
        $rid = (string) Str::uuid();
        $t0 = hrtime(true);

        try {
            $resp = Http::timeout(10)
                ->withHeaders([
                    'Content-Type' => 'application/json',
                    'User-Agent' => 'SaaS-ERP Webhooks',
                    'X-Webhook-Id' => (string) $event->id,
                    'X-Webhook-Timestamp' => (string) $sig['timestamp'],
                    'X-Webhook-Signature' => $sig['signature'],
                    'X-Request-Id' => $rid,
                ])
                ->post((string) $webhook->url, $payload);

            $this->outcomes->apply($delivery, $webhook, $job, $resp->successful(), $resp->status(), (string) $resp->body(), null, $rid, $t0);
        } catch (\Throwable $e) {
            $this->outcomes->apply($delivery, $webhook, $job, false, null, '', $e->getMessage(), $rid, $t0);
        }
    }

    private function markAttemptStart(IntegrationWebhookDelivery $delivery): void
    {
        $now = now();
        $first = $delivery->first_attempted_at;
        $delivery->forceFill([
            'attempt_count' => (int) $delivery->attempt_count + 1,
            'attempt' => (int) $delivery->attempt + 1,
            'first_attempted_at' => $first ?? $now,
            'last_attempted_at' => $now,
            'status' => 'sending',
        ])->save();
    }
}
