<?php

namespace App\Http\Controllers;

use App\Models\EmailDeliveryLog;
use App\Models\JobAlertEmailLog;
use App\Services\Analytics\EngagementLogger;
use App\Services\Settings\SettingsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class BrevoWebhookController extends Controller
{
    public function __invoke(Request $request, SettingsService $settings, EngagementLogger $engagementLogger): JsonResponse
    {
        $secret = (string) $settings->get('email.brevo_webhook_secret');

        abort_if($secret === '', 503, 'Brevo webhook secret is not configured.');
        abort_unless($this->secretMatches($request, $secret), 403, 'Invalid Brevo webhook secret.');

        $events = $this->events($request->all());
        $processed = 0;

        foreach ($events as $event) {
            $eventName = $this->normalizeEvent((string) Arr::get($event, 'event', Arr::get($event, 'type', '')));
            $messageId = $this->messageId($event);

            if ($eventName === null || $messageId === null) {
                Log::info('Brevo webhook event skipped because event name or message ID was missing.', [
                    'payload' => $event,
                ]);

                continue;
            }

            $deliveryLog = EmailDeliveryLog::query()
                ->where(function ($query) use ($messageId): void {
                    $query
                        ->where('brevo_message_id', $messageId)
                        ->orWhere('provider_message_id', $messageId);
                })
                ->latest()
                ->first();

            if (! $deliveryLog) {
                Log::info('Brevo webhook event did not match a local email delivery log.', [
                    'message_id' => $messageId,
                    'event' => $eventName,
                ]);

                continue;
            }

            $occurredAt = $this->occurredAt($event);
            $deliveryLog->markEvent($eventName, $occurredAt);

            if ($deliveryLog->type === EmailDeliveryLog::TYPE_JOB_ALERT) {
                JobAlertEmailLog::query()
                    ->where(function ($query) use ($deliveryLog, $messageId): void {
                        $query
                            ->where('email_delivery_log_id', $deliveryLog->id)
                            ->orWhere('brevo_message_id', $messageId)
                            ->orWhere('provider_message_id', $messageId);
                    })
                    ->get()
                    ->each(fn (JobAlertEmailLog $log): mixed => $log->markEvent($eventName, $occurredAt));
            }

            $engagementLogger->record(
                $deliveryLog->user,
                'email_'.$eventName,
                Str::headline($eventName).' email event for '.$deliveryLog->recipient_email.'.',
                [
                    'email_delivery_log_id' => $deliveryLog->id,
                    'message_id' => $messageId,
                    'raw_event' => $event,
                ],
                $deliveryLog,
            );

            $processed++;
        }

        return response()->json([
            'processed' => $processed,
        ]);
    }

    private function secretMatches(Request $request, string $secret): bool
    {
        $provided = (string) ($request->header('X-Brevo-Webhook-Secret') ?: $request->query('secret', ''));

        return $provided !== '' && hash_equals($secret, $provided);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<int, array<string, mixed>>
     */
    private function events(array $payload): array
    {
        if (array_is_list($payload)) {
            return array_values(array_filter($payload, 'is_array'));
        }

        foreach (['events', 'items', 'data'] as $key) {
            $events = Arr::get($payload, $key);

            if (is_array($events) && array_is_list($events)) {
                return array_values(array_filter($events, 'is_array'));
            }
        }

        return [$payload];
    }

    private function normalizeEvent(string $event): ?string
    {
        $event = Str::of($event)->lower()->replace(['-', ' '], '_')->toString();

        return match ($event) {
            'delivered', 'delivery' => 'delivered',
            'opened', 'open', 'unique_opened' => 'opened',
            'clicked', 'click', 'unique_clicked' => 'clicked',
            'unsubscribed', 'unsubscribe', 'unsubscription' => 'unsubscribed',
            'soft_bounce', 'hard_bounce', 'blocked', 'invalid', 'deferred', 'error', 'spam' => 'bounced',
            'failed', 'failure' => 'failed',
            default => null,
        };
    }

    /**
     * @param  array<string, mixed>  $event
     */
    private function messageId(array $event): ?string
    {
        foreach (['messageId', 'message-id', 'message_id', 'Message-ID', 'uuid', 'id'] as $key) {
            $value = Arr::get($event, $key);

            if (is_scalar($value) && filled((string) $value)) {
                return trim((string) $value);
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $event
     */
    private function occurredAt(array $event): ?string
    {
        foreach (['date', 'timestamp', 'event_time', 'time'] as $key) {
            $value = Arr::get($event, $key);

            if (is_scalar($value) && filled((string) $value)) {
                return (string) $value;
            }
        }

        return null;
    }
}
