<?php

namespace App\Jobs;

use App\Models\Campaign;
use App\Models\CampaignEvent;
use App\Models\Contact;
use App\Models\SmtpConfig;
use App\Services\BusinessSettings;
use App\Services\EmailDeliverabilityChecker;
use App\Services\EmailRenderer;
use App\Services\SmtpConfigService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\Message;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

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

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

    public function __construct(
        public readonly int $campaignId,
        public readonly int $contactId,
    ) {}

    public function handle(EmailRenderer $renderer): void
    {
        $campaign = Campaign::find($this->campaignId);
        $contact  = Contact::find($this->contactId);

        if (!$campaign || !$contact) {
            return;
        }

        if ($reason = EmailDeliverabilityChecker::reason($contact->email)) {
            $this->recordBounce($campaign, $contact, $reason);
            return;
        }

        $blocks = $campaign->settings['blocks'] ?? [];

        $mergeData = [
            'contact.first_name' => $contact->first_name ?? '',
            'contact.last_name'  => $contact->last_name  ?? '',
            'contact.email'      => $contact->email       ?? '',
            'contact.full_name'  => trim(($contact->first_name ?? '') . ' ' . ($contact->last_name ?? '')),
            'first_name'         => $contact->first_name ?? '',
            'last_name'          => $contact->last_name  ?? '',
            'email'              => $contact->email       ?? '',
            'full_name'          => trim(($contact->first_name ?? '') . ' ' . ($contact->last_name ?? '')),
        ];

        if ($campaign->utm_status) {
            $renderer->setUtmData([
                'utm_source'   => $campaign->utm_source,
                'utm_medium'   => $campaign->utm_medium,
                'utm_campaign' => $campaign->utm_campaign,
                'utm_term'     => $campaign->utm_term,
                'utm_content'  => $campaign->utm_content,
            ]);
        }

        $html    = $renderer->render($blocks, $mergeData, $campaign->id, $contact->id, $campaign->email_pre_header);
        $subject = $campaign->email_subject ?: $campaign->title;

        // Business tags ({{business_name}} etc.) work in the subject too,
        // not just the body  same merge-tag values EmailRenderer applies.
        $subjectMergeData = array_merge(BusinessSettings::mergeTags($campaign->id), $mergeData);
        foreach ($subjectMergeData as $k => $v) {
            $subject = str_replace('{{' . $k . '}}', $v, $subject);
        }

        // Sender identity comes from the campaign owner's selected SMTP profile or their default.
        $smtp = null;
        $smtpId = $campaign->settings['smtp_config_id'] ?? null;
        if ($smtpId) {
            $smtp = SmtpConfig::where('user_id', $campaign->user_id)->where('id', $smtpId)->first();
        }
        if (!$smtp) {
            $smtp = SmtpConfig::where('user_id', $campaign->user_id)->where('is_default', true)->first();
        }

        // Same From/Reply-To precedence used by test sends (CampaignController
        // / RecurringCampaignController ::sendTest)  see SmtpConfigService.
        $identity   = SmtpConfigService::resolveSenderIdentity($campaign, $smtp);
        $fromEmail  = $identity['from_email'];
        $fromName   = $identity['from_name'];
        $replyEmail = $identity['reply_email'];
        $replyName  = $identity['reply_name'];
        $returnPath = $identity['return_path'];

        // Use custom SMTP if a default is configured in DB
        $mailer = $this->resolveMailer($smtp);

        try {
            $mailer->html($html, function (Message $msg) use ($campaign, $contact, $subject, $fromEmail, $fromName, $replyEmail, $replyName, $returnPath) {
                $msg->to($contact->email)->subject($subject);

                if ($fromEmail) {
                    $msg->from($fromEmail, $fromName);
                }
                if ($replyEmail) {
                    $msg->replyTo($replyEmail, $replyName);
                }
                if ($returnPath) {
                    $msg->returnPath($returnPath);
                }
            });

            Campaign::where('id', $this->campaignId)->increment('emails_sent');

            CampaignEvent::create([
                'campaign_id' => $this->campaignId,
                'contact_id'  => $this->contactId,
                'event_type'  => 'sent',
            ]);

            // Feed the contact's activity log (a send is not the contact's
            // own action, so last_activity is left untouched)
            $contact->activities()->create([
                'type'        => 'email_sent',
                'description' => 'Campaign email sent: "' . $campaign->title . '"',
            ]);

            $this->markCompleteIfDone();
        } catch (\Throwable $e) {
            // Counted in failed() once retries are exhausted
            throw $e;
        }
    }

    public function failed(\Throwable $e): void
    {
        Campaign::where('id', $this->campaignId)->increment('emails_failed');
        $this->markCompleteIfDone();
    }

    private function recordBounce(Campaign $campaign, Contact $contact, string $reason): void
    {
        Campaign::where('id', $this->campaignId)->increment('emails_bounced');

        CampaignEvent::create([
            'campaign_id' => $this->campaignId,
            'contact_id'  => $this->contactId,
            'event_type'  => 'bounced',
            'reason'      => $reason,
        ]);

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

        $contact->activities()->create([
            'type'        => 'email_bounced',
            'description' => 'Campaign email bounced: "' . $campaign->title . '" - ' . $reason,
        ]);

        $this->markCompleteIfDone();
    }

    // Mark as sent once every recipient has been sent, failed, or bounced.
    // This never runs against a recurring *template* row (type='recurring')
    //  only against one-off campaigns and materialized recurring
    // occurrences (type='recurring_occurrence'), both of which use `status`
    // for delivery progress. The guard stays as a safety net in case that
    // ever changes.
    private function markCompleteIfDone(): void
    {
        $fresh = Campaign::find($this->campaignId);

        if ($fresh && $fresh->type !== 'recurring' && $fresh->recipients_count > 0 &&
            ($fresh->emails_sent + $fresh->emails_failed + $fresh->emails_bounced) >= $fresh->recipients_count) {
            $fresh->update(['status' => 'sent']);

            $message = 'Your campaign "' . $fresh->title . '" has been sent successfully to ' . $fresh->emails_sent . ' contacts.';
            $type = 'success';
            if ($fresh->emails_failed > 0 || $fresh->emails_bounced > 0) {
                $message .= ' (' . $fresh->emails_failed . ' failed, ' . $fresh->emails_bounced . ' bounced)';
                $type = 'warning';
            }

            \App\Models\Notification::notify(
                $fresh->user_id,
                $type,
                'Campaign Sent',
                $message,
                '/campaigns/' . $fresh->id
            );
        }
    }

    private function resolveMailer(?SmtpConfig $smtp): \Illuminate\Mail\Mailer|\Illuminate\Mail\PendingMail
    {
        if (!$smtp) {
            return Mail::mailer();
        }

        // Dynamically configure the transport from the DB record - the service
        // returns the mailer name for the connection's provider (smtp, ses, …)
        return Mail::mailer(SmtpConfigService::configureRuntime($smtp));
    }
}
