<?php

namespace App\Actions\Communications;

use App\Enum\Contact\MessageChannel;
use App\Enum\Contact\MessageDirection;
use App\Enum\Contact\MessageProvider;
use App\Enum\Contact\MessageStatus;
use App\Jobs\Contact\SendContactMessageJob;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Solicitor\Solicitor;
use App\Models\Solicitor\SolicitorEmailSender;
use App\Models\Solicitor\SolicitorSmsSender;
use App\Services\Communications\Placeholders\TemplateRenderer;
use App\Services\HistoryService;
use App\ValueObjects\Communications\DispatchRequest;
use App\ValueObjects\Communications\DispatchResult;
use App\ValueObjects\Communications\PlaceholderContext;
use App\ValueObjects\Communications\RenderedMessage;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;

class DispatchContactMessageAction
{
    public function __construct(
        protected TemplateRenderer $renderer,
        protected ValidateRecipientAction $validateRecipient,
        protected HistoryService $history,
    ) {}

    public function __invoke(DispatchRequest $request): DispatchResult
    {
        $template = $request->template;
        $channel = $template?->channel ?? $request->channel;

        if (!$channel) {
            return DispatchResult::invalidRecipient('No channel specified.');
        }

        if ($template !== null) {
            if (!$template->is_active) {
                return DispatchResult::invalidRecipient('Template is inactive.');
            }
            if ($request->origin->isAutomatic() && !$template->allow_automatic_send) {
                return DispatchResult::invalidRecipient('Template does not allow automatic sending.');
            }
            if (!$request->origin->isAutomatic() && !$template->allow_manual_send) {
                return DispatchResult::invalidRecipient('Template does not allow manual sending.');
            }
        }

        // --- Sender resolution ---
        $sender = $this->resolveSender($request, $template, $channel);
        if (!$sender) {
            return DispatchResult::invalidRecipient('No sender identity configured for this channel.');
        }

        // --- Recipient validation ---
        $solicitor = app(Solicitor::class);
        $recipientSnapshot = ($this->validateRecipient)($channel, $request->messageable, $solicitor);

        if (!$recipientSnapshot->canSend() && !$request->force) {
            if ($request->origin->isAutomatic()) {
                $message = $this->createRow($request, $template, $channel, $sender, $recipientSnapshot, null, MessageStatus::SUPPRESSED);
                $message->update([
                    'failure_reason' => $recipientSnapshot->reason,
                    'failed_at' => now(),
                ]);
                return DispatchResult::suppressed($message, $recipientSnapshot->reason ?? 'Recipient not contactable');
            }
            return DispatchResult::invalidRecipient($recipientSnapshot->reason ?? 'Recipient not contactable');
        }

        // --- Dedupe (automatic template sends only) ---
        $dedupeHash = $this->dedupeHash($request);

        if ($request->origin->isAutomatic() && $template !== null) {
            $windowMinutes = (int) ($request->originDetail['dedupe_window_minutes'] ?? config('contact.dedupe_window_minutes', 60));
            $existing = ContactMessage::query()
                ->where('template_id', $template->id)
                ->where('messageable_type', $request->messageable->getMorphClass())
                ->where('messageable_id', $request->messageable->getKey())
                ->where('origin_type', $request->origin->value)
                ->where('created_at', '>=', now()->subMinutes($windowMinutes))
                ->first();

            if ($existing) {
                return DispatchResult::deduped($existing, "Duplicate send within {$windowMinutes} minute window.");
            }
        }

        // --- Rate limit per recipient ---
        $recipientKey = $recipientSnapshot->value() ?? 'unknown';
        $rateKey = "contact:{$channel->value}:{$recipientKey}";
        $attempts = (int) config('contact.rate_limit.attempts', 5);
        $decay = (int) config('contact.rate_limit.decay_seconds', 60);

        if (RateLimiter::tooManyAttempts($rateKey, $attempts)) {
            Log::warning('Contact rate limit hit', ['key' => $rateKey]);
            return DispatchResult::rateLimited("Rate limit hit: {$attempts} per {$decay}s");
        }
        RateLimiter::hit($rateKey, $decay);

        // --- Render ---
        if ($template !== null && $request->reviewBodyText !== null) {
            $rendered = $this->buildReviewRendered($request, $channel, $sender);
        } elseif ($template !== null) {
            $ctx = PlaceholderContext::fromMessageable(
                messageable: $request->messageable,
                related: $request->related,
                solicitor: $solicitor,
                user: $request->user ?? auth()->user(),
                sender: $sender,
                extra: $request->extraContext,
            );
            // Automatic sends fail loud on unknown placeholders; manual sends render
            // with empty substitutions so the UI can still show the preview.
            $rendered = $this->renderer->render($template, $ctx, strict: $request->origin->isAutomatic());
        } else {
            $rendered = $this->buildFreeTextRendered($request, $channel, $sender);
        }

        // --- Persist + enqueue ---
        $message = DB::transaction(function () use ($request, $template, $channel, $sender, $recipientSnapshot, $rendered, $dedupeHash) {
            return $this->createRow(
                $request, $template, $channel, $sender, $recipientSnapshot, $rendered,
                status: MessageStatus::QUEUED,
                dedupeHash: $dedupeHash,
            );
        });

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

        $this->logHistory($request, $message, $channel);

        return DispatchResult::queued($message);
    }

