<?php

namespace App\Http\Controllers\Api\Messaging;

use App\Events\TypingStarted;
use App\Events\TypingStopped;
use App\Http\Controllers\Controller;
use App\Models\Conversation;
use App\Models\ConversationParticipant;
use Illuminate\Cache\Repository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class TypingController extends Controller
{
    public function start(Request $request, int $conversationId): JsonResponse
    {
        $this->findConversationOrFail($conversationId);

        $ttl = max(5, (int) config('messaging.typing.ttl_seconds', 8));
        $this->store()->put($this->key($conversationId, (int) $request->user()->id), 1, now()->addSeconds($ttl));

        if ((bool) config('messaging.realtime', false)) {
            event(new TypingStarted($conversationId, $request->user()));
        }

        return response()->json(['ok' => true]);
    }

    public function stop(Request $request, int $conversationId): JsonResponse
    {
        $this->findConversationOrFail($conversationId);
        $this->store()->forget($this->key($conversationId, (int) $request->user()->id));

        if ((bool) config('messaging.realtime', false)) {
            event(new TypingStopped($conversationId, $request->user()));
        }

        return response()->json(['ok' => true]);
    }

    public function index(Request $request, int $conversationId): JsonResponse
    {
        $this->findConversationOrFail($conversationId);
        $userId = (int) $request->user()->id;

        $participants = ConversationParticipant::query()
            ->where('conversation_id', $conversationId)
            ->whereNull('left_at')
            ->where('user_id', '!=', $userId)
            ->with('user:id,username')
            ->get();

        $typing = $participants
            ->filter(fn ($p) => $this->store()->has($this->key($conversationId, (int) $p->user_id)))
            ->map(fn ($p) => [
                'user_id' => (int) $p->user_id,
                'username' => (string) ($p->user->username ?? ''),
            ])
            ->values();

        return response()->json(['typing' => $typing]);
    }

    private function assertParticipant(Request $request, int $conversationId): void
    {
        abort_unless(
            ConversationParticipant::query()
                ->where('conversation_id', $conversationId)
                ->where('user_id', $request->user()->id)
                ->whereNull('left_at')
                ->exists(),
            403,
            'You are not a participant of this conversation.'
        );
    }

    private function key(int $conversationId, int $userId): string
    {
        return "typing:{$conversationId}:{$userId}";
    }

    private function store(): Repository
    {
        $store = (string) config('messaging.typing.cache_store', 'redis');
        if ($store === 'redis' && ! class_exists('Redis')) {
            return Cache::store();
        }

        try {
            return Cache::store($store);
        } catch (\Throwable) {
            return Cache::store();
        }
    }

    private function findConversationOrFail(int $conversationId): Conversation
    {
        $conversation = Conversation::query()->findOrFail($conversationId);
        $this->authorize('view', $conversation);

        return $conversation;
    }
}
