<?php

namespace App\Components\Contact;

use App\Actions\Communications\DispatchContactMessageAction;
use App\Actions\Communications\ResolveTemplateRecipientsAction;
use App\Actions\Communications\ValidateRecipientAction;
use App\Enum\Contact\MessageChannel;
use App\Enum\Contact\MessageOrigin;
use App\Models\Claim\Claim;
use App\Models\Client\Client;
use App\Models\Contact\ContactTemplate;
use App\Models\Document\Document;
use App\Models\Institution\InstitutionContact;
use App\Models\RedressScheme;
use App\Models\Solicitor\Solicitor;
use App\Models\Solicitor\SolicitorEmailSender;
use App\Models\Solicitor\SolicitorSmsSender;
use App\Models\User\User;
use App\Services\Communications\Placeholders\TemplateRenderer;
use App\ValueObjects\Communications\DispatchRequest;
use App\ValueObjects\Communications\PlaceholderContext;
use App\ValueObjects\Communications\RenderedMessage;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
use App\Services\WipService;
use App\Trait\SupportsWip;

class ComposeMessage extends Component
{
    use SupportsWip;
    public Model $model;

    public bool $open = false;

    /** Filter templates to one channel ('email'|'sms') when opened via channel button. */
    public ?string $channelFilter = null;

    /** When true the panel shows a free-text textarea instead of a template selector. */
    public bool $freeTextMode = false;

    public string $freeSubject = '';

    public string $freeBodyText = '';

    /** When true the rendered preview becomes editable before send. */
    public bool $reviewMode = false;

    public string $reviewSubject = '';

    public string $reviewBodyText = '';

    /** Recipient type: 'client' | 'scheme' | 'user' | 'institution' | 'case_worker' */
    public string $recipientType = 'client';

    /** Institution recipient selection */
    public ?int $institutionId = null;

    public ?int $institutionContactId = null;

    /** User recipient selection (null = logged-in user) */
    public ?int $selectedUserId = null;

    /** Document attachment overrides: [type_id => document_id] */
    public array $attachmentOverrides = [];

    /** The picked template id (null when none chosen). */
    public ?int $templateId = null;

    /** Override sender — email sender id or phone sender id depending on channel. */
    public ?int $senderId = null;

    /** Computed from template on selection. */
    public ?string $channel = null;

    public ?string $previewSubject = null;

    public ?string $previewBodyText = null;

    public ?string $previewBodyHtml = null;

    public array $previewPlaceholders = [];

    public array $previewUnknown = [];

    public ?int $smsLength = null;

    public ?int $smsSegments = null;

    public ?string $smsEncoding = null;

    public ?string $error = null;

    public ?string $success = null;

    public function mount(Model $model): void
    {
        $this->model = $model;
    }

    #[On('contact:compose')]
    public function openComposer(?int $templateId = null, ?string $channel = null, bool $freeText = false): void
    {
        $this->resetComposerState();
        $this->resetWip();
        $this->startWipTimer();
        $this->channelFilter = $channel;
        $this->open = true;

        if ($freeText) {
            $this->freeTextMode = true;

            return;
        }

        if ($templateId) {
            $this->templateId = (int) $templateId;
            $this->applyTemplateDefaults();
        } elseif ($channel) {
            // Auto-select the first template for this channel so the panel is
            // immediately useful rather than requiring a manual template pick.
            $first = ContactTemplate::query()
                ->where('is_active', true)
                ->where('allow_manual_send', true)
                ->where('channel', $channel)
                ->where(function ($q) {
                    $q->whereNull('applies_to_type')
                        ->orWhere('applies_to_type', $this->model->getMorphClass())
                        ->orWhere('applies_to_type', get_class($this->model));
                })
                ->orderBy('name')
                ->first();

            if ($first) {
                $this->templateId = $first->id;
                $this->applyTemplateDefaults();
            }
        }

        $this->refreshPreview();
    }

    public function switchToFreeText(): void
    {
        $this->freeTextMode = true;
        $this->templateId = null;
        $this->previewSubject = null;
        $this->previewBodyText = null;
        $this->previewBodyHtml = null;
        $this->previewPlaceholders = [];
        $this->previewUnknown = [];
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;
        $this->error = null;
    }

    public function switchToTemplate(): void
    {
        $this->freeTextMode = false;
        $this->freeSubject = '';
        $this->freeBodyText = '';
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;
        $this->error = null;
    }

    public function updatedFreeBodyText(): void
    {
        $this->recalculateFreeTextSms();
    }

