"""Permission handlers for the Meet core app."""

from django.conf import settings
from django.http import Http404

from rest_framework import permissions

from ..models import RoleChoices, RoomAccessLevel
from ..services.participants_management import (
    ParticipantNotFoundException,
    ParticipantsManagement,
    ParticipantsManagementException,
)

ACTION_FOR_METHOD_TO_PERMISSION = {
    "versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"}
}


class IsAuthenticated(permissions.BasePermission):
    """
    Allows access only to authenticated users. Alternative method checking the presence
    of the auth token to avoid hitting the database.
    """

    def has_permission(self, request, view):
        return bool(request.auth) or request.user.is_authenticated


class IsAuthenticatedOrSafe(IsAuthenticated):
    """Allows access to authenticated users (or anonymous users but only on safe methods)."""

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return super().has_permission(request, view)


class IsSelf(IsAuthenticated):
    """
    Allows access only to authenticated users. Alternative method checking the presence
    of the auth token to avoid hitting the database.
    """

    def has_object_permission(self, request, view, obj):
        """Write permissions are only allowed to the user itself."""
        return obj == request.user


class RoomPermissions(permissions.BasePermission):
    """
    Permissions applying to the room API endpoint.
    """

    def has_permission(self, request, view):
        """Only allow authenticated users for unsafe methods."""
        if request.method in permissions.SAFE_METHODS:
            return True

        return request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        """Object permissions are only given to administrators of the room."""

        if request.method in permissions.SAFE_METHODS:
            return True

        user = request.user

        if request.method == "DELETE":
            return obj.is_owner(user)

        return obj.is_administrator_or_owner(user)


class ResourceAccessPermission(IsAuthenticated):
    """
    Permissions for a room that can only be updated by room administrators.
    """

    def has_object_permission(self, request, view, obj):
        """
        Check that the logged-in user is administrator of the linked room.
        """
        user = request.user
        if request.method == "DELETE" and obj.role == RoleChoices.OWNER:
            return obj.user == user

        return obj.resource.is_administrator_or_owner(user)


class HasAbilityPermission(IsAuthenticated):
    """Permission class for access objects."""

    def has_object_permission(self, request, view, obj):
        """Check permission for a given object."""
        return obj.get_abilities(request.user).get(view.action, False)


class HasPrivilegesOnRoom(IsAuthenticated):
    """Check if user has privileges on a given room."""

    message = "You must have privileges on room to perform this action."

    def has_object_permission(self, request, view, obj):
        """Determine if user has privileges on room."""
        return obj.is_administrator_or_owner(request.user)


class HasLiveKitRoomAccess(permissions.BasePermission):
    """Check if authenticated user's LiveKit token is for the specific room."""

    def has_object_permission(self, request, view, obj):
        if not request.auth or not hasattr(request.auth, "video"):
            return False
        return request.auth.video.room == str(obj.id)


class FilePermission(IsAuthenticated):
    """
    Permissions applying to the file API endpoint.
    Handling soft deletions specificities
    """

    def has_permission(self, request, view):
        """Allow access only to authenticated users."""
        if not settings.FILE_UPLOAD_ENABLED:
            raise Http404

        return super().has_permission(request, view)

    def has_object_permission(self, request, view, obj):
        """
        Return a 404 on deleted files or if the user is not the owner
        """

        if obj.deleted_at is not None or obj.hard_deleted_at is not None:
            raise Http404

        if obj.creator != request.user:
            raise Http404

        return obj.get_abilities(request.user).get(view.action, False)


class CanMuteParticipant(permissions.BasePermission):
    """
    Grant muting rights based on role or room configuration.

    - Admins and owners can always mute.
    - When `everyone_can_mute` is enabled on the room, any participant
      currently in the room (proven by a valid LiveKit token for that room)
      can mute.
    """

    def has_object_permission(self, request, view, obj):
        """Check if the requesting user is allowed to mute a participant in the given room."""

        is_livekit_token_auth = request.auth and hasattr(request.auth, "video")

        # Always allow admins/owners when authenticated with session cookie
        if not is_livekit_token_auth and obj.is_administrator_or_owner(request.user):
            return True

        everyone_can_mute = obj.configuration.get("everyone_can_mute", True)
        if not everyone_can_mute:
            return False

        if not is_livekit_token_auth:
            return False

        # LiveKit token scoped to this room
        return request.auth.video.room == str(obj.id)


class IsPresentInMeeting(permissions.BasePermission):
    """Check that the requesting user is currently connected to the meeting.

    The requester must be session-authenticated (their DB identity is needed
    to check privileges); presence is verified against LiveKit using their
    `sub` as participant identity. Fails closed on LiveKit errors.
    """

    message = "You must be connected to the meeting to perform this action."

    def has_object_permission(self, request, view, obj):
        """Verify the requester's identity is a participant of the room."""
        user = request.user

        if not user or not user.is_authenticated:
            return False

        try:
            return ParticipantsManagement().check_if_in_meeting(
                room_name=str(obj.pk), identity=str(user.sub)
            )
        except ParticipantNotFoundException:
            return False
        except ParticipantsManagementException:
            return False


class CanManageLobby(permissions.BasePermission):
    """Grant lobby management (list/accept/deny waiting participants).

    - Room admins/owners can always manage the lobby.
    - When the room access level is TRUSTED, any authenticated user who is
      currently connected to the meeting can manage the lobby. Presence is
      verified cache-first (Redis), falling back to the LiveKit API.

    Access level is always read fresh from the DB; only presence is cached,
    so changing the room to RESTRICTED takes effect immediately.
    """

    message = "You are not allowed to manage this room's lobby."

    # pylint: disable=too-many-return-statements
    def has_object_permission(self, request, view, obj):  # noqa: PLR0911
        """Check privileges first, then the trusted-room presence path."""
        user = request.user

        if not user or not user.is_authenticated:
            return False

        # Product choice: lobby management is reserved for session-authenticated
        # users with a real account, not holders of a LiveKit room token.
        if request.auth and hasattr(request.auth, "video"):
            return False

        if obj.is_administrator_or_owner(user):
            return True

        if obj.access_level != RoomAccessLevel.TRUSTED:
            return False

        self.message = "You must be connected to the meeting to manage its lobby."

        try:
            return ParticipantsManagement().check_if_in_meeting_cached(
                room_name=str(obj.pk), identity=str(user.sub)
            )
        except ParticipantNotFoundException:
            return False
        except ParticipantsManagementException:
            return False
