<?php

namespace App\Services\Messaging;

use App\Events\ConversationUpdated;
use App\Events\MessageRead;
use App\Models\Conversation;
use App\Models\ConversationParticipant;
use App\Models\Message;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class ConversationReadService
{
    public function __construct(
        private readonly ConversationStateService $conversationState,
    ) {}

    public function markConversationRead(Conversation $conversation, User $user, ?int $messageId = null): ConversationParticipant
    {
        /** @var ConversationParticipant $participant */
        $participant = ConversationParticipant::query()
            ->where('conversation_id', $conversation->id)
            ->where('user_id', $user->id)
            ->whereNull('left_at')
            ->firstOrFail();

        $lastReadableMessage = Message::query()
            ->where('conversation_id', $conversation->id)
            ->whereNull('deleted_at')
            ->where('sender_id', '!=', $user->id)
            ->when($messageId, fn ($query) => $query->where('id', '<=', $messageId))
            ->orderByDesc('id')
            ->first();

        $readAt = now();

        $participant->forceFill([
            'last_read_at' => $readAt,
            'last_read_message_id' => $lastReadableMessage?->id,
        ])->save();

        if ($lastReadableMessage) {
            $messageReads = Message::query()
                ->select(['id'])
                ->where('conversation_id', $conversation->id)
                ->whereNull('deleted_at')
                ->where('sender_id', '!=', $user->id)
                ->where('id', '<=', $lastReadableMessage->id)
                ->get()
                ->map(fn (Message $message) => [
                    'message_id' => $message->id,
                    'user_id' => $user->id,
                    'read_at' => $readAt,
                ])
                ->all();

            if (! empty($messageReads)) {
                DB::table('message_reads')->upsert($messageReads, ['message_id', 'user_id'], ['read_at']);
            }
        }

        $participantIds = $this->conversationState->activeParticipantIds($conversation);
        $this->conversationState->touchConversationCachesForUsers($participantIds);

        DB::afterCommit(function () use ($conversation, $participant, $user, $participantIds): void {
            event(new MessageRead($conversation, $participant, $user));

            foreach ($participantIds as $participantId) {
                event(new ConversationUpdated($participantId, $conversation, 'message.read'));
            }
        });

        return $participant->fresh(['user']);
    }
}
