<?php

namespace App\Services\Messaging;

use App\Events\ConversationUpdated;
use App\Events\MessageCreated;
use App\Models\Conversation;
use App\Models\Message;
use App\Models\MessageAttachment;
use App\Models\User;
use App\Services\Messaging\ConversationStateService;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class SendMessageAction
{
    public function __construct(
        private readonly ConversationStateService $conversationState,
        private readonly MessageNotificationService $notifications,
        private readonly MessageSearchIndexer $searchIndexer,
    ) {}

    public function execute(Conversation $conversation, User $sender, array $payload): Message
    {
        $body = trim((string) ($payload['body'] ?? ''));
        $files = $payload['attachments'] ?? [];
        $clientTempId = $this->normalizedClientTempId($payload['client_temp_id'] ?? null);
        $created = false;

        /** @var Message $message */
        $message = DB::transaction(function () use ($conversation, $sender, $payload, $body, $files, $clientTempId, &$created) {
            $existing = $this->findExistingMessage($conversation, $sender, $clientTempId);

            if ($existing) {
                return $existing;
            }

            $message = Message::query()->create([
                'conversation_id' => $conversation->id,
                'sender_id' => $sender->id,
                'client_temp_id' => $clientTempId,
                'message_type' => empty($files) ? 'text' : ($body === '' ? 'attachment' : 'text'),
                'body' => $body,
                'reply_to_message_id' => $payload['reply_to_message_id'] ?? null,
            ]);

            foreach ($files as $file) {
                if ($file instanceof UploadedFile) {
                    $this->storeAttachment($file, $message, $sender->id);
                }
            }

            $conversation->forceFill([
                'last_message_id' => $message->id,
                'last_message_at' => $message->created_at,
            ])->save();

            $created = true;

            return $message;
        });

        if (! $created) {
            return $message->fresh(['sender:id,username,name', 'attachments', 'reactions']);
        }

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

        DB::afterCommit(function () use ($conversation, $message, $sender, $participantIds): void {
            $this->notifications->notifyNewMessage($conversation, $message, $sender);
            $this->searchIndexer->indexMessage($message);

            event(new MessageCreated($conversation, $message, $sender->id));

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

        return $message->fresh(['sender:id,username,name', 'attachments', 'reactions']);
    }

    private function findExistingMessage(Conversation $conversation, User $sender, ?string $clientTempId): ?Message
    {
        if ($clientTempId === null) {
            return null;
        }

        return Message::query()
            ->where('conversation_id', $conversation->id)
            ->where('sender_id', $sender->id)
            ->where('client_temp_id', $clientTempId)
            ->whereNull('deleted_at')
            ->first();
    }

    private function normalizedClientTempId(mixed $value): ?string
    {
        $normalized = trim((string) $value);

        return $normalized === '' ? null : $normalized;
    }

    private function storeAttachment(UploadedFile $file, Message $message, int $userId): void
    {
        $mime = (string) $file->getMimeType();
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $finfoMime = $finfo ? (string) finfo_file($finfo, $file->getPathname()) : '';
        if ($finfo) {
            finfo_close($finfo);
        }
        $detectedMime = $finfoMime !== '' ? $finfoMime : $mime;

        $allowedImage = (array) config('messaging.attachments.allowed_image_mimes', []);
        $allowedFile = (array) config('messaging.attachments.allowed_file_mimes', []);

        $type = in_array($detectedMime, $allowedImage, true) ? 'image' : 'file';
        $allowed = $type === 'image' ? $allowedImage : $allowedFile;

        abort_unless(in_array($detectedMime, $allowed, true), 422, 'Unsupported attachment type.');

        $maxBytes = $type === 'image'
            ? ((int) config('messaging.attachments.max_image_kb', 10240) * 1024)
            : ((int) config('messaging.attachments.max_file_kb', 25600) * 1024);

        abort_if($file->getSize() > $maxBytes, 422, 'Attachment exceeds allowed size.');

        $year = now()->format('Y');
        $month = now()->format('m');
        $ext = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'bin');
        $path = "messages/{$message->conversation_id}/{$year}/{$month}/" . uniqid('att_', true) . ".{$ext}";
        $diskName = (string) config('messaging.attachments.disk', 'local');

        Storage::disk($diskName)->put($path, file_get_contents($file->getPathname()));

        $width = null;
        $height = null;
        if ($type === 'image') {
            $dimensions = @getimagesize($file->getPathname());
            $width = isset($dimensions[0]) ? (int) $dimensions[0] : null;
            $height = isset($dimensions[1]) ? (int) $dimensions[1] : null;
        }

        MessageAttachment::query()->create([
            'message_id' => $message->id,
            'disk' => $diskName,
            'user_id' => $userId,
            'type' => $type,
            'mime' => $detectedMime,
            'size_bytes' => (int) $file->getSize(),
            'width' => $width,
            'height' => $height,
            'sha256' => hash_file('sha256', $file->getPathname()),
            'original_name' => substr((string) $file->getClientOriginalName(), 0, 255),
            'storage_path' => $path,
            'created_at' => now(),
        ]);
    }
}
