<?php

namespace App\Services\Email;

use App\Enums\EmailStatus;
use App\Jobs\SendEmailMessageJob;
use App\Mail\OutgoingSchoolEmail;
use App\Models\EmailMessage;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;

class EmailSenderService
{
    public function __construct(
        private readonly EmailConfigurationService $configuration,
        private readonly EmailSanitizer $sanitizer,
        private readonly EmailAttachmentService $attachments,
    ) {}

    /**
     * @param  array<string, mixed>  $data
     */
    public function compose(array $data, ?Model $related = null, ?int $userId = null): EmailMessage
    {
        $config = $this->configuration->get();

        return EmailMessage::query()->create([
            'status' => EmailStatus::Draft,
            'user_id' => $userId,
            'email_template_id' => $data['email_template_id'] ?? null,
            'from_name' => $config->from_name,
            'from_address' => $config->from_address,
            'reply_to' => $config->reply_to,
            'to' => $data['to'],
            'cc' => $this->parseList($data['cc'] ?? null),
            'bcc' => $this->parseList($data['bcc'] ?? null),
            'subject' => $data['subject'],
            'body_html' => $this->sanitizer->clean($data['body_html'] ?? ''),
            'related_type' => $related ? $related::class : ($data['related_type'] ?? null),
            'related_id' => $related?->getKey() ?? ($data['related_id'] ?? null),
            'related_module' => $data['related_module'] ?? null,
            'scheduled_at' => $data['scheduled_at'] ?? null,
        ]);
    }

    /**
     * @param  array<int, UploadedFile>|null  $uploadedFiles
     */
    public function queueSend(EmailMessage $message, ?array $uploadedFiles = null, bool $attachDocumentPdf = false): EmailMessage
    {
        if (! $this->configuration->isReady()) {
            throw new \RuntimeException('Email sending is disabled or not configured.');
        }

        if ($uploadedFiles) {
            $this->attachments->attachUploadedFiles($message, $uploadedFiles);
        }

        if ($attachDocumentPdf) {
            $this->attachRelatedDocument($message);
        }

        $status = $message->scheduled_at && $message->scheduled_at->isFuture()
            ? EmailStatus::Scheduled
            : EmailStatus::Queued;

        $message->update(['status' => $status]);

        if ($status === EmailStatus::Scheduled) {
            SendEmailMessageJob::dispatch($message->id)->delay($message->scheduled_at);
        } elseif ($status === EmailStatus::Queued) {
            SendEmailMessageJob::dispatch($message->id);
        }

        return $message->fresh(['attachments']);
    }

    public function sendNow(EmailMessage $message): void
    {
        if (! $this->configuration->isReady()) {
            throw new \RuntimeException('Email sending is disabled or not configured.');
        }

        $message->update(['status' => EmailStatus::Sending]);

        try {
            $mailer = $this->configuration->applyRuntimeConfig();
            $mailable = new OutgoingSchoolEmail($message->fresh(['attachments']));

            Mail::mailer($mailer)->to($message->to)->send($mailable);

            $message->update([
                'status' => EmailStatus::Sent,
                'sent_at' => now(),
                'error_message' => null,
            ]);

            $this->updateRelatedDocumentStatus($message, 'sent');
        } catch (\Throwable $e) {
            Log::warning('Email send failed', [
                'message_id' => $message->id,
                'error' => $e->getMessage(),
            ]);

            $message->update([
                'status' => EmailStatus::Failed,
                'error_message' => Str($e->getMessage())->limit(2000)->toString(),
                'retry_count' => $message->retry_count + 1,
            ]);

            $this->updateRelatedDocumentStatus($message, 'failed');

            throw $e;
        }
    }

    public function retry(EmailMessage $message): EmailMessage
    {
        if ($message->status !== EmailStatus::Failed) {
            throw new \InvalidArgumentException('Only failed emails can be retried.');
        }

        $message->update([
            'status' => EmailStatus::Queued,
            'error_message' => null,
        ]);

        SendEmailMessageJob::dispatch($message->id);

        return $message->fresh();
    }

    private function attachRelatedDocument(EmailMessage $message): void
    {
        $related = $message->related;

        if ($related instanceof \App\Models\Invoice) {
            $this->attachments->attachInvoicePdf($message, $related);

            return;
        }

        if ($related instanceof \App\Models\Quotation) {
            $this->attachments->attachQuotationPdf($message, $related);

            return;
        }

        if ($related instanceof \App\Models\Receipt) {
            $this->attachments->attachReceiptPdf($message, $related);
        }
    }

    private function updateRelatedDocumentStatus(EmailMessage $message, string $status): void
    {
        $related = $message->related;

        if ($related && in_array('email_status', $related->getFillable(), true)) {
            $related->update([
                'email_status' => $status === 'sent' ? ($message->retry_count > 0 ? 'resent' : 'sent') : 'failed',
                'email_sent_at' => $status === 'sent' ? now() : $related->email_sent_at,
            ]);
        }
    }

    /**
     * @return list<string>|null
     */
    private function parseList(mixed $value): ?array
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_array($value)) {
            $emails = array_values(array_filter(array_map('trim', $value)));

            return $emails === [] ? null : $emails;
        }

        $emails = preg_split('/[\s,;]+/', (string) $value, -1, PREG_SPLIT_NO_EMPTY) ?: [];

        return $emails === [] ? null : array_values($emails);
    }
}
