<?php

namespace App\Jobs;

use App\Models\Contact\Send\EmailSend;
use App\Services\SendGridEmailService;
use App\Support\SendStatus;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Throwable;

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

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

    // SendGrid's hard cap is 30 MB total; stay well below it and PHP's memory limit
    private const MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;  // 8 MB per file
    private const MAX_TOTAL_BYTES      = 20 * 1024 * 1024; // 20 MB across all files

    public function __construct(public EmailSend $emailSend) {
    }

    public function handle(SendGridEmailService $mailjet): void {
        if ($this->emailSend->status === SendStatus::SENT) {
            return;
        }

        $this->emailSend->update([
            'status' => SendStatus::SENDING,
            'attempts' => $this->emailSend->attempts + 1,
            'last_attempt_at' => now(),
            'provider' => 'sendgrid',
        ]);

        try {
            // Resolve attachments: entries with a 'path' key are read from storage
            // here (just-in-time) so base64 blobs are never persisted to the DB.
            // Files that exceed the per-file or total size caps are skipped rather
            // than OOM-crashing the queue worker.
            $totalBytes = 0;
            $attachments = collect($this->emailSend->attachments ?? [])
                ->map(function (array $a) use (&$totalBytes): ?array {
                    if (!isset($a['path'])) {
                        return $a;
                    }

                    $disk = $a['disk'] ?? null;
                    $fileSize = $disk
                        ? Storage::disk($disk)->size($a['path'])
                        : Storage::size($a['path']);

                    if ($fileSize > self::MAX_ATTACHMENT_BYTES) {
                        Log::warning('SendEmailJob: attachment skipped — exceeds per-file size limit', [
                            'email_send_id' => $this->emailSend->id,
                            'path'          => $a['path'],
                            'size_bytes'    => $fileSize,
                            'limit_bytes'   => self::MAX_ATTACHMENT_BYTES,
                        ]);
                        return null;
                    }

                    if ($totalBytes + $fileSize > self::MAX_TOTAL_BYTES) {
                        Log::warning('SendEmailJob: attachment skipped — would exceed total size limit', [
                            'email_send_id' => $this->emailSend->id,
                            'path'          => $a['path'],
                            'size_bytes'    => $fileSize,
                            'total_so_far'  => $totalBytes,
                            'limit_bytes'   => self::MAX_TOTAL_BYTES,
                        ]);
                        return null;
                    }

                    $totalBytes += $fileSize;
                    $a['content'] = base64_encode(
                        $disk ? Storage::disk($disk)->get($a['path']) : Storage::get($a['path'])
                    );
                    unset($a['path'], $a['disk']);
                    return $a;
                })
                ->filter()
                ->values()
                ->all();

            $response = $mailjet->send([
                'to_email'      => $this->emailSend->to_email,
                'to_name'       => $this->emailSend->to_name,
                'from_email'    => $this->emailSend->from_email,
                'from_name'     => $this->emailSend->from_name,
                'reply_to'      => $this->emailSend->reply_to,
                'subject'       => $this->emailSend->subject,
                'html'          => $this->emailSend->html_body,
                'text'          => $this->emailSend->text_body,
                'template_id'   => $this->emailSend->template,
                'template_data' => $this->emailSend->template_data,
                'attachments'   => $attachments,
            ]);

            // Free the encoded attachment content as soon as the call is done
            unset($attachments);
            gc_collect_cycles();

            // SendGrid returns 202 Accepted with no body; message ID is in the X-Message-Id header
            $messageId = $response['headers']['X-Message-Id'][0]
                ?? null;

            $this->emailSend->update([
                'provider_message_id' => $messageId,
                'provider_response' => $response,
            ]);

            if($messageId) {
                $this->emailSend->update([
                    'status' => SendStatus::SENT,
                    'sent_at' => now(),
                ]);
            }

        } catch (RequestException $e) {
            $status = $e->response->status();
            $this->emailSend->update([
                'status'        => SendStatus::FAILED,
                'error_message' => $e->getMessage(),
            ]);

            if ($status === 413) {
                // Payload too large — retrying will never succeed, fail immediately
                Log::warning('SendEmailJob: SendGrid rejected payload as too large (413)', [
                    'email_send_id' => $this->emailSend->id,
                ]);
                $this->fail($e);
                return;
            }

            throw $e;
        } catch (Throwable $e) {
            $this->emailSend->update([
                'status'        => SendStatus::FAILED,
                'error_message' => $e->getMessage(),
            ]);

            throw $e;
        }
    }
}