    public function enterReviewMode(): void
    {
        $this->reviewMode = true;
        $this->reviewSubject = $this->previewSubject ?? '';
        $this->reviewBodyText = $this->previewBodyText ?? '';
        $this->error = null;

        if ($this->channel === MessageChannel::SMS->value) {
            $this->recalculateReviewSms();
        }
    }

    public function exitReviewMode(): void
    {
        $this->reviewMode = false;
        $this->reviewSubject = '';
        $this->reviewBodyText = '';
        $this->error = null;

        // Restore SMS counter from the original preview
        if ($this->channel === MessageChannel::SMS->value) {
            $renderer = app(TemplateRenderer::class);
            $msg = new RenderedMessage(
                subject: null,
                bodyText: $this->previewBodyText ?? '',
                bodyHtml: null,
            );
            $seg = $renderer->previewSegments($msg, MessageChannel::SMS);
            $this->smsLength = $seg['length'];
            $this->smsSegments = $seg['segments'];
            $this->smsEncoding = $seg['encoding'];
        }
    }

    public function updatedReviewBodyText(): void
    {
        $this->recalculateReviewSms();
    }

    public function close(): void
    {
        $this->open = false;
        $this->resetComposerState();
    }

    public function selectTemplate($templateId): void
    {
        $this->templateId = $templateId !== '' && $templateId !== null
            ? (int) $templateId
            : null;
        $this->reviewMode = false;
        $this->reviewSubject = '';
        $this->reviewBodyText = '';
        $this->attachmentOverrides = [];
        $this->applyTemplateDefaults();
        $this->refreshPreview();
    }

    public function updatedSenderId(): void
    {
        $this->refreshPreview();
    }

    public function updatedInstitutionId(): void
    {
        $this->institutionContactId = null;
        $this->applyPrimaryContactDefault();
    }

    public function replaceAttachment(int $typeId, int $documentId): void
    {
        $this->attachmentOverrides[$typeId] = $documentId;
    }

    public function refreshPreview(): void
    {
        $this->error = null;
        $this->previewSubject = null;
        $this->previewBodyText = null;
        $this->previewBodyHtml = null;
        $this->previewPlaceholders = [];
        $this->previewUnknown = [];
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;

        $tpl = $this->currentTemplate();
        if (! $tpl) {
            return;
        }

        $sender = $this->resolveSender($tpl);
        $ctx = PlaceholderContext::fromMessageable(
            messageable: $this->model,
            solicitor: app(Solicitor::class),
            user: auth()->user(),
            sender: $sender,
        );

        try {
            $renderer = app(TemplateRenderer::class);
            $rendered = $renderer->render($tpl, $ctx, strict: false);

            $this->previewSubject = $rendered->subject;
            $this->previewBodyText = $rendered->bodyText;
            $this->previewBodyHtml = $rendered->bodyHtml;
            $this->previewPlaceholders = $rendered->placeholdersUsed;
            $this->previewUnknown = $rendered->unknownPlaceholders;

            if ($tpl->channel === MessageChannel::SMS) {
                $seg = $renderer->previewSegments($rendered, MessageChannel::SMS);
                $this->smsLength = $seg['length'];
                $this->smsSegments = $seg['segments'];
                $this->smsEncoding = $seg['encoding'];
            }
        } catch (\Throwable $e) {
            $this->error = 'Preview failed: '.$e->getMessage();
        }
    }

    public function send(
            DispatchContactMessageAction $dispatch,
        ): void
    {
        if ($this->freeTextMode) {
            $this->sendFreeText($dispatch);

            return;
        }

        $tpl = $this->currentTemplate();
        if (! $tpl) {
            $this->error = 'Select a template first.';

            return;
        }

        $recipients = $this->resolveRecipients();
        if (empty($recipients)) {
            $this->error = 'Select a recipient first.';

            return;
        }

        $channel = $tpl->channel;

        $this->authorize($channel === MessageChannel::EMAIL
            ? 'communications.send.email'
            : 'communications.send.sms');

        $client = $this->resolveClient();
        $extraContext = [
            'attachment_overrides' => array_map('intval', $this->attachmentOverrides),
        ];
        if ($client) {
            $extraContext['document_client_id'] = $client->id;
            $extraContext['document_client_morph'] = $client->getMorphClass();
        }

        // A single recipient keeps the detailed per-result feedback
        // (opted-out / duplicate / rate-limited).
        if (count($recipients) === 1) {
            $this->handleDispatchResult(
                $dispatch($this->buildDispatchRequest($recipients[0], $tpl, $extraContext))
            );

            return;
        }

        // Multiple recipients (user broadcast): send to each, then report an aggregate.
        $queued = 0;
        $lastMessageId = null;
        $failures = [];
        foreach ($recipients as $recipient) {
            $result = $dispatch($this->buildDispatchRequest($recipient, $tpl, $extraContext));
            if ($result->queued) {
                $queued++;
                $lastMessageId = $result->message?->id;
            } else {
                $failures[] = $this->describeFailure($result);
            }
        }

        if ($queued > 0) {
            // Per-recipient warnings were already shown in the panel before sending.
            $this->dispatch('contactMessageQueued', messageId: $lastMessageId);
            $this->close();

            return;
        }

        $this->error = 'No messages could be sent: '.implode('; ', array_unique($failures));
    }

