<?php
// app/Http/Controllers/SendgridWebhookController.php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Communication;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class SendgridWebhookController extends Controller
{
    public function handle(Request $request)
    {
        // Verify signed events (recommended)
        if (filter_var(config('services.sendgrid.verify', env('SENDGRID_WEBHOOK_VERIFY', true)), FILTER_VALIDATE_BOOL)) {
            if (!$this->verifySignature($request)) {
                Log::warning('SendGrid webhook signature verification failed');
                return response()->json(['error' => 'invalid signature'], 400);
            }
        }

        $events = $request->json()->all();
        if (!is_array($events)) {
            return response()->json(['ok' => true]); // nothing to do
        }

        // Check once whether de-dupe column exists (optional feature)
        $supportsPayloadHash = Schema::hasTable('communication_events')
            && Schema::hasColumn('communication_events', 'payload_hash');

        foreach ($events as $e) {
            try {
                $eventType = $e['event'] ?? 'unknown';

                // Try to locate communication by smtp-id Message-ID (e.g. "<comm-123@domain>")
                $smtpId = isset($e['smtp-id']) ? trim((string) $e['smtp-id']) : null;
                $comm   = null;

                if ($smtpId && str_contains($smtpId, '<comm-') && ($id = $this->extractCommIdFromMessageId($smtpId))) {
                    $comm = Communication::find($id);
                }

                // Fallback: unique_args.comm_id (if X-SMTPAPI provided)
                if (!$comm && isset($e['unique_args']['comm_id'])) {
                    $comm = Communication::find((int) $e['unique_args']['comm_id']);
                }

                // Last resort: our X-Comm-ID if SendGrid echoed headers (not always present)
                if (!$comm && isset($e['headers']['X-Comm-ID'])) {
                    $comm = Communication::find((int) $e['headers']['X-Comm-ID']);
                }

                if (!$comm) {
                    // Unknown message; skip silently
                    continue;
                }

                $ts = isset($e['timestamp'])
                    ? Carbon::createFromTimestampUTC((int) $e['timestamp'])
                    : now();

                // OPTIONAL: de-duplicate identical payloads if your table has payload_hash
                $payloadHash = hash('sha256', json_encode($e, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

                if ($supportsPayloadHash) {
                    // If a unique index exists on (communication_id, payload_hash) this will be naturally idempotent.
                    $comm->events()->firstOrCreate(
                        [
                            'payload_hash'   => $payloadHash,
                            'communication_id' => $comm->id,
                        ],
                        [
                            'event'         => $eventType,
                            'occurred_at'   => $ts,
                            'payload'       => $e,
                        ]
                    );
                } else {
                    // No hash column? Just create the event (same as your original behavior)
                    $comm->events()->create([
                        'event'       => $eventType,
                        'occurred_at' => $ts,
                        'payload'     => $e,
                    ]);
                }

                // Update high-level status on Communication (extended mappings)
                $newStatus = match ($eventType) {
                    'processed'          => 'sent',
                    'deferred'           => $comm->status,     // queued/temporary; keep current
                    'delivered'          => 'delivered',
                    'open'               => 'opened',
                    'click'              => 'opened',           // clicks imply open
                    'bounce'             => 'bounced',
                    'blocked'            => 'bounced',          // SendGrid sometimes uses 'blocked' as a bounce-like outcome
                    'dropped'            => 'dropped',
                    'spamreport'         => 'complaint',
                    'unsubscribe'        => $comm->status,      // or use 'unsubscribed' if you track it
                    'group_unsubscribe'  => $comm->status,
                    'group_resubscribe'  => $comm->status,
                    default              => $comm->status,
                };

                if ($newStatus !== $comm->status) {
                    $comm->update(['status' => $newStatus]);
                }

                // Optional: mirror human-readable Events for your admin feed
                if (class_exists(\App\Models\Event::class)) {
                    $msg = match ($eventType) {
                        'processed'          => "Email queued for {$comm->to_email}",
                        'deferred'           => "Email deferred for {$comm->to_email}",
                        'delivered'          => "Email delivered to {$comm->to_email}",
                        'open'               => "Email opened by {$comm->to_email}",
                        'click'              => "Email link clicked by {$comm->to_email}",
                        'bounce'             => "Email bounced for {$comm->to_email}",
                        'blocked'            => "Email blocked for {$comm->to_email}",
                        'dropped'            => "Email dropped for {$comm->to_email}",
                        'spamreport'         => "Spam complaint from {$comm->to_email}",
                        'unsubscribe'        => "{$comm->to_email} unsubscribed",
                        'group_unsubscribe'  => "{$comm->to_email} group-unsubscribed",
                        'group_resubscribe'  => "{$comm->to_email} group-resubscribed",
                        default              => "Email event: {$eventType} ({$comm->to_email})",
                    };

                    \App\Models\Event::create([
                        'job_id'  => $comm->job_id,
                        'type'    => 'communication.'.$eventType,
                        'message' => $msg,
                        'meta'    => [
                            'communication_id' => $comm->id,
                            'raw'              => $e,
                            'payload_hash'     => $payloadHash,
                        ],
                    ]);
                }
            } catch (\Throwable $ex) {
                Log::error('SendGrid webhook processing error: '.$ex->getMessage(), [
                    'trace' => $ex->getTraceAsString(),
                ]);
                continue;
            }
        }

        return response()->json(['ok' => true]);
    }

    private function extractCommIdFromMessageId(string $smtpId): ?int
    {
        // expects "<comm-123@domain>"
        if (preg_match('/<comm-(\d+)@/i', $smtpId, $m)) {
            return (int) $m[1];
        }
        return null;
    }

    /**
     * Verifies Twilio SendGrid Event Webhook signature.
     * Docs: https://docs.sendgrid.com/for-developers/tracking-events/event#security
     */
    private function verifySignature(Request $request): bool
    {
        $publicKey = trim((string) env('SENDGRID_WEBHOOK_PUBLIC_KEY', ''));
        if ($publicKey === '') {
            return false;
        }

        $timestamp = (string) $request->header('X-Twilio-Email-Event-Webhook-Timestamp', '');
        $signature = (string) $request->header('X-Twilio-Email-Event-Webhook-Signature', '');

        if ($timestamp === '' || $signature === '') {
            return false;
        }

        $payload   = $request->getContent();
        $signedMsg = $timestamp.$payload;

        if (!function_exists('sodium_crypto_sign_verify_detached')) {
            return false; // sodium not available
        }

        $sig = base64_decode($signature, true);
        $pk  = base64_decode($publicKey, true);
        if (!$sig || !$pk) {
            return false;
        }

        return sodium_crypto_sign_verify_detached($sig, $signedMsg, $pk);
    }
}
