from django_filters import rest_framework as filters
from django_filters.constants import EMPTY_VALUES
from drf_spectacular.utils import OpenApiParameter, extend_schema

from django.contrib.auth import get_user_model
from django.db.models.query_utils import Q
from django.http import Http404
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.fields import BooleanField
from rest_framework.filters import OrderingFilter
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from lms.core.views.mixins import LabPermissionsMixin
from lms.core.views.pagination import LimitOffsetAllPagination
from lms.core.views.permissions import LabPermissions, get_action_permission
from lms.core.views.viewsets import LabModelViewSet

from ..filtersets import CourseCategoryFilter
from ..models import (
    Cohort, Course, CourseBlock, CourseCategory, CourseCity, CourseFile, CourseGroup, CourseModule, CourseStudent,
    CourseTeam, CourseVisibility, CourseWorkflow, LinkedCourse, Provider, StudyMode, Tutor,
)
from ..permissions import CourseObjectPermission
from ..serializers import (
    CohortCreateLabSerializer, CohortDetailLabSerializer, CohortListLabSerializer, CohortUpdateLabSerializer,
    CourseBlockCreateLabSerializer, CourseBlockDetailLabSerializer, CourseBlockIdLabSerializer,
    CourseBlockListLabSerializer, CourseBlockUpdateLabSerializer, CourseCalendarSerializer,
    CourseCategoryDetailLabSerializer, CourseCategoryListLabSerializer, CourseCityListSerializer,
    CourseCreateLabSerializer, CourseDetailLabSerializer, CourseFileCreateLabSerializer, CourseFileDetailLabSerializer,
    CourseGroupCreateLabSerializer, CourseGroupDetailLabSerializer, CourseGroupListLabSerializer,
    CourseGroupUpdateLabSerializer, CourseListLabSerializer, CourseModuleDetailLabSerializer,
    CourseModuleIdLabSerializer, CourseModuleListLabSerializer, CourseStudentListLabSerializer,
    CourseTeamCreateLabSerializer, CourseTeamDetailLabSerializer, CourseTeamInCourseSerializer,
    CourseTeamInCourseUpdateSerializer, CourseTeamListLabSerializer, CourseTeamMemberSerializer,
    CourseTeamMembersUpdateLabSerializer, CourseTeamUpdateLabSerializer, CourseUpdateLabSerializer,
    CourseVisibilityCheckLabSerializer, CourseVisibilityDetailLabSerializer, CourseVisibilityUpdateLabSerializer,
    CourseWorkflowSerializer, LinkedCourseCreateLabSerializer, LinkedCourseDetailLabSerializer,
    LinkedCourseUpdateLabSerializer, ProviderListSerializer, StudentCourseProgressListSerializer,
    StudyModeListSerializer, TutorDetailLabSerializer, TutorListLabSerializer,
)
from ..services import is_course_available_for_user
from ..tasks import recalculate_all_course_progresses_task
from ..views import GetCourseMixin
from .api import BaseCoursePermissionsViewSet, BaseCourseSlugViewSet

User = get_user_model()


