<?php

namespace App\Http\Controllers\Api\Messaging;

use App\Http\Controllers\Controller;
use App\Models\ConversationParticipant;
use App\Models\Message;
use App\Services\Messaging\MessageSearchIndexer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Meilisearch\Client;

class MessageSearchController extends Controller
{
    public function __construct(
        private readonly MessageSearchIndexer $indexer,
    ) {}

    public function index(Request $request): JsonResponse
    {
        $user = $request->user();
        $data = $request->validate([
            'q' => 'required|string|min:1|max:200',
            'conversation_id' => 'nullable|integer|exists:conversations,id',
            'cursor' => 'nullable|integer|min:0',
        ]);

        $allowedConversationIds = ConversationParticipant::query()
            ->where('user_id', $user->id)
            ->whereNull('left_at')
            ->pluck('conversation_id')
            ->map(fn ($id) => (int) $id)
            ->all();

        $conversationId = isset($data['conversation_id']) ? (int) $data['conversation_id'] : null;
        if ($conversationId !== null && ! in_array($conversationId, $allowedConversationIds, true)) {
            abort(403, 'You are not a participant of this conversation.');
        }

        if (empty($allowedConversationIds)) {
            return response()->json(['data' => [], 'next_cursor' => null]);
        }

        $limit = max(1, (int) config('messaging.search.page_size', 20));
        $offset = max(0, (int) ($data['cursor'] ?? 0));

        $hits = collect();
        $estimated = 0;

        try {
            $client = new Client(
                config('scout.meilisearch.host'),
                config('scout.meilisearch.key')
            );

            $prefix = (string) config('scout.prefix', '');
            $indexName = $prefix . (string) config('messaging.search.index', 'messages');

            $conversationFilter = $conversationId !== null
                ? "conversation_id = {$conversationId}"
                : 'conversation_id IN [' . implode(',', $allowedConversationIds) . ']';

            $result = $client
                ->index($indexName)
                ->search((string) $data['q'], [
                    'limit' => $limit,
                    'offset' => $offset,
                    'sort' => ['created_at:desc'],
                    'filter' => $conversationFilter,
                ]);

            $hits = collect($result->getHits() ?? []);
            $estimated = (int) ($result->getEstimatedTotalHits() ?? $hits->count());

            if ($hits->isEmpty()) {
                [$hits, $estimated] = $this->fallbackHits($allowedConversationIds, $conversationId, (string) $data['q'], $offset, $limit);
            }
        } catch (\Throwable) {
            [$hits, $estimated] = $this->fallbackHits($allowedConversationIds, $conversationId, (string) $data['q'], $offset, $limit);
        }
        $messageIds = $hits->pluck('id')->map(fn ($id) => (int) $id)->all();

        $messages = Message::query()
            ->whereIn('id', $messageIds)
            ->whereIn('conversation_id', $allowedConversationIds)
            ->whereNull('deleted_at')
            ->with(['sender:id,username', 'attachments'])
            ->get()
            ->keyBy('id');

        $ordered = $hits
            ->map(function (array $hit) use ($messages) {
                $message = $messages->get((int) ($hit['id'] ?? 0));
                if (! $message) {
                    return null;
                }

                return [
                    'id' => $message->id,
                    'conversation_id' => $message->conversation_id,
                    'sender_id' => $message->sender_id,
                    'sender' => $message->sender,
                    'body' => $message->body,
                    'created_at' => optional($message->created_at)?->toISOString(),
                    'has_attachments' => $message->attachments->isNotEmpty(),
                ];
            })
            ->filter()
            ->values();

        $nextCursor = ($offset + $limit) < $estimated ? ($offset + $limit) : null;

        return response()->json([
            'data' => $ordered,
            'next_cursor' => $nextCursor,
        ]);
    }

    private function fallbackHits(array $allowedConversationIds, ?int $conversationId, string $queryString, int $offset, int $limit): array
    {
        $query = Message::query()
            ->select('id')
            ->whereNull('deleted_at')
            ->whereIn('conversation_id', $allowedConversationIds)
            ->when($conversationId !== null, fn ($builder) => $builder->where('conversation_id', $conversationId))
            ->where('body', 'like', '%' . $queryString . '%')
            ->orderByDesc('created_at')
            ->orderByDesc('id');

        $estimated = (clone $query)->count();
        $hits = $query->offset($offset)->limit($limit)->get()->map(fn ($row) => ['id' => (int) $row->id]);

        return [$hits, $estimated];
    }

    public function rebuild(Request $request): JsonResponse
    {
        abort_unless($request->user()?->isAdmin(), 403, 'Admin access required.');

        $conversationId = $request->integer('conversation_id');
        if ($conversationId > 0) {
            $this->indexer->rebuildConversation($conversationId);
            return response()->json(['queued' => true, 'scope' => 'conversation']);
        }

        $this->indexer->rebuildAll();

        return response()->json(['queued' => true, 'scope' => 'all']);
    }
}
