<?php

namespace App\Http\Controllers\Meet;

use App\Http\Controllers\Controller;
use App\Models\Participant;
use App\Models\Recording;
use App\Models\Room;
use App\Models\Session;
use App\Services\Meet\BreakoutService;
use App\Services\Meet\ChatService;
use App\Services\Meet\Media\MediaProviderInterface;
use App\Services\Meet\ParticipantPresenter;
use App\Services\Meet\RecordingService;
use App\Services\Meet\SecurityPolicyService;
use App\Services\Meet\SessionService;
use App\Services\Meet\SpaceService;
use App\Services\Meet\StageLayoutService;
use App\Services\Meet\TownHallService;
use App\Services\Meet\TranscriptService;
use App\Services\Meet\WebinarService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class MeetingRoomController extends Controller
{
    public function __construct(
        protected SessionService $sessions,
        protected ChatService $chat,
        protected MediaProviderInterface $media,
        protected RecordingService $recordings,
        protected TranscriptService $transcripts,
        protected SecurityPolicyService $security,
    ) {}

    public function show(Request $request, Session $session): View|RedirectResponse
    {
        if (! $session->isLive()) {
            return redirect()->route('meet.ended', $session);
        }

        $room = $session->room;
        $kind = $room->isConference() ? 'conference' : 'meeting';

        $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");

        if (! $participantUuid && $request->user() && $request->user()->ownerRef() === $room->host_user_ref) {
            $hostParticipant = $this->sessions->ensureJoinedParticipant($request, $session, $request->user(), 'host');
            $participantUuid = $hostParticipant->uuid;
        }

        abort_unless($participantUuid, 403, "Please join the {$kind} first.");

        $participant = Participant::where('uuid', $participantUuid)
            ->where('session_id', $session->id)
            ->firstOrFail();

        if ($participant->status === 'waiting') {
            return redirect()->route('meet.join.waiting', $session->room);
        }

        abort_unless($participant->status === 'joined', 403, "You are not in this {$kind}.");

        $participant = $this->sessions->syncEventsSpeakerRole($request, $participant);

        $token = $this->media->isConfigured()
            ? $this->sessions->generateMediaToken($participant)
            : '';

        $session->load(['room', 'participants']);

        $room = $session->room;
        $webinars = app(WebinarService::class);
        $spaces = app(SpaceService::class);
        $isAudioOnly = $room->isAudioOnly();
        $stageLayout = app(StageLayoutService::class);
        $authUser = $request->user();
        $isRoomHost = $authUser && $authUser->ownerRef() === $room->host_user_ref;
        $canManageConference = $participant->isHost() || $isRoomHost;
        $canManageSpeakAccess = app(\App\Services\Meet\SpeakAccessService::class)->canManageSpeakAccess($participant);
        $usesSpeakAccess = $room->isWebinar() || $room->isConference();
        $mediaState = $this->sessions->mediaSessionState($participant);
        $breakoutService = app(BreakoutService::class);
        $inBreakout = $breakoutService->isInActiveBreakout($participant);

        return view('meet.room.show', [
            'session' => $session,
            'room' => $room,
            'participant' => $participant,
            'mediaToken' => $token,
            'mediaUrl' => $this->media->serverUrl(),
            'mediaConfigured' => $this->media->isConfigured(),
            'messages' => $this->chat->recentMessages($session),
            'activeRecording' => $session->recordings()->where('status', 'recording')->first(),
            'watermark' => $room->organization?->securitySetting('watermark_enabled', false),
            'isWebinar' => $room->isWebinar() || $room->isConference(),
            'isConference' => $room->isConference(),
            'isGreenRoom' => $session->session_mode === 'green_room',
            'sessionMode' => $session->session_mode,
            'presenterRefs' => (array) $session->presenter_refs,
            'isAudioOnly' => $isAudioOnly,
            'isSpace' => $room->isSpace(),
            'canPublish' => $mediaState['can_publish'],
            'audioOnlyPublish' => $mediaState['audio_only_publish'],
            'speakGranted' => $mediaState['speak_granted'],
            'speakRequested' => $mediaState['speak_requested'],
            'usesSpeakAccess' => $usesSpeakAccess,
            'canManageSpeakAccess' => $canManageSpeakAccess,
            'isAttendee' => $participant->isAttendee() || $spaces->isListener($room, $participant),
            'usesStageLayout' => $stageLayout->usesStageLayout($room) && ! $inBreakout,
            'inBreakout' => $inBreakout,
            'stageConfig' => $stageLayout->config($room),
            'canManageConference' => $canManageConference,
            'programmeSnapshot' => $room->setting('programme_snapshot'),
            'linkedProgrammeItems' => (array) $room->setting('linked_programme_items', []),
        ]);
    }

    public function end(Request $request, Session $session): RedirectResponse
    {
        $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
        $participant = Participant::where('uuid', $participantUuid)->first();

        abort_unless($participant?->isHost(), 403);

        $this->sessions->end($session, $participant->user_ref ?? $participant->uuid);

        $room = $session->room;

        return redirect()->route($this->roomShowRoute($room), $room)
            ->with('success', $this->roomEndedMessage($room));
    }

    protected function roomShowRoute(Room $room): string
    {
        if ($room->isWebinar()) {
            return 'meet.webinars.show';
        }

        if ($room->isConference()) {
            return 'meet.conferences.show';
        }

        if ($room->isSpace()) {
            return 'meet.spaces.show';
        }

        return 'meet.rooms.show';
    }

    protected function roomEndedMessage(Room $room): string
    {
        if ($room->isWebinar()) {
            return 'Webinar ended.';
        }

        if ($room->isConference()) {
            return 'Conference ended.';
        }

        if ($room->isSpace()) {
            return 'Room closed.';
        }

        return 'Meeting ended.';
    }

    public function leave(Request $request, Session $session): RedirectResponse
    {
        $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
        $participant = Participant::where('uuid', $participantUuid)->first();

        if ($participant) {
            $this->sessions->leaveParticipant($participant);

            $request->session()->put('meet.left.feedback', [
                'session_uuid' => $session->uuid,
                'participant_uuid' => $participant->uuid,
                'display_name' => $participant->display_name,
                'user_ref' => $participant->user_ref,
            ]);
        }

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

        return redirect()->route('meet.left', $session);
    }

    public function raiseHand(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        $participant->update(['hand_raised' => ! $participant->hand_raised]);

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

    public function requestSpeak(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        $speak = app(\App\Services\Meet\SpeakAccessService::class);

        return response()->json([
            'participant' => $speak->requestSpeak($participant),
        ]);
    }

    public function cancelSpeakRequest(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        $speak = app(\App\Services\Meet\SpeakAccessService::class);

        return response()->json([
            'participant' => $speak->cancelSpeakRequest($participant),
        ]);
    }

    public function grantSpeak(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);

        $speak = app(\App\Services\Meet\SpeakAccessService::class);

        return response()->json([
            'participant' => $speak->grantSpeak($participant, $actor),
        ]);
    }

    public function revokeSpeak(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);

        $speak = app(\App\Services\Meet\SpeakAccessService::class);

        return response()->json([
            'participant' => $speak->revokeSpeak($participant, $actor),
        ]);
    }

    public function dismissSpeakRequest(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);

        $speak = app(\App\Services\Meet\SpeakAccessService::class);

        return response()->json([
            'participant' => $speak->dismissSpeakRequest($participant, $actor),
        ]);
    }

    public function inviteSpaceSpeaker(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);
        abort_unless($session->room->isSpace(), 404);
        abort_unless($actor->isHost() || ($request->user() && $request->user()->ownerRef() === $session->room->host_user_ref), 403);

        $updated = app(SpaceService::class)->inviteToSpeak($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function acceptSpaceSpeakInvitation(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        abort_unless($session->room->isSpace(), 404);

        $updated = app(SpaceService::class)->acceptSpeakInvitation($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function declineSpaceSpeakInvitation(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        abort_unless($session->room->isSpace(), 404);

        $updated = app(SpaceService::class)->declineSpeakInvitation($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function dismissSpaceSpeakInvitation(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);
        abort_unless($session->room->isSpace(), 404);
        abort_unless($actor->isHost() || ($request->user() && $request->user()->ownerRef() === $session->room->host_user_ref), 403);

        $updated = app(SpaceService::class)->dismissSpeakInvitation($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function promoteSpaceCoHost(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);
        abort_unless($session->room->isSpace(), 404);
        abort_unless($actor->isHost() || ($request->user() && $request->user()->ownerRef() === $session->room->host_user_ref), 403);

        $updated = app(SpaceService::class)->promoteToCoHost($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function demoteSpaceSpeaker(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $actor = $this->currentParticipant($request, $session);
        abort_unless($participant->session_id === $session->id, 404);
        abort_unless($session->room->isSpace(), 404);
        abort_unless($actor->isHost() || ($request->user() && $request->user()->ownerRef() === $session->room->host_user_ref), 403);

        $updated = app(SpaceService::class)->demoteFromSpeaker($session->room, $participant);
        $avatars = ParticipantPresenter::avatarMap($session->participants()->get(), $session->room->host_user_ref);

        return response()->json([
            'participant' => ParticipantPresenter::toArray($updated, $avatars, $session->room->host_user_ref),
        ]);
    }

    public function sendMessage(Request $request, Session $session): JsonResponse
    {
        abort_unless(app(SpaceService::class)->allowsRoomFeature($session->room, 'chat'), 422);

        $participant = $this->currentParticipant($request, $session);

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

        $message = $this->chat->sendPublicMessage(
            $session,
            $participant->display_name,
            $validated['body'],
            $participant->user_ref,
        );

        return response()->json([
            'message' => [
                'uuid' => $message->uuid,
                'sender_name' => $message->sender_name,
                'body' => $message->body,
                'created_at' => $message->created_at->toIso8601String(),
            ],
        ]);
    }

    public function sendReaction(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);

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

        $reaction = $this->chat->sendReaction(
            $session,
            $participant->display_name,
            $validated['emoji'],
            $participant->uuid,
        );

        return response()->json([
            'reaction' => [
                'sender_name' => $reaction->sender_name,
                'emoji' => $reaction->emoji,
                'created_at' => $reaction->created_at->toIso8601String(),
            ],
        ]);
    }

    public function poll(Request $request, Session $session): JsonResponse
    {
        if (! $session->isLive()) {
            $townHall = app(TownHallService::class);
            $liveSession = $townHall->successorLiveSession($session);

            if ($liveSession) {
                $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");

                if ($participantUuid) {
                    $greenParticipant = Participant::where('uuid', $participantUuid)
                        ->where('session_id', $session->id)
                        ->first();

                    if ($greenParticipant && $this->sessions->transitionGreenRoomParticipant($request, $greenParticipant, $liveSession)) {
                        return response()->json([
                            'ended' => true,
                            'redirect' => route('meet.room', $liveSession),
                            'transition' => 'go_live',
                        ]);
                    }
                }
            }

            return response()->json([
                'ended' => true,
                'redirect' => route('meet.ended', $session),
            ]);
        }

        $participant = $this->currentParticipant($request, $session);

        app(BreakoutService::class)->closeExpired($session);

        $participant->refresh();

        $session->load(['participants' => fn ($q) => $q->whereIn('status', ['joined', 'waiting'])]);

        $avatars = ParticipantPresenter::avatarMap($session->participants, $session->room->host_user_ref);

        $messages = $this->chat->recentMessages($session, 50);
        $reactions = $session->reactions()->where('created_at', '>=', now()->subMinutes(5))->latest()->limit(20)->get();
        $qa = app(\App\Services\Meet\QaService::class)->forSession($session, true);
        $polls = $session->polls()->where('status', 'open')->with('votes')->get();
        $breakouts = $session->breakoutRooms()->where('status', 'open')->get();
        $broadcasts = $session->messages()->where('type', 'breakout_broadcast')->where('created_at', '>=', now()->subMinutes(30))->latest()->limit(5)->get();
        $files = $session->sessionFiles()
            ->where(function ($q) {
                $q->whereNull('expires_at')->orWhere('expires_at', '>', now());
            })
            ->latest()
            ->limit(20)
            ->get();

        return response()->json(array_merge([
            'participants' => $session->participants->map(
                fn ($p) => ParticipantPresenter::toArray($p, $avatars, $session->room->host_user_ref)
            ),
            'waiting_count' => $session->participants->where('status', 'waiting')->count(),
            'messages' => $messages->map(fn ($m) => [
                'uuid' => $m->uuid,
                'sender_name' => $m->sender_name,
                'body' => $m->body,
                'type' => $m->type,
                'created_at' => $m->created_at->toIso8601String(),
            ]),
            'reactions' => $reactions->map(fn ($r) => [
                'sender_name' => $r->sender_name,
                'emoji' => $r->emoji,
                'created_at' => $r->created_at->toIso8601String(),
            ]),
            'qa' => $qa->map(fn ($q) => [
                'uuid' => $q->uuid,
                'asker_name' => $q->asker_name,
                'question' => $q->question,
                'status' => $q->status,
                'answer' => $q->answer,
                'upvotes' => $q->upvotes,
            ]),
            'polls' => $polls->map(fn ($p) => [
                'uuid' => $p->uuid,
                'question' => $p->question,
                'options' => $p->options,
                'votes' => $p->votes->count(),
            ]),
            'breakouts' => $breakouts->map(fn ($b) => [
                'uuid' => $b->uuid,
                'name' => $b->name,
                'closes_at' => $b->closes_at?->toIso8601String(),
            ]),
            'broadcasts' => $broadcasts->map(fn ($m) => [
                'body' => $m->body,
                'sender_name' => $m->sender_name,
                'created_at' => $m->created_at->toIso8601String(),
            ]),
            'files' => $files->map(fn ($f) => [
                'uuid' => $f->uuid,
                'original_name' => $f->original_name,
                'file_size' => $f->file_size,
            ]),
            'media' => $this->sessions->mediaSessionState($participant),
            'breakouts_active' => app(BreakoutService::class)->hasActiveBreakouts($session),
        ], app(StageLayoutService::class)->config($session->room)));
    }

    public function mediaToken(Request $request, Session $session): JsonResponse
    {
        abort_unless($session->isLive(), 404);

        $participant = $this->currentParticipant($request, $session);

        return response()->json(array_merge(
            ['token' => $this->sessions->generateMediaToken($participant)],
            $this->sessions->mediaSessionState($participant->fresh()),
            ['breakouts_active' => app(BreakoutService::class)->hasActiveBreakouts($session)],
        ));
    }

    public function startRecording(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        abort_unless($participant->isHost(), 403);
        abort_if($session->room->organization?->securitySetting('recording_host_only') && ! $participant->isHost(), 403);

        $host = $request->user() ?? \App\Models\User::where('public_id', $session->room->host_user_ref)->first();
        abort_unless($host, 403);

        $recording = $this->recordings->start($session, $host);

        return response()->json(['recording_uuid' => $recording->uuid, 'status' => $recording->status]);
    }

    public function stopRecording(Request $request, Session $session): JsonResponse
    {
        $participant = $this->recordingHost($request, $session);

        $recording = $session->recordings()->where('status', 'recording')->firstOrFail();
        $actorRef = $participant->user_ref ?? $participant->uuid;

        if ($request->boolean('failed')) {
            $recording = $this->recordings->fail(
                $recording,
                (string) $request->input('reason', 'Recording capture did not complete.'),
                $actorRef,
            );

            return response()->json([
                'status' => 'failed',
                'recording_uuid' => $recording->uuid,
            ]);
        }

        $recording = $this->recordings->stop($recording, $actorRef);

        return response()->json([
            'status' => 'processing',
            'recording_uuid' => $recording->uuid,
        ]);
    }

    public function failRecording(Request $request, Session $session, Recording $recording): JsonResponse
    {
        $participant = $this->recordingHost($request, $session);
        abort_unless($recording->session_id === $session->id, 404);
        abort_if(! in_array($recording->status, ['recording', 'processing'], true), 422);

        $recording = $this->recordings->fail(
            $recording,
            (string) $request->input('reason', 'Recording capture did not complete.'),
            $participant->user_ref ?? $participant->uuid,
        );

        return response()->json([
            'status' => $recording->status,
            'recording_uuid' => $recording->uuid,
        ]);
    }

    public function uploadRecording(Request $request, Session $session, Recording $recording): JsonResponse
    {
        $participant = $this->recordingHost($request, $session);
        abort_unless($recording->session_id === $session->id, 404);

        $maxKb = max(1, (int) config('meet.recordings.max_mb', 512)) * 1024;

        $request->validate([
            'recording' => ['required', 'file', 'mimetypes:video/webm,video/mp4,video/x-matroska,video/quicktime', 'max:'.$maxKb],
        ]);

        $this->recordings->storeUpload($recording, $request->file('recording'));

        return response()->json(['status' => 'ready', 'recording_uuid' => $recording->uuid]);
    }

    public function lock(Request $request, Session $session): JsonResponse
    {
        abort_unless(app(SpaceService::class)->allowsRoomFeature($session->room, 'lock'), 422);

        $participant = $this->currentParticipant($request, $session);
        abort_unless($participant->isHost(), 403);
        $this->sessions->lock($session, $participant->user_ref ?? $participant->uuid);

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

    public function unlock(Request $request, Session $session): JsonResponse
    {
        abort_unless(app(SpaceService::class)->allowsRoomFeature($session->room, 'lock'), 422);

        $participant = $this->currentParticipant($request, $session);
        abort_unless($participant->isHost(), 403);
        $this->sessions->unlock($session, $participant->user_ref ?? $participant->uuid);

        return response()->json(['locked' => false]);
    }

    public function caption(Request $request, Session $session): JsonResponse
    {
        $participant = $this->currentParticipant($request, $session);
        $validated = $request->validate(['text' => ['required', 'string', 'max:500']]);

        if (! $session->room->setting('live_captions', false)) {
            return response()->json(['ok' => false], 422);
        }

        $transcript = $session->transcripts()->latest()->first()
            ?? $this->transcripts->ensureForSession($session);

        $this->transcripts->appendSegment($transcript, $participant->display_name, $validated['text']);

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

    public function admit(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $current = $this->currentParticipant($request, $session);
        abort_unless($current->isHost(), 403);
        abort_unless($participant->session_id === $session->id, 404);

        $this->sessions->admitParticipant($participant);

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

    public function deny(Request $request, Session $session, Participant $participant): JsonResponse
    {
        $current = $this->currentParticipant($request, $session);
        abort_unless($current->isHost(), 403);
        abort_unless($participant->session_id === $session->id, 404);

        $this->sessions->denyParticipant($participant);

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

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

        return $this->sessions->syncEventsSpeakerRole(
            $request,
            Participant::where('uuid', $participantUuid)
                ->where('session_id', $session->id)
                ->firstOrFail()
        );
    }

    /** Host/co-host in the room session, or the room host after the meeting ended. */
    protected function recordingHost(Request $request, Session $session): Participant
    {
        $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
        if ($participantUuid) {
            $participant = Participant::query()
                ->where('uuid', $participantUuid)
                ->where('session_id', $session->id)
                ->first();

            if ($participant?->isHost()) {
                return $this->sessions->syncEventsSpeakerRole($request, $participant);
            }
        }

        $user = $request->user();
        abort_unless($user, 403);

        $hostParticipant = $session->participants()
            ->where('user_ref', $user->ownerRef())
            ->whereIn('role', ['host', 'co_host'])
            ->orderByRaw("case when role = 'host' then 0 else 1 end")
            ->first();

        abort_unless($hostParticipant, 403);

        return $hostParticipant;
    }
}
