<?php

namespace App\Services\Meet;

use App\Models\Participant;
use App\Models\Room;
use App\Models\Session;
use App\Models\User;
use App\Services\Meet\Media\MediaProviderInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class SessionService
{
    public function __construct(
        protected MediaProviderInterface $media,
        protected RoomService $rooms,
    ) {}

    public function start(Room $room, User $host): Session
    {
        $existing = $room->activeSession();
        if ($existing) {
            return $existing;
        }

        app(LicenseService::class)->assertCanStartSession($room->organization, $room->branch_id, $room);

        $session = Session::create([
            'owner_ref' => $room->owner_ref,
            'room_id' => $room->id,
            'media_room_name' => $this->rooms->mediaRoomName($room),
            'status' => 'live',
            'started_at' => now(),
        ]);

        $room->update(['status' => 'live']);

        $this->media->createRoom($session->media_room_name);

        $this->joinParticipant($session, $host, 'host');

        if ($room->setting('auto_record', false)) {
            app(RecordingService::class)->start($session, $host);
        }

        app(MeetNotificationService::class)->meetingStarted($session, $host);

        app(\App\Services\Integrations\WebhookDispatcher::class)->meetingStarted($session);

        AuditLogger::record($room->owner_ref, 'session.started', $room->organization_id, $host->ownerRef(), Session::class, $session->id);

        return $session;
    }

    public function end(Session $session, string $actorRef): Session
    {
        $session->update([
            'status' => 'ended',
            'ended_at' => now(),
        ]);

        $session->room->update(['status' => 'ended']);

        $session->participants()
            ->whereIn('status', ['joined', 'waiting'])
            ->update(['status' => 'left', 'left_at' => now()]);

        foreach ($session->recordings()->where('status', 'recording')->get() as $recording) {
            app(RecordingService::class)->stop($recording, $actorRef);
        }

        if ($session->room->isRecurring()) {
            app(RecurringMeetingService::class)->spawnNextOccurrences($session->room, 1);
        }

        AuditLogger::record($session->owner_ref, 'session.ended', $session->room->organization_id, $actorRef, Session::class, $session->id);

        app(\App\Services\Integrations\WebhookDispatcher::class)->meetingEnded($session);

        app(UsageMeteringService::class)->recordSessionUsage($session);

        return $session;
    }

    public function joinParticipant(
        Session $session,
        ?User $user,
        string $role,
        ?string $displayName = null,
        ?string $email = null,
        string $status = 'joined',
    ): Participant {
        $userRef = $user?->ownerRef();
        $existing = $this->findExistingParticipant($session, $userRef, $email);

        if ($existing) {
            $updates = [
                'left_at' => null,
                'display_name' => $displayName ?? $existing->display_name,
                'ip_address' => request()?->ip(),
            ];

            if ($existing->status === 'waiting') {
                $updates['status'] = 'waiting';
            } else {
                $updates['status'] = 'joined';
                $updates['joined_at'] = $existing->joined_at ?? now();
            }

            if (in_array($role, ['host', 'co_host'], true)) {
                $updates['role'] = $role;
            } elseif ($existing->role === 'guest' && $role !== 'guest') {
                $updates['role'] = $role;
            }

            $existing->update($updates);

            return $existing;
        }

        $joinedAt = $status === 'joined' ? now() : null;

        $participant = Participant::create([
            'owner_ref' => $session->owner_ref,
            'session_id' => $session->id,
            'user_ref' => $userRef,
            'display_name' => $displayName ?? $user?->name ?? 'Guest',
            'email' => $email,
            'role' => $role,
            'status' => $status,
            'joined_at' => $joinedAt,
            'ip_address' => request()?->ip(),
        ]);

        if ($status === 'joined') {
            $this->updatePeakParticipants($session);
        }

        if ($status === 'joined') {
            AuditLogger::record(
                $session->owner_ref,
                'participant.joined',
                $session->room->organization_id,
                $userRef,
                Participant::class,
                $participant->id,
            );
        }

        return $participant;
    }

    public function admitParticipant(Participant $participant): Participant
    {
        abort_unless($participant->status === 'waiting', 422);

        $participant->update([
            'status' => 'joined',
            'joined_at' => now(),
            'left_at' => null,
        ]);

        $this->updatePeakParticipants($participant->session);

        AuditLogger::record(
            $participant->owner_ref,
            'participant.joined',
            $participant->session->room->organization_id,
            $participant->user_ref,
            Participant::class,
            $participant->id,
        );

        return $participant->fresh();
    }

    public function denyParticipant(Participant $participant): Participant
    {
        abort_unless($participant->status === 'waiting', 422);

        $participant->update([
            'status' => 'removed',
            'left_at' => now(),
        ]);

        AuditLogger::record(
            $participant->owner_ref,
            'participant.removed',
            $participant->session->room->organization_id,
            $participant->user_ref,
            Participant::class,
            $participant->id,
        );

        return $participant->fresh();
    }

    protected function findExistingParticipant(Session $session, ?string $userRef, ?string $email): ?Participant
    {
        $active = fn ($q) => $q->whereIn('status', ['invited', 'waiting', 'joined']);

        if ($userRef) {
            return $session->participants()
                ->where('user_ref', $userRef)
                ->where($active)
                ->first();
        }

        if ($email) {
            return $session->participants()
                ->whereNull('user_ref')
                ->where('email', $email)
                ->where($active)
                ->first();
        }

        $guestUuid = (string) request()?->session()?->get("meet.participant.{$session->uuid}", '');
        if ($guestUuid === '') {
            return null;
        }

        return $session->participants()
            ->where('uuid', $guestUuid)
            ->whereNull('user_ref')
            ->where($active)
            ->first();
    }

    public function rememberParticipantSession(Request $request, Participant $participant, ?Session $session = null): void
    {
        $session ??= $participant->session;
        $request->session()->put("meet.participant.{$session->uuid}", $participant->uuid);
    }

    public function joinedParticipantForUser(Session $session, User $user): ?Participant
    {
        return $session->participants()
            ->where('user_ref', $user->ownerRef())
            ->whereIn('status', ['invited', 'waiting', 'joined'])
            ->first();
    }

    public function leaveParticipant(Participant $participant): Participant
    {
        $participant->update([
            'status' => 'left',
            'left_at' => now(),
            'hand_raised' => false,
        ]);

        AuditLogger::record(
            $participant->owner_ref,
            'participant.left',
            $participant->session->room->organization_id,
            $participant->user_ref,
            Participant::class,
            $participant->id,
        );

        return $participant;
    }

    public function generateMediaToken(Participant $participant): string
    {
        $session = $participant->session;
        $room = $session->room;
        $isHost = $participant->isHost();
        $breakoutService = app(BreakoutService::class);
        $webinarService = app(WebinarService::class);
        $spaceService = app(\App\Services\Meet\SpaceService::class);
        $mediaRoom = $breakoutService->mediaRoomForParticipant($participant);
        $canPublish = ($room->isSpace()
            ? $spaceService->canPublish($room, $participant)
            : $webinarService->canPublish($room, $participant)) && $participant->canPublishMedia();

        return $this->media->generateToken(
            $mediaRoom,
            $participant->uuid,
            $participant->display_name,
            [
                'can_publish' => $canPublish,
                'can_subscribe' => true,
                'can_publish_data' => true,
                'room_admin' => $isHost,
            ],
        );
    }

    public function getOrStartSession(Room $room, User $host): Session
    {
        if ($room->activeSession()) {
            return $room->activeSession();
        }

        return $this->start($room, $host);
    }

    public function lock(Session $session, string $actorRef): Session
    {
        $session->update(['is_locked' => true, 'locked_at' => now()]);
        AuditLogger::record($session->owner_ref, 'meeting.locked', $session->room->organization_id, $actorRef, Session::class, $session->id);

        return $session;
    }

    public function unlock(Session $session, string $actorRef): Session
    {
        $session->update(['is_locked' => false, 'locked_at' => null]);
        AuditLogger::record($session->owner_ref, 'meeting.unlocked', $session->room->organization_id, $actorRef, Session::class, $session->id);

        return $session;
    }

    protected function updatePeakParticipants(Session $session): void
    {
        $count = $session->participants()->where('status', 'joined')->count();
        if ($count > $session->peak_participants) {
            $session->update(['peak_participants' => $count]);
        }
    }
}
