import logging

from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import BaseSerializer
from rest_framework.viewsets import ModelViewSet

from apps.api.permissions import AuthenticatedRequest, IsOwner, RBACPermission
from apps.api.serializers.shift_swap import (
    ShiftSwapRequestExpandedUsersListSerializer,
    ShiftSwapRequestExpandedUsersSerializer,
    ShiftSwapRequestListSerializer,
    ShiftSwapRequestSerializer,
)
from apps.auth_token.auth import PluginAuthentication
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
from apps.schedules import exceptions
from apps.schedules.models import ShiftSwapRequest
from apps.schedules.tasks.shift_swaps import update_shift_swap_request_message
from apps.user_management.models import User
from common.api_helpers.exceptions import BadRequest
from common.api_helpers.mixins import PublicPrimaryKeyMixin
from common.api_helpers.paginators import FiftyPageSizePaginator
from common.insight_log import EntityEvent, write_resource_insight_log

logger = logging.getLogger(__name__)


class BaseShiftSwapViewSet(ModelViewSet):
    model = ShiftSwapRequest
    serializer_class = ShiftSwapRequestSerializer
    pagination_class = FiftyPageSizePaginator

    def _do_take(self, benefactor: User) -> dict:
        shift_swap = self.get_object()
        prev_state = shift_swap.insight_logs_serialized

        try:
            shift_swap.take(benefactor)
        except exceptions.ShiftSwapRequestNotOpenForTaking:
            raise BadRequest(detail="The shift swap request is not in a state which allows it to be taken")
        except exceptions.BeneficiaryCannotTakeOwnShiftSwapRequest:
            raise BadRequest(detail="A shift swap request cannot be created and taken by the same user")

        write_resource_insight_log(
            instance=shift_swap,
            author=self.request.user,
            event=EntityEvent.UPDATED,
            prev_state=prev_state,
            new_state=shift_swap.insight_logs_serialized,
        )

        return ShiftSwapRequestSerializer(shift_swap).data

    def get_serializer_class(self):
        if self.request.query_params.get("expand_users", "false") == "true":
            # return detailed benefactor/beneficiary user information
            return (
                ShiftSwapRequestExpandedUsersListSerializer
                if self.action == "list"
                else ShiftSwapRequestExpandedUsersSerializer
            )
        return ShiftSwapRequestListSerializer if self.action == "list" else super().get_serializer_class()

    def get_queryset(self):
        queryset = ShiftSwapRequest.objects.filter(schedule__organization=self.request.auth.organization).order_by(
            "-created_at"
        )
        return self.serializer_class.setup_eager_loading(queryset)

    def perform_destroy(self, instance: ShiftSwapRequest) -> None:
        super().perform_destroy(instance)
        write_resource_insight_log(instance=instance, author=self.request.user, event=EntityEvent.DELETED)

        update_shift_swap_request_message.apply_async((instance.pk,))

    def perform_create(self, serializer: BaseSerializer[ShiftSwapRequest]) -> None:
        # default to create swap request with logged in user as beneficiary
        serializer.save(beneficiary=self.request.user)

    def perform_update(self, serializer: BaseSerializer[ShiftSwapRequest]) -> None:
        prev_state = serializer.instance.insight_logs_serialized
        serializer.save()
        shift_swap_request = serializer.instance

        write_resource_insight_log(
            instance=shift_swap_request,
            author=self.request.user,
            event=EntityEvent.UPDATED,
            prev_state=prev_state,
            new_state=shift_swap_request.insight_logs_serialized,
        )

        update_shift_swap_request_message.apply_async((shift_swap_request.pk,))


class ShiftSwapViewSet(PublicPrimaryKeyMixin[ShiftSwapRequest], BaseShiftSwapViewSet):
    authentication_classes = (MobileAppAuthTokenAuthentication, PluginAuthentication)
    permission_classes = (IsAuthenticated, RBACPermission)

    rbac_permissions = {
        "metadata": [RBACPermission.Permissions.SCHEDULES_READ],
        "list": [RBACPermission.Permissions.SCHEDULES_READ],
        "retrieve": [RBACPermission.Permissions.SCHEDULES_READ],
        "create": [RBACPermission.Permissions.SCHEDULES_WRITE],
        "update": [RBACPermission.Permissions.SCHEDULES_WRITE],
        "partial_update": [RBACPermission.Permissions.SCHEDULES_WRITE],
        "destroy": [RBACPermission.Permissions.SCHEDULES_WRITE],
        "take": [RBACPermission.Permissions.SCHEDULES_WRITE],
    }

    is_beneficiary = IsOwner(ownership_field="beneficiary")

    rbac_object_permissions = {
        is_beneficiary: [
            "update",
            "partial_update",
            "destroy",
        ],
    }

    @action(methods=["post"], detail=True)
    def take(self, request: AuthenticatedRequest, pk: str) -> Response:
        serialized_shift_swap = self._do_take(benefactor=request.user)
        return Response(serialized_shift_swap, status=status.HTTP_200_OK)
