<?php

namespace App\Http\Controllers\Meet;

use App\Http\Controllers\Controller;
use App\Models\BreakoutRoom;
use App\Models\MeetPoll;
use App\Models\Participant;
use App\Models\QaQuestion;
use App\Models\Session;
use App\Models\SessionFile;
use App\Models\Whiteboard;
use App\Services\Meet\BreakoutService;
use App\Services\Meet\FileSharingService;
use App\Services\Meet\LiveStreamService;
use App\Services\Meet\PollService;
use App\Services\Meet\QaService;
use App\Services\Meet\SessionService;
use App\Services\Meet\SpaceService;
use App\Services\Meet\StageLayoutService;
use App\Services\Meet\TownHallService;
use App\Services\Meet\WhiteboardService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;

class MeetingFeaturesController extends Controller
{
    public function __construct(
        protected QaService $qa,
        protected PollService $polls,
        protected BreakoutService $breakouts,
        protected FileSharingService $files,
        protected WhiteboardService $whiteboards,
        protected LiveStreamService $liveStreams,
        protected TownHallService $townHall,
        protected StageLayoutService $stageLayout,
        protected SessionService $sessions,
    ) {}

    // --- Q&A ---

    public function submitQuestion(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'qa');
        $participant = $this->participant($request, $session);
        $validated = $request->validate(['question' => ['required', 'string', 'max:1000']]);
        $question = $this->qa->submit($session, $participant, $validated['question']);

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