class TutorLabViewSet(LabModelViewSet):
    serializer_class = TutorListLabSerializer
    serializer_classes = {
        'list': TutorListLabSerializer,
        'retrieve': TutorDetailLabSerializer,
    }
    queryset = Tutor.objects.active().select_related('user', 'user__staffprofile')
    pagination_class = None

    @extend_schema(
        summary=gettext("Список наставников")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Информация о наставнике")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)


class CourseWorkflowLabViewSet(LabModelViewSet):
    serializer_class = CourseWorkflowSerializer
    queryset = CourseWorkflow.objects.active()
    pagination_class = None

    @extend_schema(
        summary=gettext("Список воркфлоу")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class StudyModeLabViewSet(LabModelViewSet):
    serializer_class = StudyModeListSerializer
    queryset = StudyMode.objects.active()
    pagination_class = None

    @extend_schema(
        summary=gettext("Список форм обучения")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class ProviderLabViewSet(LabModelViewSet):
    serializer_class = ProviderListSerializer
    queryset = Provider.objects.active()
    pagination_class = None

    @extend_schema(
        summary=gettext("Список провайдеров")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class CourseCityLabViewSet(LabModelViewSet):
    serializer_class = CourseCityListSerializer
    queryset = CourseCity.objects.active()
    pagination_class = None

    @extend_schema(
        summary=gettext("Список городов (для курсов)")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class CourseLabFilterSet(filters.FilterSet):
    name = filters.CharFilter(field_name='name', lookup_expr='icontains')
    is_active = filters.BooleanFilter(field_name='is_active')
    is_archive = filters.BooleanFilter(field_name='is_archive')
    author_only = filters.BooleanFilter(field_name='author', method='filter_author_only')
    type = filters.ChoiceFilter(field_name='course_type', choices=Course.TypeChoices.choices)

    def filter_author_only(self, queryset, _, value):
        """Фильтрует курсы, в которых текущий пользователь является автором"""

        if value in EMPTY_VALUES:
            return queryset

        if value:
            return queryset.filter(author=self.request.user)

        return queryset


class CourseLabViewSet(LabModelViewSet):
    serializer_class = CourseListLabSerializer
    serializer_classes = {
        'create': CourseCreateLabSerializer,
        'update': CourseUpdateLabSerializer,
        'retrieve': CourseDetailLabSerializer,
        'partial_update': CourseUpdateLabSerializer,
    }
    queryset = Course.objects.select_related('author', 'study_mode', 'workflow', 'occupancy').prefetch_related('tags')
    filter_backends = [filters.DjangoFilterBackend]
    filterset_class = CourseLabFilterSet
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]

    def get_course(self, obj):
        return obj

    def get_queryset(self):
        queryset = super().get_queryset()

        if self.action == 'list':
            permission = get_action_permission(action=self.action, model=Course, is_related=False)
            queryset = queryset.permitted(permission=permission, user=self.request.user)
            queryset = queryset.prefetch_related('categories')

        return queryset

    @extend_schema(
        parameters=[
            OpenApiParameter(name='author_only', required=False, type=bool),
            OpenApiParameter(name='name', required=False, type=str),
            OpenApiParameter(name='is_active', required=False, type=bool),
            OpenApiParameter(name='is_archive', required=False, type=bool),
        ],
        summary=gettext("Список курсов"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        responses={200: CourseDetailLabSerializer},
        summary=gettext("Информация по курсу")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        request=CourseCreateLabSerializer,
        responses={201: CourseDetailLabSerializer},
        summary=gettext("Создание курса"),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    def perform_create(self, serializer: CourseCreateLabSerializer):
        serializer.save(author=self.request.user)

    @extend_schema(
        request=CourseUpdateLabSerializer,
        responses={200: CourseDetailLabSerializer},
        summary=gettext("Обновление информации по курсу")
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        request=CourseUpdateLabSerializer,
        responses={200: CourseDetailLabSerializer},
        summary=gettext("Частичное обновление информации по курсу")
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)


class CourseCategoryLabViewSet(LabModelViewSet):
    serializer_classes = {
        'retrieve': CourseCategoryDetailLabSerializer,
        'list': CourseCategoryListLabSerializer,
    }
    queryset = CourseCategory.objects.select_related('node', 'color_theme').order_by('node__path')
    pagination_class = LimitOffsetAllPagination
    filter_backends = [filters.DjangoFilterBackend]
    filterset_fields = ['is_active']
    filterset_class = CourseCategoryFilter

    @extend_schema(
        parameters=[
            OpenApiParameter(name='is_active', required=False, type=bool),
            OpenApiParameter(name='with_courses', required=False, type=bool),
        ],
        summary=gettext("Список категорий"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Информация по категории")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)


class CourseCategoriesLabViewSet(LabModelViewSet):
    """
    Список категорий курса.
    """
    serializer_class = CourseCategoryListLabSerializer
    queryset = CourseCategory.objects.select_related('node', 'color_theme')
    pagination_class = LimitOffsetAllPagination
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    filter_backends = [filters.DjangoFilterBackend]
    filterset_fields = ['is_active']

    def get_course(self, obj=None):
        if self.action == 'list':
            return get_object_or_404(Course.objects.all(), pk=self.kwargs.get('pk'))

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'list':
            course_id = self.kwargs['pk']
            filter_kwargs = {'courses': course_id}
            queryset = queryset.filter(**filter_kwargs)

        return queryset

    @extend_schema(
        parameters=[
            OpenApiParameter(name='is_active', required=False, type=bool),
        ],
        summary=gettext("Список категорий курса")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class CourseVisibilityLabViewSet(LabModelViewSet):
    serializer_class = CourseVisibilityDetailLabSerializer
    serializer_classes = {
        'update': CourseVisibilityUpdateLabSerializer,
        'retrieve': CourseVisibilityDetailLabSerializer,
    }
    queryset = CourseVisibility.objects.all().select_related('course')
    lookup_url_kwarg = 'pk'
    lookup_field = 'course_id'
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]

    def get_course(self, obj=None):
        if obj is not None:
            return obj.course
        if self.action == 'create':
            return get_object_or_404(Course.objects.all(), pk=self.request.data.get('course_id'))

    @extend_schema(
        summary=gettext("Список правил видимости курса")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Обновление правил видимости курса")
    )
    def update(self, request, *args, **kwargs):
        course_id = self.kwargs[self.lookup_url_kwarg]
        course = get_object_or_404(Course.objects.all(), pk=course_id)

        queryset = self.filter_queryset(self.get_queryset())
        instance = queryset.filter(course=course).first()

        # update
        if instance:
            serializer = self.get_serializer(instance, data=request.data)

            if getattr(instance, '_prefetched_objects_cache', None):
                instance._prefetched_objects_cache = {}

        # create
        else:
            serializer = self.get_serializer(data=request.data)

        serializer.is_valid(raise_exception=True)
        serializer.save(course=course)

        response_serializer = self.get_retrieve_serializer(serializer.instance)
        return Response(response_serializer.data)

    @extend_schema(
        summary=gettext("Удаление правил видимости курса")
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)


class CourseVisibilityCheckLabViewSet(LabModelViewSet):
    serializer_class = CourseVisibilityCheckLabSerializer
    queryset = Course.objects.select_related('visibility')
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]

    def get_course(self, obj: 'Course'):
        return obj

    @extend_schema(
        parameters=[
            OpenApiParameter(name='username', required=False),
        ],
        summary=gettext("Проверка правил видимости")
    )
    def retrieve(self, request, *args, **kwargs):
        username = request.query_params.get('username', None)
        if username:
            try:
                user = get_object_or_404(User, username=username)
            except Http404:
                raise NotFound(_("Пользователь не найден"))
        else:
            user = request.user

        instance = self.get_object()
        result = {
            'visible': is_course_available_for_user(course=instance, user=user),
        }

        serializer = self.get_serializer(result)

        return Response(serializer.data)


class CourseFileLabViewSet(GetCourseMixin, LabModelViewSet):
    serializer_class = CourseFileDetailLabSerializer
    serializer_classes = {
        'create': CourseFileCreateLabSerializer,
        'retrieve': CourseFileDetailLabSerializer,
    }
    queryset = CourseFile.objects.all()
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]

    @extend_schema(
        request=CourseFileCreateLabSerializer,
        responses={201: CourseFileDetailLabSerializer},
        summary=gettext("Создание ссылки для загрузки scorm-файла"),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    def perform_create(self, serializer: CourseFileCreateLabSerializer):
        serializer.save(uploaded_by=self.request.user)


class CourseGroupLabViewSet(LabModelViewSet):
    serializer_class = CourseGroupListLabSerializer
    serializer_classes = {
        'create': CourseGroupCreateLabSerializer,
        'update': CourseGroupUpdateLabSerializer,
        'partial_update': CourseGroupUpdateLabSerializer,
        'retrieve': CourseGroupDetailLabSerializer,
    }
    queryset = CourseGroup.objects.all().select_related('course', 'tutor', 'tutor__user', 'tutor__user__staffprofile')
    pagination_class = LimitOffsetAllPagination
    filter_backends = [filters.DjangoFilterBackend, OrderingFilter]
    filterset_fields = ['is_active', 'can_join']
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    ordering_fields = ['begin_date', 'end_date', 'enroll_begin', 'enroll_end', 'name']

    def get_course(self, obj=None):
        if obj is not None:
            return obj.course
        if self.action == 'create':
            return get_object_or_404(Course.objects.all(), pk=self.request.data.get('course_id'))
        if self.action == 'list':
            return get_object_or_404(Course.objects.all(), pk=self.kwargs.get('pk'))

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'list':
            course_id = self.kwargs['pk']
            filter_kwargs = {'course_id': course_id}
            queryset = queryset.filter(**filter_kwargs)

        return queryset

    @extend_schema(
        parameters=[
            OpenApiParameter(name='is_active', required=False, type=bool),
            OpenApiParameter(name='can_join', required=False, type=bool),
        ],
        summary=gettext("Список групп"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        responses={200: CourseGroupDetailLabSerializer},
        summary=gettext("Информация по группе")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        request=CourseGroupCreateLabSerializer,
        responses={201: CourseGroupDetailLabSerializer},
        summary=gettext("Создание группы"),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(
        request=CourseGroupUpdateLabSerializer,
        responses={200: CourseGroupDetailLabSerializer},
        summary=gettext("Обновление информации по группе"),
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        request=CourseGroupUpdateLabSerializer,
        responses={200: CourseGroupDetailLabSerializer},
        summary=gettext("Частичное обновление информации по группе"),
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Удаление информации по группе"),
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)


class CourseSlugLabViewSet(LabPermissionsMixin, BaseCourseSlugViewSet):
    pass


class CourseTeamLabViewSet(LabModelViewSet):
    serializer_class = CourseTeamListLabSerializer
    serializer_classes = {
        'create': CourseTeamCreateLabSerializer,
        'update': CourseTeamUpdateLabSerializer,
        'partial_update': CourseTeamUpdateLabSerializer,
        'retrieve': CourseTeamDetailLabSerializer,
    }
    queryset = CourseTeam.objects.all()
    pagination_class = LimitOffsetAllPagination

    @extend_schema(
        summary=gettext("Список команд"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        responses={200: CourseTeamDetailLabSerializer},
        summary=gettext("Информация по команде")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        request=CourseTeamCreateLabSerializer,
        responses={201: CourseTeamDetailLabSerializer},
        summary=gettext("Создание команды"),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(
        request=CourseTeamUpdateLabSerializer,
        responses={200: CourseTeamDetailLabSerializer},
        summary=gettext("Обновление команды"),
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        request=CourseTeamUpdateLabSerializer,
        responses={200: CourseTeamDetailLabSerializer},
        summary=gettext("Частичное Обновление команды"),
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Удаление команды"),
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)


class CourseTeamMemberListLabViewSet(LabModelViewSet):
    serializer_class = CourseTeamMemberSerializer
    serializer_classes = {
        'update': CourseTeamMembersUpdateLabSerializer,
        'list': CourseTeamMemberSerializer,
    }
    queryset = CourseTeam.objects.prefetch_related('user_set').all()
    pagination_class = None

    @extend_schema(
        responses={200: CourseTeamMemberSerializer},
        summary=gettext("Список участников команды")
    )
    def list(self, request, *args, **kwargs):
        obj = self.get_object()
        data = CourseTeamMemberSerializer(obj.members.all().order_by('username'), many=True).data
        return Response(data)

    @extend_schema(
        request=CourseTeamMembersUpdateLabSerializer,
        responses={200: CourseTeamMemberSerializer},
        summary=gettext("Обновление участников команды"),
    )
    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        users_ids = serializer.validated_data
        if not users_ids:
            raise ValidationError('users_ids is empty')
        not_found_users = set(users_ids) - set(
            User.objects.filter(is_active=True, id__in=users_ids).values_list('id', flat=True)
        )
        if not_found_users:
            raise NotFound(not_found_users)
        obj = self.get_object()
        obj.user_set.set(users_ids)
        data = CourseTeamMemberSerializer(obj.members.all().order_by('username'), many=True).data
        return Response(data)

    @extend_schema(
        summary=gettext("Удаление участников из команды"),
    )
    def destroy(self, request, *args, **kwargs):
        self.get_object().user_set.clear()
        return Response(status=status.HTTP_204_NO_CONTENT)


class CourseTeamInCourseLabViewSet(LabModelViewSet):
    serializer_class = CourseTeamInCourseSerializer
    serializer_classes = {
        'update': CourseTeamInCourseUpdateSerializer,
        'list': CourseTeamInCourseSerializer,
    }
    queryset = Course.objects.prefetch_related('teams').all()
    pagination_class = None

    @extend_schema(
        responses={200: CourseTeamInCourseSerializer},
        summary=gettext("Команды курса")
    )
    def list(self, request, *args, **kwargs):
        obj = self.get_object()
        data = CourseTeamInCourseSerializer(obj.teams.all(), many=True).data
        return Response(data)

    @extend_schema(
        request=CourseTeamInCourseUpdateSerializer,
        responses={200: CourseTeamInCourseSerializer},
        summary=gettext("Обновление команд курса"),
    )
    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        teams_ids = serializer.validated_data
        if not teams_ids:
            raise ValidationError('teams_ids is empty')
        not_found_teams = set(teams_ids) - set(
            CourseTeam.objects.filter(id__in=teams_ids).values_list('id', flat=True)
        )
        if not_found_teams:
            raise NotFound(not_found_teams)
        obj = self.get_object()
        obj.teams.set(teams_ids)
        data = CourseTeamInCourseSerializer(obj.teams.all(), many=True).data
        return Response(data)

    @extend_schema(
        summary=gettext("Удаление команд из курса"),
    )
    def destroy(self, request, *args, **kwargs):
        self.get_object().teams.clear()
        return Response(status=status.HTTP_204_NO_CONTENT)


class CoursePermissionsLabViewSet(LabPermissions, BaseCoursePermissionsViewSet):
    pass


class CourseModuleFilter(filters.FilterSet):
    app_label = filters.CharFilter(field_name='module_type', lookup_expr='app_label')
    model = filters.CharFilter(field_name='module_type', lookup_expr='model')


class CourseModuleLabViewSet(LabModelViewSet):
    serializer_class = CourseModuleListLabSerializer
    serializer_classes = {
        'retrieve': CourseModuleDetailLabSerializer,
        'move_above': CourseModuleIdLabSerializer,
        'move_below': CourseModuleIdLabSerializer,
    }
    queryset = CourseModule.objects.select_related('module_type')
    pagination_class = LimitOffsetAllPagination
    filter_backends = [filters.DjangoFilterBackend]
    filterset_class = CourseModuleFilter
    filterset_fields = ['is_active']

    MOVE_ACTIONS = [
        'move_up', 'move_down', 'move_top', 'move_bottom', 'move_above', 'move_below',
    ]

    def get_queryset(self):
        queryset = super().get_queryset()
        filter_kwargs = None
        if self.action == 'list':
            course_id = self.kwargs['pk']
            filter_kwargs = {'course_id': course_id}
        if self.action in self.MOVE_ACTIONS:
            module_id = self.kwargs['pk']
            filter_kwargs = {'course__modules': module_id}
        if filter_kwargs:
            queryset = queryset.filter(**filter_kwargs)
        return queryset

    @extend_schema(
        parameters=[
            OpenApiParameter(name='is_active', required=False, type=bool),
            OpenApiParameter(name='app_label', required=False, type=str),
            OpenApiParameter(name='model', required=False, type=str),
        ],
        summary=gettext("Список модулей курса"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        responses={200: CourseModuleDetailLabSerializer},
        summary=gettext("Общая ннформация по модулю")
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        request=None,
        responses={200: CourseModuleListLabSerializer(many=True)},
        summary=gettext("Перемещение модуля выше по списку")
    )
    @action(detail=True, methods=['put'])
    def move_up(self, request, *args, **kwargs):
        instance: CourseModule = self.get_object()
        instance.up()
        return self.list(request, *args, **kwargs)

    @extend_schema(
        request=None,
        responses={200: CourseModuleListLabSerializer(many=True)},
        summary=gettext("Перемещение модуля ниже по списку")
    )
    @action(detail=True, methods=['put'])
    def move_down(self, request, *args, **kwargs):
        instance: CourseModule = self.get_object()
        instance.down()
        return self.list(request, *args, **kwargs)

    @extend_schema(
        request=None,
        responses={200: CourseModuleListLabSerializer(many=True)},
        summary=gettext("Перемещение модуля наверх списка")
    )
    @action(detail=True, methods=['put'])
    def move_top(self, request, *args, **kwargs):
        instance: CourseModule = self.get_object()
        instance.top()
        return self.list(request, *args, **kwargs)

    @extend_schema(
        request=None,
        responses={200: CourseModuleListLabSerializer(many=True)},
        summary=gettext("Перемещение модуля вниз списка")
    )
    @action(detail=True, methods=['put'])
    def move_bottom(self, request, *args, **kwargs):
        instance: CourseModule = self.get_object()
        instance.bottom()
        return self.list(request, *args, **kwargs)

    def get_module_object(self):
        serializer = self.get_serializer(data=self.request.data)
        serializer.is_valid(raise_exception=True)
        return serializer.validated_data.get('module_id')

    @extend_schema(
        request=CourseModuleIdLabSerializer,
    )
    @action(detail=True, methods=['put'])
    def move_above(self, request, *args, **kwargs):
        module = self.get_module_object()
        instance: CourseModule = self.get_object()
        instance.above(module)
        return Response(status=status.HTTP_204_NO_CONTENT)

    @extend_schema(
        request=CourseModuleIdLabSerializer,
    )
    @action(detail=True, methods=['put'])
    def move_below(self, request, *args, **kwargs):
        module = self.get_module_object()
        instance: CourseModule = self.get_object()
        instance.below(module)
        return Response(status=status.HTTP_204_NO_CONTENT)


class CourseBlockLabViewSet(LabModelViewSet):
    serializer_class = CourseBlockListLabSerializer
    serializer_classes = {
        'create': CourseBlockCreateLabSerializer,
        'retrieve': CourseBlockDetailLabSerializer,
        'update': CourseBlockUpdateLabSerializer,
        'partial_update': CourseBlockUpdateLabSerializer,
        'list': CourseBlockListLabSerializer,
        'move_above': CourseBlockIdLabSerializer,
        'move_below': CourseBlockIdLabSerializer,
    }
    queryset = CourseBlock.objects.all()
    pagination_class = LimitOffsetAllPagination

    MOVE_ACTIONS = [
        'move_above', 'move_below',
    ]

    def get_queryset(self):
        queryset = super().get_queryset()
        filter_kwargs = None
        if self.action == 'list':
            course_id = self.kwargs['pk']
            filter_kwargs = {'course_id': course_id}
        if self.action in self.MOVE_ACTIONS:
            block_id = self.kwargs['pk']
            filter_kwargs = {'course__blocks': block_id}

        if filter_kwargs:
            queryset = queryset.filter(**filter_kwargs)
        return queryset

    def get_block_object(self):
        serializer = self.get_serializer(data=self.request.data)
        serializer.is_valid(raise_exception=True)
        return serializer.validated_data['block_id']

    @extend_schema(
        request=CourseBlockCreateLabSerializer,
        responses={201: CourseBlockDetailLabSerializer},
        summary=gettext("Создание блока"),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Информация по блоку"),
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        request=CourseBlockUpdateLabSerializer,
        responses={200: CourseBlockDetailLabSerializer},
        summary=gettext("Обновление блока"),
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Удаление блока"),
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)

    @extend_schema(
        request=CourseBlockUpdateLabSerializer,
        responses={200: CourseBlockDetailLabSerializer},
        summary=gettext("Частичное обновление блока"),
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Список блоков курса"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        description=gettext("Перемещение блока над другим блоком"),
        request=CourseBlockIdLabSerializer,
        responses={204: None},
    )
    @action(detail=True, methods=['put'])
    def move_above(self, request, *args, **kwargs):
        block = self.get_block_object()
        instance: CourseBlock = self.get_object()
        instance.above(block)
        return Response(status=status.HTTP_204_NO_CONTENT)

    @extend_schema(
        description=gettext("Перемещение блока под другой блок"),
        request=CourseBlockIdLabSerializer,
        responses={204: None},
    )
    @action(detail=True, methods=['put'])
    def move_below(self, request, *args, **kwargs):
        block = self.get_block_object()
        instance: CourseBlock = self.get_object()
        instance.below(block)
        return Response(status=status.HTTP_204_NO_CONTENT)


class CohortLabViewSet(LabModelViewSet):
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    serializer_class = CohortDetailLabSerializer
    serializer_classes = {
        'retrieve': CohortDetailLabSerializer,
        'list': CohortListLabSerializer,
        'create': CohortCreateLabSerializer,
        'update': CohortUpdateLabSerializer,
        'partial_update': CohortUpdateLabSerializer,
    }
    queryset = Cohort.objects.select_related('course')
    pagination_class = None
    filter_backends = [filters.DjangoFilterBackend]
    filterset_fields = ['is_active']

    def get_course(self, obj=None):
        if not hasattr(self, '_course'):
            if obj is not None:
                if obj.course is None:
                    raise NotFound(_("когорта не найдена"))
                self._course = obj.course
            if self.action == 'create':
                self._course = get_object_or_404(Course.objects.all(), pk=self.request.data.get('course_id'))
            if self.action == 'list':
                self._course = get_object_or_404(Course.objects.all(), pk=self.kwargs.get('pk'))

        return self._course

    def get_queryset(self):
        qs = super().get_queryset()
        if self.action == 'list':
            filter_q = Q(course_id=self.kwargs.get('pk'))
            if BooleanField().to_representation(self.request.query_params.get('with_global', False)):
                filter_q |= Q(course__isnull=True)
            qs = qs.filter(filter_q)

        return qs

    @extend_schema(
        summary=gettext("Список когорт курса"),
        parameters=[
            OpenApiParameter(name='with_global', required=False, type=bool),
        ],
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Создать когорту"),
        responses=({201: CohortDetailLabSerializer}),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Получить когорту"),
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Обновить когорту"),
        responses=({200: CohortDetailLabSerializer}),
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Частично обновить когорту"),
        responses=({200: CohortDetailLabSerializer}),
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Удалить когорту"),
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)


class CourseStudentLabViewSet(LabModelViewSet):
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    serializer_class = CourseStudentListLabSerializer
    serializer_classes = {
        'retrieve': CourseStudentListLabSerializer,
    }
    queryset = CourseStudent.objects.select_related('course', 'user')
    pagination_class = LimitOffsetAllPagination

    def get_course(self, obj=None):
        if not hasattr(self, '_course'):
            if self.action == 'list':
                self._course = get_object_or_404(Course.objects.all(), pk=self.kwargs.get('pk'))

        return self._course

    def get_queryset(self):
        qs = super().get_queryset()
        if self.action == 'list':
            qs = qs.filter(course_id=self.get_course().id)

        return qs

    @extend_schema(
        summary=gettext("Список студентов курса"),
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class CourseCalendarViewSet(LabModelViewSet):
    """
    Информация о встречах в календаре
    """
    serializer_class = CourseCalendarSerializer
    queryset = Course.objects.select_related('author').prefetch_related(
        'classroom_timeslots',
        'classroom_timeslots__calendar_event'
    )
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]

    def get_course(self, obj=None):
        return obj

    @extend_schema(
        summary=gettext("Получить информацию о встречах в календаре для курса"),
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)


class LinkedCourseLabViewSet(LabModelViewSet, GetCourseMixin):
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    serializer_class = LinkedCourseDetailLabSerializer
    serializer_classes = {
        'retrieve': LinkedCourseDetailLabSerializer,
        'create': LinkedCourseCreateLabSerializer,
        'update': LinkedCourseUpdateLabSerializer,
        'partial_update': LinkedCourseUpdateLabSerializer,
    }
    queryset = LinkedCourse.objects.select_related('course', 'linked_course')

    @extend_schema(
        summary=gettext("Создать вложенный курс"),
        responses=({201: LinkedCourseDetailLabSerializer}),
    )
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Информация о вложенном курсе"),
    )
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Обновить вложенный курс"),
        responses=({200: LinkedCourseDetailLabSerializer}),
    )
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Частично обновить вложенный курс"),
        responses=({200: LinkedCourseDetailLabSerializer}),
    )
    def partial_update(self, request, *args, **kwargs):
        return super().partial_update(request, *args, **kwargs)

    @extend_schema(
        summary=gettext("Удалить вложенный курс"),
    )
    def destroy(self, request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)


class CourseProgressLabViewSet(LabModelViewSet, GetCourseMixin):
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    serializer_class = StudentCourseProgressListSerializer
    serializer_classes = {
        'list': StudentCourseProgressListSerializer,
    }
    queryset = CourseStudent.objects.order_by('id').select_related(
        'user',
    ).prefetch_related(
        'course_progresses',
        'module_progresses',
    )

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'list':
            course_id = self.kwargs['pk']
            filter_kwargs = {'course_id': course_id}
            queryset = queryset.filter(**filter_kwargs)
        return queryset

    @extend_schema(
        summary=gettext("Список студентов с их прогрессом по курсу и модулям")
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class CourseProgressRecalculateLabViewSet(LabModelViewSet):
    """
    Пересчёт прогресса студентов курса
    """
    permission_classes = LabModelViewSet.permission_classes + [CourseObjectPermission]
    queryset = Course.objects.all()

    def get_course(self, obj=None):
        return self.get_object() if not obj else obj

    @extend_schema(
        request=None,
        responses={204: None},
        summary=gettext("Пересчёт прогресса студентов курса")
    )
    def update(self, request, *args, **kwargs):
        course = self.get_course()
        recalculate_all_course_progresses_task.delay(course_id=course.id)
        return Response(status=status.HTTP_204_NO_CONTENT)
