from django.db import IntegrityError, transaction
from django.db.models import Subquery, OuterRef, Value
from django.utils import timezone
from rest_framework import generics, status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny, IsAuthenticated

from apps.core.permissions import IsGymStaff, get_gym_owner
from rest_framework.response import Response

from .models import (
    ClassAttendanceLog,
    ClassBooking,
    ClassSession,
    ClassType,
    RecurringClassTemplate,
    Room,
)
from .serializers import (
    BookClassSerializer,
    ClassAttendanceLogSerializer,
    ClassBookingSerializer,
    ClassSessionSerializer,
    ClassTypeSerializer,
    CreateClassSessionSerializer,
    MemberAvailableClassSerializer,
    RecurringClassTemplateSerializer,
    RoomSerializer,
)


class ClassTypeViewSet(viewsets.ModelViewSet):
    serializer_class = ClassTypeSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        user = self.request.user
        qs = ClassType.objects.all()
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(gym=owner)
        active = self.request.query_params.get("active")
        if active is not None:
            qs = qs.filter(is_active=active.lower() == "true")
        return qs

    def perform_create(self, serializer):
        gym_owner = get_gym_owner(self.request.user)
        serializer.save(gym=gym_owner)


class RoomViewSet(viewsets.ModelViewSet):
    serializer_class = RoomSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        user = self.request.user
        qs = Room.objects.select_related("location").all()
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(gym=owner)
        active = self.request.query_params.get("active")
        if active is not None:
            qs = qs.filter(is_active=active.lower() == "true")
        room_type = self.request.query_params.get("room_type")
        if room_type:
            qs = qs.filter(room_type=room_type)
        room_status = self.request.query_params.get("status")
        if room_status:
            qs = qs.filter(status=room_status)
        return qs

    def perform_create(self, serializer):
        gym_owner = get_gym_owner(self.request.user)
        serializer.save(gym=gym_owner)


class ClassSessionViewSet(viewsets.ModelViewSet):
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        qs = ClassSession.objects.select_related(
            "class_type", "instructor", "room", "location"
        ).all()

        user = self.request.user
        # Trainers only see their own sessions
        if user.role == "trainer":
            qs = qs.filter(instructor=user)
        elif not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(instructor__gym=owner)

        status_param = self.request.query_params.get("status")
        if status_param:
            qs = qs.filter(status=status_param)
        instructor = self.request.query_params.get("instructor")
        if instructor:
            qs = qs.filter(instructor_id=instructor)
        date_from = self.request.query_params.get("date_from")
        if date_from:
            qs = qs.filter(start_time__date__gte=date_from)
        date_to = self.request.query_params.get("date_to")
        if date_to:
            qs = qs.filter(start_time__date__lte=date_to)
        class_type = self.request.query_params.get("class_type")
        if class_type:
            qs = qs.filter(class_type_id=class_type)

        return qs

    def get_serializer_class(self):
        if self.action == "create":
            return CreateClassSessionSerializer
        return ClassSessionSerializer

    def create(self, request, *args, **kwargs):
        serializer = CreateClassSessionSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        session = serializer.save()
        return Response(
            ClassSessionSerializer(session).data,
            status=status.HTTP_201_CREATED,
        )

    @action(detail=False, methods=["get"])
    def instructors(self, request):
        """Return all active staff who can instruct classes."""
        from apps.staff.models import StaffProfile

        profiles = StaffProfile.objects.select_related("user").filter(is_active=True)
        user = request.user
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            profiles = profiles.filter(user__gym=owner)
        data = [
            {"id": str(p.user_id), "name": p.user.get_full_name() or p.user.email}
            for p in profiles
        ]
        return Response(data)


class ClassScheduleView(generics.ListAPIView):
    """Public weekly schedule."""
    serializer_class = ClassSessionSerializer
    permission_classes = [AllowAny]

    def get_queryset(self):
        return (
            ClassSession.objects.select_related("class_type", "instructor", "room")
            .filter(status__in=["scheduled", "active"])
            .order_by("start_time")
        )