    public function approveQuestion(Request $request, Session $session, QaQuestion $question): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'qa');
        $this->hostOnly($request, $session);
        abort_unless($question->session_id === $session->id, 404);

        return response()->json(['question' => $this->qa->approve($question)]);
    }

    public function answerQuestion(Request $request, Session $session, QaQuestion $question): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'qa');
        $participant = $this->hostOnly($request, $session);
        abort_unless($question->session_id === $session->id, 404);
        $validated = $request->validate(['answer' => ['required', 'string', 'max:2000']]);

        return response()->json([
            'question' => $this->qa->answer($question, $validated['answer'], $participant->display_name),
        ]);
    }

    public function upvoteQuestion(Request $request, Session $session, QaQuestion $question): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'qa');
        $this->participant($request, $session);
        abort_unless($question->session_id === $session->id, 404);

        return response()->json(['question' => $this->qa->upvote($question)]);
    }

    // --- Polls ---

    public function createPoll(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'polls');
        $this->hostOnly($request, $session);
        $validated = $request->validate([
            'question' => ['required', 'string', 'max:500'],
            'options' => ['required', 'array', 'min:2', 'max:6'],
            'options.*' => ['required', 'string', 'max:200'],
        ]);

        $poll = $this->polls->create($session, $validated['question'], $validated['options']);

        return response()->json(['poll' => $poll], 201);
    }

    public function votePoll(Request $request, Session $session, MeetPoll $poll): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'polls');
        $participant = $this->participant($request, $session);
        abort_unless($poll->session_id === $session->id, 404);
        $validated = $request->validate(['option_index' => ['required', 'integer', 'min:0']]);
        $this->polls->vote($poll, $participant, $validated['option_index']);

        return response()->json($this->polls->results($poll));
    }

    public function closePoll(Request $request, Session $session, MeetPoll $poll): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'polls');
        $this->hostOnly($request, $session);
        abort_unless($poll->session_id === $session->id, 404);

        return response()->json($this->polls->results($this->polls->close($poll)));
    }

    // --- Breakouts ---

    public function createBreakouts(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'breakouts');
        $this->hostOnly($request, $session);
        $validated = $request->validate([
            'count' => ['required', 'integer', 'min:1', 'max:20'],
            'timer_minutes' => ['nullable', 'integer', 'min:5', 'max:120'],
        ]);

        $rooms = $this->breakouts->createRooms(
            $session,
            $validated['count'],
            $validated['timer_minutes'] ?? null,
        );

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

    public function moveToBreakout(Request $request, Session $session, BreakoutRoom $breakout): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'breakouts');
        $participant = $this->participant($request, $session);
        abort_unless($breakout->session_id === $session->id, 404);
        $this->breakouts->moveParticipant($participant, $breakout);

        return response()->json(['breakout_uuid' => $breakout->uuid, 'media_room' => $breakout->media_room_name]);
    }

    public function returnFromBreakout(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'breakouts');
        $participant = $this->participant($request, $session);
        $this->breakouts->returnToMain($participant);

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

    public function broadcastBreakout(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'breakouts');
        $participant = $this->hostOnly($request, $session);
        $validated = $request->validate(['message' => ['required', 'string', 'max:1000']]);
        $message = $this->breakouts->broadcast($session, $validated['message'], $participant->display_name);

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

    public function closeBreakouts(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'breakouts');
        $this->hostOnly($request, $session);
        $this->breakouts->closeAll($session);

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

    // --- Files ---

    public function uploadFile(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'files');
        $participant = $this->participant($request, $session);
        $validated = $request->validate(['file' => ['required', 'file', 'max:51200']]);

        $file = $this->files->upload(
            $session->room,
            $session,
            $validated['file'],
            $participant->user_ref ?? $participant->uuid,
            $participant->display_name,
        );

        return response()->json(['file' => $file], 201);
    }

    public function downloadFile(Request $request, Session $session, SessionFile $file): StreamedResponse|JsonResponse
    {
        $participant = $this->participant($request, $session);
        abort_unless($file->room_id === $session->room_id, 404);

        if ($file->drive_file_id) {
            return response()->json(['url' => $this->files->download($file, $participant)]);
        }

        $path = $this->files->download($file, $participant);

        return Storage::disk(config('meet.files.disk', 'local'))->download($file->storage_path, $file->original_name);
    }

    // --- Whiteboard ---

    public function showWhiteboard(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'whiteboard');
        $this->participant($request, $session);
        $board = $this->whiteboards->ensureForSession($session);

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

    public function saveWhiteboard(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'whiteboard');
        $this->participant($request, $session);
        $validated = $request->validate(['state' => ['required', 'array']]);
        $board = $this->whiteboards->ensureForSession($session);

        return response()->json(['whiteboard' => $this->whiteboards->saveState($board, $validated['state'])]);
    }

    public function exportWhiteboard(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'whiteboard');
        $this->participant($request, $session);
        $validated = $request->validate(['image_data' => ['required', 'string']]);
        $data = preg_replace('#^data:image/\w+;base64,#i', '', $validated['image_data']);
        $binary = base64_decode($data, true);
        abort_unless($binary !== false, 422);

        $path = 'whiteboards/'.$session->uuid.'/'.now()->timestamp.'.png';
        Storage::disk(config('meet.files.disk', 'local'))->put($path, $binary);

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

    // --- Live streaming ---

    public function configureLiveStream(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'live_stream');
        $this->hostOnly($request, $session);
        $validated = $request->validate([
            'platform' => ['required', 'string', 'in:'.implode(',', config('meet.live_stream.platforms'))],
            'rtmp_url' => ['nullable', 'url', 'max:2048'],
            'stream_key' => ['required', 'string', 'max:512'],
            'layout' => ['nullable', 'string', 'in:'.implode(',', config('meet.live_stream.layouts'))],
        ]);

        $stream = $this->liveStreams->configure($session, $request->user(), $validated);

        return response()->json(['stream' => $stream->makeHidden('stream_key')], 201);
    }

    public function startLiveStream(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'live_stream');
        $this->hostOnly($request, $session);
        $validated = $request->validate([
            'platform' => ['required', 'string', 'in:'.implode(',', config('meet.live_stream.platforms'))],
        ]);

        $stream = $session->liveStreams()->where('platform', $validated['platform'])->firstOrFail();
        $started = $this->liveStreams->start($stream);

        return response()->json(['stream' => $started->makeHidden('stream_key')]);
    }

    public function stopLiveStream(Request $request, Session $session): JsonResponse
    {
        $this->abortUnlessRoomFeature($session, 'live_stream');
        $this->hostOnly($request, $session);
        $validated = $request->validate([
            'platform' => ['required', 'string', 'in:'.implode(',', config('meet.live_stream.platforms'))],
        ]);

        $stream = $session->liveStreams()->where('platform', $validated['platform'])->firstOrFail();

        return response()->json(['stream' => $this->liveStreams->stop($stream)->makeHidden('stream_key')]);
    }

    // --- Town hall ---

    public function startGreenRoom(Request $request, Session $session): JsonResponse
    {
        $this->hostOnly($request, $session);
        abort_unless($session->room->isTownHall(), 422);

        $greenRoom = $this->townHall->startGreenRoom($session->room, $request->user());

        return response()->json(['session_uuid' => $greenRoom->uuid, 'mode' => 'green_room']);
    }

    public function goLiveTownHall(Request $request, Session $session): JsonResponse
    {
        $hostParticipant = $this->hostOnly($request, $session);

        $user = $request->user()
            ?? ($hostParticipant->user_ref ? \App\Models\User::where('public_id', $hostParticipant->user_ref)->first() : null);
        abort_unless($user, 403);

        $live = $this->townHall->goLive($session, $user);

        $this->sessions->ensureJoinedParticipant(
            $request,
            $live,
            $user,
            'host',
            $hostParticipant->display_name,
            $hostParticipant->email,
        );

        $request->session()->forget("meet.participant.{$session->uuid}");

        return response()->json([
            'session_uuid' => $live->uuid,
            'mode' => 'live',
            'room_url' => route('meet.room', $live),
        ]);
    }

    public function addPresenter(Request $request, Session $session): JsonResponse
    {
        $this->hostOnly($request, $session);
        abort_unless($session->room->isTownHall(), 422);

        $validated = $request->validate(['user_ref' => ['required', 'string', 'max:64']]);

        $updated = $this->townHall->addPresenter($session, $validated['user_ref']);

        return response()->json([
            'session' => $updated,
            'presenter_refs' => $updated->presenter_refs,
        ]);
    }

    public function handoffCoHost(Request $request, Session $session): JsonResponse
    {
        $participant = $this->hostOnly($request, $session);
        $validated = $request->validate(['user_ref' => ['required', 'string', 'max:64']]);

        return response()->json([
            'participant' => $this->townHall->handoffCoHost($session, $participant, $validated['user_ref']),
        ]);
    }

    public function updateStage(Request $request, Session $session): JsonResponse
    {
        $this->hostOnly($request, $session);
        $room = $session->room;
        abort_unless($this->stageLayout->usesStageLayout($room), 422);

        $validated = $request->validate([
            'panel_discussions' => ['sometimes', 'boolean'],
            'stage_layout' => ['sometimes', 'string', 'in:plenary,panel'],
            'active_panel_id' => ['nullable', 'string', 'max:64'],
            'spotlight_speaker_ref' => ['nullable', 'string', 'max:64'],
            'panels' => ['sometimes', 'array', 'max:12'],
            'panels.*.id' => ['nullable', 'string', 'max:64'],
            'panels.*.name' => ['required_with:panels', 'string', 'max:100'],
            'panels.*.speaker_refs' => ['nullable', 'array'],
            'panels.*.speaker_refs.*' => ['string', 'max:64'],
        ]);

        $room = $this->stageLayout->update($room, $validated);

        return response()->json($this->stageLayout->config($room));
    }

    protected function participant(Request $request, Session $session): Participant
    {
        $uuid = $request->session()->get("meet.participant.{$session->uuid}");
        abort_unless($uuid, 403);

        return Participant::where('uuid', $uuid)->where('session_id', $session->id)->firstOrFail();
    }

    protected function hostOnly(Request $request, Session $session): Participant
    {
        $participant = $this->participant($request, $session);
        $user = $request->user();
        $isRoomHost = $user && $user->ownerRef() === $session->room->host_user_ref;

        abort_unless($participant->isHost() || $isRoomHost, 403);

        if ($isRoomHost && ! $participant->isHost()) {
            $participant->update(['role' => 'host']);

            return $participant->fresh();
        }

        return $participant;
    }

    protected function abortUnlessRoomFeature(Session $session, string $feature): void
    {
        abort_unless(
            app(SpaceService::class)->allowsRoomFeature($session->room, $feature),
            422,
            'This feature is not available in audio rooms.',
        );
    }
}
