<?php

namespace App\Jobs\Contact;

use App\Contracts\Communications\EmailChannelProvider;
use App\Contracts\Communications\SmsChannelProvider;
use App\Enum\Contact\MessageChannel;
use App\Enum\Contact\MessageStatus;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Contact\Hub\ContactMessageEvent;
use App\Enum\Contact\MessageEventType;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Throwable;

class SendContactMessageJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;
    public int $timeout = 60;

    /** Exponential-ish backoff in seconds. */
    public array $backoff = [30, 120, 600, 3600, 14400];

    public function __construct(public int $contactMessageId) {}

    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("contact-message:{$this->contactMessageId}"))
                ->releaseAfter(30)
                ->expireAfter(600),
        ];
    }

    public function handle(): void
    {
        $message = ContactMessage::withoutGlobalScopes()->find($this->contactMessageId);
        if (!$message) {
            return;
        }

        // Short-circuit already-sent.
        if ($message->status === MessageStatus::SENT
            || $message->status?->isTerminalSuccess()
            || $message->status?->isTerminalFailure()) {
            return;
        }

        if (!empty($message->provider_message_id)) {
            // Provider already accepted on a prior attempt — do not double-send.
            $message->transitionStatus(MessageStatus::SENT);
            return;
        }

        $message->forceFill(['status' => MessageStatus::SENDING])->save();

        try {
            $result = $message->channel === MessageChannel::EMAIL
                ? app(EmailChannelProvider::class)->send($message)
                : app(SmsChannelProvider::class)->send($message);

            $message->markSent($result->providerMessageId, $result->payload);

            ContactMessageEvent::create([
                'contact_message_id' => $message->id,
                'solicitor_id' => $message->solicitor_id,
                'event_type' => MessageEventType::SENT,
                'mapped_status' => MessageStatus::SENT,
                'payload' => ['provider_message_id' => $result->providerMessageId],
                'occurred_at' => now(),
            ]);
        } catch (Throwable $e) {
            $message->markFailed($e->getMessage());

            ContactMessageEvent::create([
                'contact_message_id' => $message->id,
                'solicitor_id' => $message->solicitor_id,
                'event_type' => MessageEventType::FAILED,
                'mapped_status' => MessageStatus::FAILED,
                'payload' => ['error' => $e->getMessage()],
                'occurred_at' => now(),
            ]);

            throw $e;
        }
    }

    public function failed(?Throwable $e): void
    {
        $message = ContactMessage::withoutGlobalScopes()->find($this->contactMessageId);
        if ($message && !$message->failed_at) {
            $message->markFailed($e?->getMessage() ?? 'Job failed');
        }
    }
}