    private function buildDispatchRequest(Model $recipient, ContactTemplate $tpl, array $extraContext): DispatchRequest
    {
        return new DispatchRequest(
            messageable: $this->model,
            recipient: $recipient === $this->model ? null : $recipient,
            template: $tpl,
            user: auth()->user(),
            senderEmailId: $tpl->isEmail() ? $this->senderId : null,
            smsSenderId: $tpl->isSms() ? $this->senderId : null,
            origin: MessageOrigin::MANUAL,
            reviewSubject: $this->reviewMode ? ($this->reviewSubject ?: null) : null,
            reviewBodyText: $this->reviewMode ? $this->reviewBodyText : null,
            extraContext: $extraContext,
        );
        $this->stopWipTimer();
    }

    private function describeFailure($result): string
    {
        return match (true) {
            $result->wasSuppressed => 'recipient opted out',
            $result->wasDeduped => 'duplicate send prevented',
            $result->wasRateLimited => 'rate limit hit',
            default => $result->reason ?? 'unknown error',
        };
    }

    private function sendFreeText(DispatchContactMessageAction $dispatch): void
    {
        if (! $this->channelFilter) {
            $this->error = 'No channel selected.';

            return;
        }
        if (! trim($this->freeBodyText)) {
            $this->error = 'Message body is required.';

            return;
        }
        if ($this->channelFilter === 'email' && ! trim($this->freeSubject)) {
            $this->error = 'Subject is required for email.';

            return;
        }

        $channel = MessageChannel::from($this->channelFilter);

        $this->authorize($channel === MessageChannel::EMAIL
            ? 'communications.send.email'
            : 'communications.send.sms');

        $request = new DispatchRequest(
            messageable: $this->model,
            template: null,
            user: auth()->user(),
            senderEmailId: $channel === MessageChannel::EMAIL ? $this->senderId : null,
            smsSenderId: $channel === MessageChannel::SMS ? $this->senderId : null,
            origin: MessageOrigin::MANUAL,
            channel: $channel,
            freeSubject: $this->freeSubject ?: null,
            freeBodyText: $this->freeBodyText,
        );

        $this->handleDispatchResult($dispatch($request));
    }

    private function handleDispatchResult($result): void
    {

        if ($result->queued) {
            $this->stopWipTimer();

            app(WipService::class)->create([
                'client_id' => $this->model instanceof Client ? $this->model->id : null,
                'claim_id' => $this->model instanceof Claim ? $this->model->id : null,
                ...$this->getWipPayload('email'),
                'description' => $this->wipDescription ?: 'Sent communication',
            ], $result->message);
    
            $this->success = 'Message queued for sending.';
            $this->dispatch('contactMessageQueued', messageId: $result->message?->id);
            $this->close();

            return;
        }
        if ($result->wasSuppressed) {
            $this->error = 'Recipient has opted out: '.($result->reason ?? '');

            return;
        }
        if ($result->wasDeduped) {
            $this->error = 'Duplicate send prevented: '.($result->reason ?? '');

            return;
        }
        if ($result->wasRateLimited) {
            $this->error = 'Rate limit hit — please wait before sending again.';

            return;
        }
        $this->error = $result->reason ?? 'Unable to send.';
    }