class ClassBookingViewSet(viewsets.ModelViewSet):
    serializer_class = ClassBookingSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        qs = ClassBooking.objects.select_related(
            "class_session", "class_session__instructor", "member", "member__user"
        ).all()
        user = self.request.user
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(member__user__gym=owner)
        session = self.request.query_params.get("class_session")
        if session:
            qs = qs.filter(class_session_id=session)
        return qs


class BookClassView(generics.CreateAPIView):
    """Member books a class session — concurrency-safe."""
    serializer_class = BookClassSerializer
    permission_classes = [IsAuthenticated]

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        session_id = serializer.validated_data["class_session_id"]

        member = getattr(request.user, "member_profile", None)
        if not member:
            return Response(
                {"detail": "Member profile not found."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        with transaction.atomic():
            try:
                session = (
                    ClassSession.objects
                    .select_for_update()
                    .get(id=session_id, status__in=["scheduled", "active"])
                )
            except ClassSession.DoesNotExist:
                return Response(
                    {"detail": "Class session not found or not available."},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Reject booking for past non-recurring classes
            if not session.is_recurring and session.start_time < timezone.now():
                return Response(
                    {"detail": "Cannot book a class that has already started."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            if ClassBooking.objects.filter(
                class_session=session, member=member
            ).exclude(status="cancelled").exists():
                return Response(
                    {"detail": "Already booked for this class."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            booking_status = "booked"
            waitlist_pos = None
            if session.enrolled_count >= session.capacity:
                if not session.allow_waitlist:
                    return Response(
                        {"detail": "Class is full and waitlist is not allowed."},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                booking_status = "waitlisted"
                waitlist_pos = session.waitlist_count + 1
                session.waitlist_count += 1
            else:
                session.enrolled_count += 1
            session.save()

            try:
                booking = ClassBooking.objects.create(
                    class_session=session,
                    member=member,
                    status=booking_status,
                    waitlist_position=waitlist_pos,
                )
            except IntegrityError:
                return Response(
                    {"detail": "Already booked for this class."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        return Response(
            ClassBookingSerializer(booking).data,
            status=status.HTTP_201_CREATED,
        )


class CancelBookingView(generics.UpdateAPIView):
    """Cancel a booking — concurrency-safe with waitlist auto-promotion."""
    permission_classes = [IsAuthenticated]

    def update(self, request, *args, **kwargs):
        booking_id = kwargs.get("pk")

        with transaction.atomic():
            try:
                booking = (
                    ClassBooking.objects
                    .select_for_update()
                    .select_related("class_session")
                    .get(id=booking_id)
                )
            except ClassBooking.DoesNotExist:
                return Response(
                    {"detail": "Booking not found."},
                    status=status.HTTP_404_NOT_FOUND,
                )

            member_profile = getattr(request.user, "member_profile", None)
            is_staff_role = request.user.role in (
                "owner", "admin", "manager", "trainer", "front_desk"
            )
            if not is_staff_role and (
                not member_profile or booking.member != member_profile
            ):
                return Response(
                    {"detail": "Not authorized to cancel this booking."},
                    status=status.HTTP_403_FORBIDDEN,
                )

            if booking.status == "cancelled":
                return Response(
                    {"detail": "Booking already cancelled."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            session = (
                ClassSession.objects
                .select_for_update()
                .get(id=booking.class_session_id)
            )

            was_booked = booking.status == "booked"

            if booking.status == "booked":
                session.enrolled_count = max(0, session.enrolled_count - 1)
            elif booking.status == "waitlisted":
                session.waitlist_count = max(0, session.waitlist_count - 1)

            booking.status = "cancelled"
            booking.cancelled_at = timezone.now()
            booking.save()

            # Auto-promote first waitlisted member if a booked slot freed up
            if was_booked:
                first_waitlisted = (
                    ClassBooking.objects
                    .select_for_update()
                    .filter(class_session=session, status="waitlisted")
                    .order_by("waitlist_position")
                    .first()
                )
                if first_waitlisted:
                    first_waitlisted.status = "booked"
                    first_waitlisted.waitlist_position = None
                    first_waitlisted.save()
                    session.enrolled_count += 1
                    session.waitlist_count = max(0, session.waitlist_count - 1)

            session.save()

        return Response(ClassBookingSerializer(booking).data)


class MemberScheduleView(generics.ListAPIView):
    """Member sees classes from their assigned trainers, annotated with booking status."""
    serializer_class = MemberAvailableClassSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        member = getattr(self.request.user, "member_profile", None)
        if not member:
            return ClassSession.objects.none()

        # Get trainer user IDs from the member's assigned trainers
        trainer_user_ids = list(
            member.trainers.values_list("staff__user_id", flat=True)
        )

        qs = (
            ClassSession.objects
            .select_related("class_type", "instructor", "room")
            .filter(
                status__in=["scheduled", "active"],
                instructor_id__in=trainer_user_ids,
            )
            .order_by("start_time")
        )

        # If no trainers assigned, show all scheduled classes
        if not trainer_user_ids:
            qs = (
                ClassSession.objects
                .select_related("class_type", "instructor", "room")
                .filter(status__in=["scheduled", "active"])
                .order_by("start_time")
            )

        # Annotate with member's booking info
        bookings = {
            b.class_session_id: b
            for b in ClassBooking.objects.filter(
                member=member
            ).exclude(status="cancelled")
        }

        sessions = list(qs)
        for s in sessions:
            booking = bookings.get(s.id)
            s._booking_status = booking.status if booking else None
            s._booking_id = booking.id if booking else None

        return sessions

    def list(self, request, *args, **kwargs):
        sessions = self.get_queryset()
        serializer = self.get_serializer(sessions, many=True)
        return Response(serializer.data)


class AdminBookingsView(generics.ListAPIView):
    """Admin/staff sees all bookings with filters. Trainers see only their class bookings."""
    serializer_class = ClassBookingSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        qs = ClassBooking.objects.select_related(
            "class_session", "class_session__instructor", "member", "member__user"
        ).all()

        user = self.request.user
        # Trainers only see bookings for their own classes
        if user.role == "trainer":
            qs = qs.filter(class_session__instructor=user)
        elif not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(member__user__gym=owner)

        booking_status = self.request.query_params.get("status")
        if booking_status:
            qs = qs.filter(status=booking_status)
        date_from = self.request.query_params.get("date_from")
        if date_from:
            qs = qs.filter(class_session__start_time__date__gte=date_from)
        date_to = self.request.query_params.get("date_to")
        if date_to:
            qs = qs.filter(class_session__start_time__date__lte=date_to)
        instructor = self.request.query_params.get("instructor")
        if instructor:
            qs = qs.filter(class_session__instructor_id=instructor)

        return qs.order_by("-booked_at")


class WaitlistView(generics.ListAPIView):
    """Waitlisted bookings."""
    serializer_class = ClassBookingSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        qs = (
            ClassBooking.objects
            .select_related(
                "class_session", "class_session__instructor", "member", "member__user"
            )
            .filter(status="waitlisted")
        )
        user = self.request.user
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(member__user__gym=owner)
        return qs.order_by("class_session__start_time", "waitlist_position")


class ClassAttendanceLogViewSet(viewsets.ModelViewSet):
    serializer_class = ClassAttendanceLogSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        user = self.request.user
        qs = ClassAttendanceLog.objects.select_related("member").all()
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(member__user__gym=owner)
        session = self.request.query_params.get("class_session")
        if session:
            qs = qs.filter(class_session_id=session)
        return qs


class TrainerClassesView(generics.ListAPIView):
    """Trainer sees their assigned classes."""
    serializer_class = ClassSessionSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return (
            ClassSession.objects.select_related("class_type", "room")
            .filter(instructor=self.request.user, status__in=["scheduled", "active"])
            .order_by("start_time")
        )


class RecurringClassTemplateViewSet(viewsets.ModelViewSet):
    serializer_class = RecurringClassTemplateSerializer
    permission_classes = [IsGymStaff]

    def get_queryset(self):
        user = self.request.user
        qs = RecurringClassTemplate.objects.select_related(
            "class_type", "instructor", "room"
        ).filter(is_active=True)
        if not user.is_superuser:
            gym_owner = get_gym_owner(user)
            owner = gym_owner if (gym_owner and gym_owner != user) else user
            qs = qs.filter(instructor__gym=owner)
        return qs
