<?php

namespace App\Jobs;

use App\Models\EmailAccount;
use App\Models\Message;
use App\Models\UsageRecord;
use App\Services\Email\EmailSendService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

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

    public int $tries = 3;
    public int $timeout = 120;
    public array $backoff = [30, 120, 600];
    // Queue is set via constructor: $this->onQueue('campaigns')

    public function __construct(public Message $message)
    {
        $this->onQueue('campaigns');
    }

    public function handle(EmailSendService $emailSendService): void
    {
        // Re-read from DB so concurrent updates (undo-send cancel, manual
        // re-schedule, etc.) are respected.
        $this->message->refresh();

        // Guard: skip if already sent or cancelled since dispatch
        if ($this->message->schedule_status !== 'pending') {
            return;
        }

        $conversation = $this->message->conversation;
        if (!$conversation) {
            Log::error('SendScheduledEmailJob: no conversation found', [
                'message_id' => $this->message->id,
            ]);
            $this->message->update([
                'schedule_status' => 'cancelled',
                'delivery_status' => 'failed',
                'delivery_error' => 'Conversation not found',
            ]);
            return;
        }

        // ── SMTP account resolution ──────────────────────────────────
        // Strict priority order so we never end up authenticating as
        // account A while sending From: account B (the classic SMTP
        // 436 "MAIL FROM doesn't conform with authentication" error):
        //
        //   1. If the Message has from_email set, prefer the account
        //      whose `email` matches it — that's the address the agent
        //      composed against and the From: header carries.
        //   2. Fall back to the conversation's linked email account.
        //   3. Last resort: any connected account in the workspace
        //      (kept for legacy installs where conversations were
        //      stitched without an account_id).
        //
        // After resolution we double-check the chosen account's email
        // matches the Message's from_email. A mismatch means the
        // workspace lost the original account (deleted, suspended)
        // and the only available account would produce 436. We fail
        // the message with a clear actionable error rather than send
        // from the wrong identity.
        $emailAccount = null;
        $messageFrom  = (string) ($this->message->from_email ?? '');

        if ($messageFrom !== '') {
            $emailAccount = EmailAccount::where('workspace_id', $conversation->workspace_id)
                ->where('email', $messageFrom)
                ->where('status', 'connected')
                ->first();
        }

        if (!$emailAccount) {
            $emailAccount = $conversation->emailAccount;
        }

        if (!$emailAccount) {
            $emailAccount = EmailAccount::where('workspace_id', $conversation->workspace_id)
                ->where('status', 'connected')
                ->first();
        }

        if ($emailAccount && $messageFrom !== '' && $emailAccount->email !== $messageFrom) {
            Log::error('SendScheduledEmailJob: from_email mismatch with resolved account — refusing to send', [
                'message_id'      => $this->message->id,
                'from_email'      => $messageFrom,
                'account_email'   => $emailAccount->email,
                'workspace_id'    => $conversation->workspace_id,
            ]);
            $this->message->update([
                'schedule_status' => 'failed',
                'delivery_status' => 'failed',
                'delivery_error'  => "Sender mismatch: this message wants to send From <{$messageFrom}> but the matching email account is not connected. Reconnect that mailbox in Settings → Email, or compose a new reply.",
            ]);
            return;
        }

        // No account to send from — fail the message immediately instead of
        // silently marking it 'sent'. Marking as 'failed' (not 'pending')
        // keeps readyToSend() from re-picking it every minute forever.
        if (! $emailAccount) {
            Log::error('SendScheduledEmailJob: no connected email account for workspace', [
                'message_id' => $this->message->id,
                'workspace_id' => $conversation->workspace_id,
            ]);
            $this->message->update([
                'schedule_status' => 'failed',
                'delivery_status' => 'failed',
                'delivery_error' => 'No connected email account available to send from.',
            ]);
            return;
        }

        try {
            $emailSendService->sendReply($this->message, $emailAccount);

            $this->message->update([
                'schedule_status' => 'sent',
                'delivery_status' => 'sent',
                'sent_at' => now(),
            ]);

            // Track email send usage for plan limit enforcement.
            // Key MUST be 'emails_per_month' to match what PlanLimitService
            // reads + the admin Resource Limits UI.
            UsageRecord::incrementUsage($conversation->workspace_id, 'emails_per_month');
        } catch (\Throwable $e) {
            Log::error('SendScheduledEmailJob: failed to send', [
                'message_id' => $this->message->id,
                'attempt' => $this->attempts(),
                'error' => $e->getMessage(),
            ]);

            $this->message->update([
                'delivery_status' => 'failed',
                'delivery_error' => $e->getMessage(),
            ]);

            throw $e; // Let the queue retry
        }
    }

    /**
     * Called by the queue worker once all retries are exhausted. Flip
     * schedule_status to 'failed' so the every-minute SendScheduledEmails
     * command stops re-dispatching this message — otherwise a permanently
     * broken send loops forever (the "never-ending mail" bug).
     */
    public function failed(\Throwable $exception): void
    {
        try {
            $this->message->refresh();
            if ($this->message->schedule_status === 'pending') {
                $this->message->update([
                    'schedule_status' => 'failed',
                    'delivery_status' => 'failed',
                    'delivery_error' => \Illuminate\Support\Str::limit($exception->getMessage(), 500),
                ]);
            }
        } catch (\Throwable $e) {
            Log::error('SendScheduledEmailJob::failed could not mark message', [
                'message_id' => $this->message->id ?? null,
                'error' => $e->getMessage(),
            ]);
        }
    }
}