    public function render()
    {
        $relatedClaim = method_exists($this->model, 'claim') ? $this->model->claim : null;
        $claim = $this->model instanceof Claim ? $this->model : $relatedClaim;

        $morphTypes = array_unique(array_filter([
            $this->model->getMorphClass(),
            get_class($this->model),
            $relatedClaim?->getMorphClass(),
            $relatedClaim ? get_class($relatedClaim) : null,
        ]));

        $templates = ContactTemplate::query()
            ->where('is_active', true)
            ->where('allow_manual_send', true)
            ->where(function ($q) use ($morphTypes) {
                $q->whereNull('applies_to_type')
                    ->orWhereIn('applies_to_type', $morphTypes);
            })
            ->when($this->channelFilter, fn ($q) => $q->where('channel', $this->channelFilter))
            ->orderBy('channel')
            ->orderBy('name')
            ->get();

        $resolvedChannel = $this->freeTextMode ? $this->channelFilter : $this->channel;

        $senders = $resolvedChannel === MessageChannel::SMS->value
            ? SolicitorSmsSender::query()->active()->orderByDesc('is_default')->orderBy('label')->get()
            : SolicitorEmailSender::query()->orderByDesc('is_default')->get();

        // Recipient validity for every selected recipient (the 'user' type may
        // resolve to several staff users configured on the template).
        $validityChannel = ($this->channel ? MessageChannel::tryFrom($this->channel) : null)
            ?? MessageChannel::EMAIL;
        $recipientValidities = [];
        foreach ($this->resolveRecipients() as $recipient) {
            $recipientValidities[] = app(ValidateRecipientAction::class)(
                $validityChannel,
                $recipient,
                app(Solicitor::class),
            );
        }
        $canSendAny = collect($recipientValidities)->contains(fn ($v) => $v->canSend());

        // Institutions for the claim (for institution recipient type) — includes
        // both directly-linked institutions and those reached via care settings.
        $claimInstitutions = $this->claimInstitutions();

        // Contacts for the selected institution
        $institutionContacts = $this->institutionId
            ? ($claimInstitutions->firstWhere('id', $this->institutionId)?->contacts ?? collect())
            : collect();

        // Attachment preview — resolve documents per template doc type
        $loadedAttachments = $this->resolveAttachments($claim);

        return view('components.contact.compose-message', [
            'templates' => $templates,
            'senders' => $senders,
            'currentTemplate' => $this->currentTemplate(),
            'recipientValidities' => $recipientValidities,
            'canSendAny' => $canSendAny,
            'claimInstitutions' => $claimInstitutions,
            'institutionContacts' => $institutionContacts,
            'loadedAttachments' => $loadedAttachments,
            'scheme' => RedressScheme::first(),
        ]);
    }

    /* ---------- helpers ---------- */

    private function resetComposerState(): void
    {
        $this->channelFilter = null;
        $this->freeTextMode = false;
        $this->freeSubject = '';
        $this->freeBodyText = '';
        $this->reviewMode = false;
        $this->reviewSubject = '';
        $this->reviewBodyText = '';
        $this->recipientType = 'client';
        $this->institutionId = null;
        $this->institutionContactId = null;
        $this->selectedUserId = null;
        $this->attachmentOverrides = [];
        $this->templateId = null;
        $this->senderId = null;
        $this->channel = null;
        $this->previewSubject = null;
        $this->previewBodyText = null;
        $this->previewBodyHtml = null;
        $this->previewPlaceholders = [];
        $this->previewUnknown = [];
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;
        $this->error = null;
        $this->success = null;
    }

    private function recalculateReviewSms(): void
    {
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;

        if ($this->channel !== MessageChannel::SMS->value || $this->reviewBodyText === '') {
            return;
        }

        $renderer = app(TemplateRenderer::class);
        $msg = new RenderedMessage(
            subject: null,
            bodyText: $this->reviewBodyText,
            bodyHtml: null,
        );
        $seg = $renderer->previewSegments($msg, MessageChannel::SMS);
        $this->smsLength = $seg['length'];
        $this->smsSegments = $seg['segments'];
        $this->smsEncoding = $seg['encoding'];
    }

    private function recalculateFreeTextSms(): void
    {
        $this->smsLength = null;
        $this->smsSegments = null;
        $this->smsEncoding = null;

        if ($this->channelFilter !== 'sms' || $this->freeBodyText === '') {
            return;
        }

        $renderer = app(TemplateRenderer::class);
        $msg = new RenderedMessage(
            subject: null,
            bodyText: $this->freeBodyText,
            bodyHtml: null,
        );
        $seg = $renderer->previewSegments($msg, MessageChannel::SMS);
        $this->smsLength = $seg['length'];
        $this->smsSegments = $seg['segments'];
        $this->smsEncoding = $seg['encoding'];
    }

    private function applyTemplateDefaults(): void
    {
        $tpl = $this->currentTemplate();
        if (! $tpl) {
            return;
        }
        $this->channel = $tpl->channel?->value;
        $this->senderId = $tpl->isEmail()
            ? $tpl->default_sender_email_id
            : $tpl->default_sms_sender_id;

        // Picking a template resets recipient sub-selections, then seeds defaults
        // for the new recipient type.
        $this->recipientType = $tpl->recipient_type ?? 'client';
        $this->institutionId = null;
        $this->institutionContactId = null;
        $this->selectedUserId = null;

        if ($this->recipientType === 'institution') {
            $this->applyInstitutionDefaults();
        }
    }

