<?php

namespace App\Services;

use App\Models\Chat;
use App\Models\MessageDeliveryHistory;
use App\Models\MessageViewHistory;
use App\Models\RealtimeMessage;
use App\Models\RealtimeParticipant;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Mockery\Exception;

trait RealtimeMessageService
{
   /**
    * Store new record
    *
    * @param array $data
    * @return Builder|Model
    */
   public static function store(array $data): Model|Builder
   {
      return RealtimeMessage::query()->create($data);
   }

   /**
    * Update existing record
    *
    * @param array $data
    * @return mixed
    */
   public function updateService(array $data): mixed
   {
      return tap($this)->update($data);
   }

   /**
    * Find by uuid
    *
    * @param string $uuid
    * @return Builder|Model|null
    */
   public static function findByUuid(string $uuid): Model|Builder|null
   {
      return RealtimeMessage::query()->where('uuid', $uuid)->first();
   }

   /**
    * Serialize for channel interaction
    *
    * @return array
    */
   public function serializeForChannel(): array
   {
      $message = $this;
      $chatData = $message->{'chat'}->toArray();

      unset($chatData['participants']);
      $sideParticipant = $message->{'chat'}->sideParticipant();
      $chatData['side_participant'] = $sideParticipant->load(['user.userDetail', 'user.business']);

      $serializedData = array(
         'uuid' => $message->{'uuid'},
         'message' => $message->{'message'},
         'sent_at' => $message->{'sent_at'},
         'chat' => $chatData,
         'emitter' => $message->{'emitter'}->load(['user.userDetail', 'user.business']),
         'latest_delivered_histories' => MessageDeliveryHistory::messageDeliveryHistory($message),
         'latest_view_histories' => MessageViewHistory::messageViewsHistory($message),
      );

      // Check attachment
      $realtimeMessageAttachment = $message->{'realtimeMessageAttachment'};
      if($realtimeMessageAttachment !== null) {
         $serializedData['realtime_message_attachment'] = $realtimeMessageAttachment->serializeForChannel();
      }

      return $serializedData;
   }

   /**
    * Send new chat message
    *
    * @param array $data
    * @return RealtimeMessage|null
    */
   public static function newMessage(array $data): ?RealtimeMessage
   {
      DB::beginTransaction();

      try {
         $message = self::createNewMessage($data);

         // Auto delivery ...
         // $message->deliveredBy();

         // Auto reading ...
         // $message->readBy();

         DB::commit();

         return $message->refresh();
      } catch (\Exception $exception) {
         DB::rollBack();
         log_debug($exception, 'ChatMessage::newMessage Unable to send message');
      }

      return null;
   }

   /**
    * Create new message
    *
    * @param array $data
    * @return RealtimeMessage|null
    */
   public static function createNewMessage(array $data): ?RealtimeMessage
   {
      /** @var User $user */
      $user = auth()->user();

      try {
         // Chat ...
         /** @var ?Chat $chat */
         $chat = Chat::findByNameOrCreate($data['chat_name']);

         // Emitter participant
         $emitter = RealtimeParticipant::findOrCreate($user, $chat);

         // Receiver participant
         /** @var User $receiver */
         $receiver = User::findByPhone($data['receiver']);
         /** @var RealtimeParticipant $receiverParticipant */
         $receiverParticipant = RealtimeParticipant::findOrCreate($receiver, $chat);

         // Create message
         /** @var RealtimeMessage $message */
         $message = self::store([
            'uuid' => generate_uuid('realtime_messages'),
            'message' => $data['message'] ?? '',

            'chat_id' => $chat->{'id'},
            'emitter_participant_id' => $emitter->{'id'},
            'receiver_participant_id' => $receiverParticipant->{'id'},
            'respond_to_message_id' => null,
            "sent_at" => now(),
         ]);

         return $message;
      } catch (\Exception $exception) {
         log_debug($exception, 'ChatMessage::createNewMessage Unable to create new message');
      }

      return null;
   }

   /**
    * Mark message as read
    *
    * @return RealtimeMessage|null
    */
   public function markAsDelivered(): ?RealtimeMessage
   {
      DB::beginTransaction();

      try {
         $this->deliveredBy();

         DB::commit();

         return $this->refresh();
      } catch (\Exception $exception) {
         DB::rollBack();
         log_debug($exception, 'RealtimeMessageService::markAsDelivered Unable to deliver realtime message');
      }

      return null;
   }

   /**
    * Delivered by user
    *
    * @return void
    */
   public function deliveredBy(): void
   {
      /** @var User $user */
      $user = auth()->user();

      $lastDelivery = MessageDeliveryHistory::lastMessageDelivery($this, $user);
      if ($lastDelivery === null) {
         MessageDeliveryHistory::store([
            "delivered_at" => now(),
            'message_id' => $this->{'id'},
            'participant_id' => $user->{'participant'}->{'id'}
         ]);
      }
   }

   /**
    * Read messages
    *
    * @param array $uuids
    * @return void
    */
   public static function markMessagesAsRead(array $uuids): void
   {
      foreach ($uuids as $uuid) {
         try {
            /** @var ?RealtimeMessage $message */
            $message = self::findByUuid($uuid);

            if($message === null) {
               throw new Exception();
            }

            $message->readBy();

            // Message broadcasting for delivery ...
            $message->seen();
         } catch (\Exception $exception) {
            log_debug($exception, 'RealtimeMessageService::markMessagesAsRead Unable to read message ' . $uuid);
         }
      }
   }

   /**
    * Mas as read for user
    *
    * @return void
    */
   public function readBy(): void
   {
      /** @var User $user */
      $user = auth()->user();

      $lastView = MessageViewHistory::lastMessageViews($this, $user);
      if ($lastView === null) {
         MessageViewHistory::store([
            "seen_at" => now(),
            'message_id' => $this->{'id'},
            'participant_id' => $user->{'participant'}->{'id'}
         ]);
      }
   }
}
