<?php

namespace App\Actions\Communications;

use App\Enum\Contact\MessageChannel;
use App\Enum\Contact\MessageEventType;
use App\Enum\Contact\MessageStatus;
use App\Models\Contact\ContactSuppression;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Contact\Hub\ContactMessageEvent;
use App\Services\Communications\ProviderEventMapper;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class IngestProviderEventAction
{
    public function __construct(
        protected ProviderEventMapper $mapper,
    ) {}

    /**
     * Ingest a single SendGrid event.  Called from the webhook controller
     * once per event in the payload.  Idempotent on provider_event_id.
     */
    public function handleSendGrid(array $event): ?ContactMessageEvent
    {
        $contactMessageId = $this->resolveSendGridMessageId($event);
        if (!$contactMessageId) {
            Log::channel('stack')->info('SendGrid event unmatched', ['event' => $event]);
            return null;
        }

        $message = ContactMessage::withoutGlobalScopes()->find($contactMessageId);
        if (!$message) {
            return null;
        }

        $mapping = $this->mapper->mapSendGrid((string) ($event['event'] ?? ''));
        $occurredAt = isset($event['timestamp'])
            ? Carbon::createFromTimestamp((int) $event['timestamp'])
            : now();
        $providerEventId = (string) ($event['sg_event_id'] ?? ($event['sg_message_id'] ?? '') . ':' . $event['event']);

        return $this->ingest($message, $mapping, $event, $occurredAt, $providerEventId);
    }

    /**
     * Ingest a Twilio status callback payload.
     */
    public function handleTwilio(array $payload): ?ContactMessageEvent
    {
        $messageSid = $payload['MessageSid'] ?? null;
        $mid = $payload['mid'] ?? null;

        $message = null;
        if ($mid) {
            $message = ContactMessage::withoutGlobalScopes()->find($mid);
        }
        if (!$message && $messageSid) {
            $message = ContactMessage::withoutGlobalScopes()
                ->where('provider_message_id', $messageSid)->first();
        }

        if (!$message) {
            Log::channel('stack')->info('Twilio event unmatched', ['payload' => $payload]);
            return null;
        }

        $mapping = $this->mapper->mapTwilio((string) ($payload['MessageStatus'] ?? ''));
        $occurredAt = now();
        $providerEventId = ($messageSid ?: 'twilio') . ':' . ($payload['MessageStatus'] ?? 'unknown');

        return $this->ingest($message, $mapping, $payload, $occurredAt, $providerEventId);
    }

    /**
     * @param array{event: MessageEventType, status: ?MessageStatus, timestamp_column: ?string} $mapping
     */
    private function ingest(ContactMessage $message, array $mapping, array $payload, Carbon $occurredAt, string $providerEventId): ?ContactMessageEvent
    {
        // Idempotency — return existing if we've already ingested this provider_event_id.
        $existing = ContactMessageEvent::query()
            ->withoutGlobalScopes()
            ->where('contact_message_id', $message->id)
            ->where('provider_event_id', $providerEventId)
            ->first();

        if ($existing) {
            return $existing;
        }

        $event = ContactMessageEvent::create([
            'solicitor_id' => $message->solicitor_id,
            'contact_message_id' => $message->id,
            'event_type' => $mapping['event'],
            'provider_event_id' => $providerEventId,
            'mapped_status' => $mapping['status'],
            'payload' => $payload,
            'occurred_at' => $occurredAt,
        ]);

        if ($mapping['status']) {
            $timestamps = [];
            if ($mapping['timestamp_column']) {
                // Only stamp if not already stamped (idempotent across retries).
                if (empty($message->{$mapping['timestamp_column']})) {
                    $timestamps[$mapping['timestamp_column']] = $occurredAt;
                }
            }
            $message->transitionStatus($mapping['status'], $timestamps);
        }

        // Auto-suppress on bounces / spam reports per config.
        if ($this->shouldAutoSuppress($mapping['event'])) {
            $this->autoSuppress($message);
        }

        return $event;
    }

    private function shouldAutoSuppress(MessageEventType $event): bool
    {
        if (in_array($event, [MessageEventType::BOUNCE, MessageEventType::DROPPED])
            && config('contact.auto_suppress_on_bounce')) {
            return true;
        }
        if ($event === MessageEventType::SPAM_REPORT && config('contact.auto_suppress_on_spam_report')) {
            return true;
        }
        return false;
    }

    private function autoSuppress(ContactMessage $message): void
    {
        $value = $message->channel === MessageChannel::EMAIL
            ? $message->recipient_email
            : $message->recipient_phone;

        if (!$value) {
            return;
        }

        ContactSuppression::withoutGlobalScopes()->updateOrCreate(
            [
                'solicitor_id' => $message->solicitor_id,
                'channel' => $message->channel->value,
                'value' => $value,
            ],
            [
                'reason' => 'auto-' . $message->status->value,
                'suppressed_at' => now(),
                'source_type' => $message->messageable_type,
                'source_id' => $message->messageable_id,
            ],
        );
    }

    private function resolveSendGridMessageId(array $event): ?int
    {
        if (!empty($event['contact_message_id'])) {
            return (int) $event['contact_message_id'];
        }
        if (isset($event['unique_args']['contact_message_id'])) {
            return (int) $event['unique_args']['contact_message_id'];
        }

        // Try to resolve by sg_message_id if custom_args weren't set.
        if (!empty($event['sg_message_id'])) {
            $msg = ContactMessage::withoutGlobalScopes()
                ->where('provider_message_id', 'like', $event['sg_message_id'] . '%')
                ->first();
            return $msg?->id;
        }

        return null;
    }
}