    /**
     * Pre-select an institution + contact for the compose panel: if the claim has
     * exactly one linked institution, select it; then default to its primary contact.
     */
    private function applyInstitutionDefaults(): void
    {
        $institutions = $this->claimInstitutions();
        if ($institutions->count() === 1) {
            $this->institutionId = $institutions->first()->id;
        }
        $this->applyPrimaryContactDefault($institutions);
    }

    /** Default the contact to the institution's primary (or first) contact. */
    private function applyPrimaryContactDefault($institutions = null): void
    {
        if (! $this->institutionId) {
            return;
        }
        $institutions ??= $this->claimInstitutions();
        $contacts = $institutions->firstWhere('id', $this->institutionId)?->contacts ?? collect();
        $primary = $contacts->firstWhere('is_primary', true) ?? $contacts->first();
        $this->institutionContactId = $primary?->id;
    }

    private function currentTemplate(): ?ContactTemplate
    {
        return $this->templateId ? ContactTemplate::find($this->templateId) : null;
    }

    private function resolveSender(ContactTemplate $tpl)
    {
        $id = $this->senderId
            ?: ($tpl->isEmail() ? $tpl->default_sender_email_id : $tpl->default_sms_sender_id);

        if (! $id) {
            return $tpl->isEmail()
                ? app(Solicitor::class)?->emailSenderFor('general')
                : SolicitorSmsSender::query()->active()->orderByDesc('is_default')->first();
        }

        return $tpl->isEmail()
            ? SolicitorEmailSender::find($id)
            : SolicitorSmsSender::find($id);
    }

    /**
     * Resolve every send-to recipient for the current selection. Returns a list so the
     * 'user' type can broadcast to all the staff users configured on the template.
     *
     * Resolution is delegated to the shared ResolveTemplateRecipientsAction (also used by
     * automatic trigger sends) so manual and automatic sends agree. Institution sends are
     * the exception: they are driven by the in-panel picker so the user can choose a
     * specific institution + contact, so they resolve from that selection here.
     *
     * @return array<int, Model>
     */
    private function resolveRecipients(): array
    {
        $tpl = $this->currentTemplate();
        if (! $tpl) {
            return [];
        }

        if ($this->recipientType === 'institution') {
            return $this->institutionContactId
                ? array_values(array_filter([InstitutionContact::find($this->institutionContactId)]))
                : [];
        }

        $recipients = app(ResolveTemplateRecipientsAction::class)($tpl, $this->model);

        // Manual convenience: a 'user' template with no staff configured falls back to the
        // explicitly-picked or acting user (the automatic path has no acting user).
        if (empty($recipients) && $this->recipientType === 'user') {
            $fallback = $this->selectedUserId
                ? User::find($this->selectedUserId)
                : auth()->user();

            return $fallback ? [$fallback] : [];
        }

        return $recipients;
    }

    /**
     * Institutions linked to the claim — used to populate the in-panel institution picker.
     * Delegates to the shared recipient resolver so the claim/institution traversal lives
     * in one place.
     *
     * @return Collection<int, \App\Models\Institution\Institution>
     */
    private function claimInstitutions(): Collection
    {
        $resolver = app(ResolveTemplateRecipientsAction::class);

        return $resolver->institutionsFor($resolver->claimFor($this->model));
    }

    private function resolveClient(): ?Client
    {
        if ($this->model instanceof Client) {
            return $this->model;
        }
        if ($this->model instanceof Claim) {
            return $this->model->client;
        }

        return null;
    }

    private function resolveAttachments(?Claim $claim): array
    {
        $tpl = $this->currentTemplate();
        if (! $tpl) {
            return [];
        }
        $tpl->load('documentTypes');
        $docTypes = $tpl->documentTypes;
        if ($docTypes->isEmpty()) {
            return [];
        }

        $client = $this->resolveClient();

        $attachments = [];
        foreach ($docTypes as $docType) {
            $overrideId = $this->attachmentOverrides[$docType->id] ?? null;

            // All documents of this type for the replace dropdown
            $available = $client
                ? Document::where('owner_type', $client->getMorphClass())
                    ->where('owner_id', $client->getKey())
                    ->where('type_id', $docType->id)
                    ->latest()
                    ->get()
                : collect();

            // A template document type represents every matching client document.
            // An explicit replacement still narrows the selection to that one file.
            $documents = $overrideId
                ? $available->where('id', (int) $overrideId)->values()
                : $available;

            $attachments[] = [
                'type' => $docType,
                'documents' => $documents,
                'available' => $available,
            ];
        }

        return $attachments;
    }
}
