<?php

namespace Modules\MedPortal\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Modules\MedPortal\Entities\MportalMessage;

/**
 * MessagingService — secure patient<->provider messaging (SIFA Phase 8,
 * patient_portal → communications → secure_messaging).
 *
 * Threads are grouped by a UUID `thread_id`. Sending a message with no
 * thread_id opens a new thread. On a NEW message, MedNotify is pinged fail-soft
 * (class_exists guard, try/catch) — never a hard dependency.
 */
class MessagingService
{
    /**
     * Send a message. If $threadId is null a new thread UUID is generated.
     *
     * @param  string  $senderType  'patient' | 'staff'
     */
    public function send(
        int $businessId,
        int $patientId,
        int $contactId,
        string $senderType,
        int $senderId,
        string $body,
        ?string $threadId = null,
        ?string $attachmentPath = null
    ): MportalMessage {
        $threadId = $threadId ?: (string) Str::uuid();

        $message = MportalMessage::create([
            'business_id'     => $businessId,
            'thread_id'       => $threadId,
            'patient_id'      => $patientId,
            'contact_id'      => $contactId,
            'sender_type'     => $senderType,
            'sender_id'       => $senderId,
            'body'            => $body,
            'attachment_path' => $attachmentPath,
        ]);

        $this->notifyNewMessage($message);

        return $message;
    }

    /**
     * Thread summaries for the staff inbox: latest message per thread with an
     * unread count (messages FROM the patient not yet read by staff).
     *
     * @return \Illuminate\Support\Collection
     */
    public function threadsForBusiness(int $businessId)
    {
        $latestIds = MportalMessage::forBusiness($businessId)
            ->select(DB::raw('MAX(id) as id'))
            ->groupBy('thread_id')
            ->pluck('id');

        $latest = MportalMessage::whereIn('id', $latestIds)
            ->orderByDesc('created_at')
            ->get();

        // Unread-from-patient counts per thread (what staff still needs to read).
        $unread = MportalMessage::forBusiness($businessId)
            ->where('sender_type', 'patient')
            ->whereNull('read_at')
            ->select('thread_id', DB::raw('COUNT(*) as c'))
            ->groupBy('thread_id')
            ->pluck('c', 'thread_id');

        return $latest->map(function ($m) use ($unread) {
            $m->unread_count = (int) ($unread[$m->thread_id] ?? 0);

            return $m;
        });
    }

    /** Full message list of a thread, oldest first. */
    public function thread(int $businessId, string $threadId)
    {
        return MportalMessage::forBusiness($businessId)
            ->thread($threadId)
            ->orderBy('created_at')
            ->get();
    }

    /**
     * Mark a thread's messages from the OTHER party as read.
     *
     * @param  string  $readerType  the party doing the reading ('staff' reads
     *                              patient messages, and vice-versa)
     */
    public function markRead(int $businessId, string $threadId, string $readerType): int
    {
        $fromType = $readerType === 'staff' ? 'patient' : 'staff';

        return MportalMessage::forBusiness($businessId)
            ->thread($threadId)
            ->where('sender_type', $fromType)
            ->whereNull('read_at')
            ->update(['read_at' => now()]);
    }

    /** Count of threads with unread patient messages (dashboard tile). */
    public function unreadThreadCount(int $businessId): int
    {
        return (int) MportalMessage::forBusiness($businessId)
            ->where('sender_type', 'patient')
            ->whereNull('read_at')
            ->distinct()
            ->count('thread_id');
    }

    /**
     * Ping MedNotify on a new message — fail-soft. When the patient sends,
     * notify staff (mportal.messages holders); when staff sends, notify the
     * patient contact. Never throws; a clean no-op without MedNotify.
     */
    protected function notifyNewMessage(MportalMessage $message): void
    {
        try {
            if (! class_exists(\Modules\MedNotify\Services\MedNotify::class)) {
                return;
            }

            $context = [
                'business_id' => (int) $message->business_id,
                'variables'   => [
                    'thread_id' => $message->thread_id,
                    'preview'   => Str::limit((string) $message->body, 120),
                    'link'      => url('/med-portal/messages'),
                ],
            ];

            if ($message->sender_type === 'patient') {
                $context['users'] = $this->messagingUserIds((int) $message->business_id);
            } else {
                $context['contacts'] = [(int) $message->contact_id];
            }

            app(\Modules\MedNotify\Services\MedNotify::class)->notify('portal.message_received', $context);
        } catch (\Throwable $e) {
            \Log::warning('MedPortal message notify skipped: ' . $e->getMessage());
        }
    }

    /** ERP users with the mportal.messages permission in this business. */
    protected function messagingUserIds(int $businessId): array
    {
        try {
            if (! Schema::hasTable('users')) {
                return [];
            }

            return \App\User::where('business_id', $businessId)
                ->get()
                ->filter(fn ($u) => $u->can('mportal.messages'))
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
        } catch (\Throwable $e) {
            return [];
        }
    }
}