    private function resolveSender(DispatchRequest $request, $template, MessageChannel $channel)
    {
        if ($channel === MessageChannel::EMAIL) {
            if ($request->senderEmailId) {
                return SolicitorEmailSender::query()->find($request->senderEmailId);
            }
            if ($template?->default_sender_email_id) {
                return $template->defaultSenderEmail;
            }
            $solicitor = app(Solicitor::class);
            return $solicitor?->emailSenderFor($template?->config['sender_purpose'] ?? 'general');
        }

        if ($request->smsSenderId) {
            return SolicitorSmsSender::query()->find($request->smsSenderId);
        }
        if ($template?->default_sms_sender_id) {
            return $template->defaultSmsSender;
        }
        return SolicitorSmsSender::query()
            ->active()
            ->orderByDesc('is_default')
            ->first();
    }

    private function buildReviewRendered(DispatchRequest $request, MessageChannel $channel, $sender): RenderedMessage
    {
        $bodyText = $request->reviewBodyText ?? '';

        if ($channel === MessageChannel::EMAIL) {
            $bodyHtml = nl2br(htmlspecialchars($bodyText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
            if ($sender->signatureHtml()) {
                $bodyHtml .= config('contact.signature.html_separator', '<br/><br/>') . $sender->signatureHtml();
            }
            if ($sender->signatureText()) {
                $bodyText .= config('contact.signature.text_separator', "\n\n") . $sender->signatureText();
            }
        } else {
            $bodyHtml = null;
        }

        return new RenderedMessage(
            subject: $request->reviewSubject ?: null,
            bodyText: $bodyText,
            bodyHtml: $bodyHtml,
        );
    }

    private function buildFreeTextRendered(DispatchRequest $request, MessageChannel $channel, $sender): RenderedMessage
    {
        $bodyText = $request->freeBodyText ?? '';

        if ($channel === MessageChannel::EMAIL) {
            $bodyHtml = nl2br(htmlspecialchars($bodyText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
            if ($sender->signatureHtml()) {
                $bodyHtml .= config('contact.signature.html_separator', '<br/><br/>') . $sender->signatureHtml();
            }
            if ($sender->signatureText()) {
                $bodyText .= config('contact.signature.text_separator', "\n\n") . $sender->signatureText();
            }
        } else {
            $bodyHtml = null;
        }

        return new RenderedMessage(
            subject: $request->freeSubject ?: null,
            bodyText: $bodyText,
            bodyHtml: $bodyHtml,
        );
    }

    private function createRow(
        DispatchRequest $request,
        $template,
        MessageChannel $channel,
        $sender,
        $recipientSnapshot,
        $rendered,
        MessageStatus $status,
        ?string $dedupeHash = null,
    ): ContactMessage {
        $now = now();

        return ContactMessage::create([
            'channel' => $channel,
            'direction' => MessageDirection::OUTBOUND,
            'provider' => $channel === MessageChannel::EMAIL
                ? MessageProvider::SENDGRID
                : MessageProvider::TWILIO,
            'status' => $status,
            'origin_type' => $request->origin,
            'origin_detail' => $request->originDetail,
            'messageable_type' => $request->messageable->getMorphClass(),
            'messageable_id' => $request->messageable->getKey(),
            'related_type' => $request->related?->getMorphClass(),
            'related_id' => $request->related?->getKey(),
            'template_id' => $template?->id,
            'sender_email_id' => $sender instanceof SolicitorEmailSender ? $sender->id : null,
            'sms_sender_id' => $sender instanceof SolicitorSmsSender ? $sender->id : null,
            'recipient_name' => $recipientSnapshot->name,
            'recipient_email' => $recipientSnapshot->email,
            'recipient_phone' => $recipientSnapshot->phone,
            'sender_display' => $sender->label(),
            'sender_value' => $sender->value(),
            'reply_to' => $sender->replyTo(),
            'subject' => $rendered?->subject,
            'body_text' => $rendered?->bodyText,
            'body_html' => $rendered?->bodyHtml,
            'rendered_placeholders' => $rendered?->placeholdersUsed,
            'dedupe_hash' => $dedupeHash,
            'queued_at' => $status === MessageStatus::QUEUED ? $now : null,
            'created_by' => auth()->id() ?? $request->user?->id,
        ]);
    }

    private function dedupeHash(DispatchRequest $request): string
    {
        return md5(implode('|', [
            $request->template?->id,
            $request->messageable->getMorphClass(),
            $request->messageable->getKey(),
            $request->origin->value,
            json_encode($request->originDetail),
        ]));
    }

    private function logHistory(DispatchRequest $request, ContactMessage $message, MessageChannel $channel): void
    {
        try {
            if ($request->messageable instanceof \App\Interface\HistoryOwner) {
                $label = $channel === MessageChannel::EMAIL ? 'Email' : 'SMS';
                $subject = $message->subject ?: 'message';
                $this->history->add(
                    $request->messageable,
                    "{$label} queued: \"{$subject}\" via {$message->sender_display}",
                    ['contact_message_id' => $message->id],
                );
            }
        } catch (\Throwable $e) {
            Log::debug('History log skipped for contact message', ['error' => $e->getMessage()]);
        }
    }
}
