<?php

namespace App\Http\Controllers\Meet;

use App\Http\Controllers\Controller;
use App\Models\Participant;
use App\Models\Room;
use App\Models\Session;
use App\Services\Integrations\EventsClient;
use App\Services\Meet\ConferenceService;
use App\Services\Meet\InvitationService;
use App\Services\Meet\SecurityPolicyService;
use App\Services\Meet\SessionService;
use App\Services\Meet\SpaceService;
use App\Support\EventsRegistrationSession;
use App\Support\EventsSourceLink;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class JoinController extends Controller
{
    public function __construct(
        protected SessionService $sessions,
        protected SecurityPolicyService $security,
        protected SpaceService $spaces,
        protected ConferenceService $conferences,
        protected EventsClient $events,
    ) {}

    public function show(Request $request, Room $room): View|RedirectResponse
    {
        $room->loadMissing('organization');

        if ($room->status === 'cancelled') {
            return view('meet.join.cancelled', [
                'room' => $room,
                'organization' => $room->organization,
            ]);
        }

        if ($room->isWebinar() && $request->query('token')) {
            $request->session()->put("meet.webinar.{$room->uuid}", $request->query('token'));
        }

        [$allowed, $reason] = $this->security->canJoin($room, $request->user(), $request->user()?->email);
        if (! $allowed && $reason !== 'passcode_required') {
            return view('meet.join.denied', [
                'room' => $room,
                'organization' => $room->organization,
                'reason' => $reason,
            ]);
        }

        if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) {
            return view('meet.join.passcode', [
                'room' => $room,
                'organization' => $room->organization,
            ]);
        }

        $speakerToken = trim((string) $request->query('speaker', ''));
        if ($speakerToken !== '' && $this->acceptsEventsSpeakerToken($room, $request)) {
            if ($this->verifyAndStoreSpeaker($request, $room, $speakerToken)) {
                $session = $room->activeSession();
                if ($session) {
                    $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
                    $participant = $participantUuid
                        ? Participant::where('uuid', $participantUuid)->where('session_id', $session->id)->first()
                        : null;

                    if ($participant?->status === 'joined') {
                        $this->sessions->syncEventsSpeakerRole($request, $participant);

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

                return redirect()->route('meet.join', $room);
            }

            return $this->gateView($request, $room)->withErrors([
                'speaker' => 'Speaker invitation link is invalid or expired.',
            ]);
        }

        $session = $room->activeSession();
        if ($session) {
            $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
            $participant = $participantUuid
                ? Participant::where('uuid', $participantUuid)->where('session_id', $session->id)->first()
                : null;

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

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

        $badge = trim((string) $request->query('badge', ''));
        if ($badge !== '' && $this->requiresEventsRegistration($room, $request)) {
            if ($this->verifyAndStoreBadge($request, $room, $badge)) {
                return redirect()->route('meet.join', $room);
            }

            return $this->gateView($request, $room)->withErrors([
                'badge_code' => 'Badge code not found or registration is not confirmed.',
            ]);
        }

        if ($this->requiresEventsRegistration($room, $request)) {
            return $this->gateView($request, $room);
        }

        return $this->displayNameView($request, $room);
    }

    public function verifyBadge(Request $request, Room $room): RedirectResponse
    {
        if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) {
            return redirect()->route('meet.join', $room);
        }

        $request->validate(['badge_code' => ['required', 'string', 'max:16']]);

        if ($this->verifyAndStoreBadge($request, $room, $request->input('badge_code'))) {
            return redirect()->route('meet.join', $room);
        }

        return back()->withErrors([
            'badge_code' => 'Badge code not found or registration is not confirmed.',
        ]);
    }

    public function verifyPasscode(Request $request, Room $room): RedirectResponse
    {
        $request->validate(['passcode' => ['required', 'string']]);

        if ($room->passcode !== $request->input('passcode')) {
            return back()->withErrors(['passcode' => 'Incorrect passcode.']);
        }

        $request->session()->put("meet.passcode.{$room->uuid}", true);

        return redirect()->route('meet.join', $room);
    }

    public function enter(Request $request, Room $room): RedirectResponse
    {
        if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) {
            return redirect()->route('meet.join', $room);
        }

        $speakerToken = trim((string) $request->query('speaker', ''));
        if ($speakerToken !== '' && $this->acceptsEventsSpeakerToken($room, $request)) {
            $this->verifyAndStoreSpeaker($request, $room, $speakerToken);
        }

        if ($this->requiresEventsRegistration($room, $request)) {
            return redirect()->route('meet.join', $room);
        }

        $rules = [
            'email' => ['nullable', 'email'],
        ];

        if ($request->user()) {
            $rules['display_name'] = ['nullable', 'string', 'max:100'];
        } else {
            $rules['display_name'] = ['required', 'string', 'max:100'];
        }

        try {
            $validated = $request->validate($rules);
        } catch (ValidationException $e) {
            throw $e->redirectTo(route('meet.join', $room));
        }

        $user = $request->user();
        $registration = EventsRegistrationSession::get($request, $room);
        $speakerVerified = EventsRegistrationSession::isSpeakerVerified($request, $room);
        $displayName = $user?->name ?? ($validated['display_name'] ?? ($registration['name'] ?? 'Guest'));
        $email = $user?->email ?? ($registration['email'] ?? null);

        if ($speakerVerified && filled($registration['email'] ?? null)) {
            $email = (string) $registration['email'];
            $displayName = (string) ($registration['name'] ?? $displayName);
        }

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

        [$allowed, $reason] = $this->security->canJoin($room, $user, $email ?? $displayName.'@guest.local');
        if (! $allowed && $reason !== 'passcode_required') {
            return back()->withErrors(['join' => $reason ?? "Unable to join {$kind}."]);
        }

        try {
            if ($room->isLive() && $room->activeSession()) {
                $session = $room->activeSession();
            } elseif ($room->isTownHall() && $user && $user->ownerRef() === $room->host_user_ref && $room->setting('green_room')) {
                $session = app(\App\Services\Meet\TownHallService::class)->startGreenRoom($room, $user);
            } elseif ($user && $user->ownerRef() === $room->host_user_ref) {
                $session = $this->sessions->start($room, $user);
            } elseif ($room->setting('join_before_host') || ($user && $user->ownerRef() === $room->host_user_ref)) {
                if (! $room->activeSession() && $user && $user->ownerRef() === $room->host_user_ref) {
                    $session = $this->sessions->start($room, $user);
                } elseif (! $room->activeSession()) {
                    return back()->withErrors(['join' => ucfirst($kind).' has not started yet. Please wait for the host.']);
                } else {
                    $session = $room->activeSession();
                }
            } else {
                if (! $room->activeSession()) {
                    return back()->withErrors(['join' => ucfirst($kind).' has not started yet. Please wait for the host.']);
                }

                $session = $room->activeSession();
            }
        } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
            if (in_array($e->getStatusCode(), [402, 503], true)) {
                return back()->withErrors(['join' => $e->getMessage() ?: "Unable to start {$kind}."]);
            }

            throw $e;
        }

        $role = ($user && $user->ownerRef() === $room->host_user_ref) ? 'host' : 'guest';

        if ($role !== 'host' && EventsRegistrationSession::isSpeakerVerified($request, $room)) {
            $role = 'panelist';
        } elseif ($room->isSpace()) {
            $role = $this->spaces->resolveJoinRole($room, $user, $role);
        } elseif ($room->isConference()) {
            $role = $this->conferences->resolveJoinRole($room, $user, $email, $role);
        } elseif ($room->isWebinar()) {
            if ($room->isEventsLinked()) {
                $role = 'attendee';
            } else {
                return back()->withErrors([
                    'join' => 'This webinar must be linked to a Ladill Events event before attendees can register and join.',
                ]);
            }
        }

        if ($room->isConference() && ! $this->conferences->canJoinSession($room, $session, $role)) {
            return back()->withErrors([
                'join' => 'The conference has not gone live yet. Assigned speakers can join from the green room link when the host starts.',
            ]);
        }

        $joinStatus = $room->requiresWaitingRoom($user, $role) ? 'waiting' : 'joined';

        $participant = $this->sessions->joinParticipant($session, $user, $role, $displayName, $email, $joinStatus);

        if ($room->isConference() && $session->session_mode === 'green_room') {
            $this->conferences->registerPresenter($session, $user, $role);
        }

        app(InvitationService::class)->markAcceptedOnJoin($room, $user, $email);

        $this->sessions->rememberParticipantSession($request, $participant, $session);

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

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

    public function waiting(Request $request, Room $room): View|RedirectResponse
    {
        $room->loadMissing('organization');

        $session = $room->activeSession() ?? $this->participantSessionForRoom($request, $room);

        if (! $session || ! $session->isLive()) {
            if ($session) {
                return redirect()->route('meet.ended', $session);
            }

            return redirect()->route('meet.join', $room);
        }

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

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

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

        return view('meet.join.waiting', [
            'room' => $room,
            'session' => $session,
            'organization' => $room->organization,
            'participant' => $participant,
        ]);
    }

    public function waitingStatus(Request $request, Room $room): JsonResponse
    {
        $session = $room->activeSession() ?? $this->participantSessionForRoom($request, $room);

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

            abort(404);
        }

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

        return response()->json([
            'status' => $participant?->status ?? 'unknown',
            'redirect' => ($participant && $participant->status === 'joined')
                ? route('meet.room', $session)
                : null,
            'denied' => $participant?->status === 'removed',
        ]);
    }

    protected function participantFromSession(Request $request, Session $session): ?Participant
    {
        $participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
        if (! $participantUuid) {
            return null;
        }

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

    protected function participantSessionForRoom(Request $request, Room $room): ?Session
    {
        foreach ($room->sessions()->orderByDesc('started_at')->get() as $session) {
            if ($request->session()->has("meet.participant.{$session->uuid}")) {
                return $session;
            }
        }

        return null;
    }

    protected function isRoomHost(Request $request, Room $room): bool
    {
        $user = $request->user();

        return $user && $user->ownerRef() === $room->host_user_ref;
    }

    protected function requiresEventsRegistration(Room $room, Request $request): bool
    {
        if (! $room->isEventsLinked()) {
            return false;
        }

        if ($this->isRoomHost($request, $room)) {
            return false;
        }

        if (! $room->isWebinar() && ! $room->isConference()) {
            return false;
        }

        return ! EventsRegistrationSession::canJoinEventsLinked($request, $room);
    }

    protected function acceptsEventsSpeakerToken(Room $room, Request $request): bool
    {
        if (! $room->isEventsLinked() || $this->isRoomHost($request, $room)) {
            return false;
        }

        return $room->isWebinar() || $room->isConference();
    }

    protected function verifyAndStoreSpeaker(Request $request, Room $room, string $speakerToken): bool
    {
        $eventId = EventsSourceLink::eventId($room);
        if (! $eventId || ! $this->events->isConfigured()) {
            return false;
        }

        $speaker = $this->events->verifySpeakerInvite($eventId, $room->owner_ref, $speakerToken);
        if (! $speaker) {
            return false;
        }

        EventsRegistrationSession::store($request, $room, [
            'email' => $speaker['email'] ?? null,
            'name' => $speaker['name'] ?? null,
            'speaker_token' => $speakerToken,
        ]);

        return EventsRegistrationSession::isSpeakerVerified($request, $room);
    }

    protected function verifyAndStoreBadge(Request $request, Room $room, string $badgeCode): bool
    {
        $eventId = EventsSourceLink::eventId($room);
        if (! $eventId || ! $this->events->isConfigured()) {
            return false;
        }

        $registration = $this->events->verifyRegistration($eventId, $room->owner_ref, $badgeCode);
        if (! $registration) {
            return false;
        }

        EventsRegistrationSession::store($request, $room, [
            'email' => $registration['attendee_email'] ?? null,
            'name' => $registration['attendee_name'] ?? null,
            'badge_code' => $registration['badge_code'] ?? $badgeCode,
        ]);

        return EventsRegistrationSession::isVerified($request, $room);
    }

    protected function gateView(Request $request, Room $room): View
    {
        $meetReturn = EventsSourceLink::meetReturnUrl($room);
        $registrationUrl = EventsSourceLink::eventRegistrationUrl($room, $meetReturn);
        $sessionNoun = $room->isConference() ? 'conference' : 'webinar';

        return view('meet.join.gate', [
            'room' => $room,
            'organization' => $room->organization,
            'eventTitle' => EventsSourceLink::eventTitle($room),
            'registrationUrl' => $registrationUrl,
            'sessionNoun' => $sessionNoun,
        ]);
    }

    protected function displayNameView(Request $request, Room $room): View
    {
        $registration = EventsRegistrationSession::get($request, $room);
        $sessionNoun = $room->isConference() ? 'conference' : ($room->isWebinar() ? 'webinar' : 'meeting');

        return view('meet.join.show', [
            'room' => $room,
            'organization' => $room->organization,
            'user' => $request->user(),
            'isWebinar' => $room->isWebinar(),
            'sessionNoun' => $sessionNoun,
            'defaultDisplayName' => $registration['name'] ?? '',
        ]);
    }
}
