from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django.shortcuts import get_object_or_404
from django.db import models
from django.db.models import Sum, Count, Q, Avg, F, FloatField
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.db import IntegrityError
from .models import (
    ExamType,
    Exam,
    ExamClassRegistration,
    StudentExamRegistration,
    ExamSubject,
    StudentMarks,
    ExamResult,
    ExamAttendance,
    ExamGradeSystem,
    ExamConfiguration,
)
from .serializers import (
    ExamTypeSerializer,
    ExamSerializer,
    ExamClassRegistrationSerializer,
    StudentExamRegistrationSerializer,
    StudentExamRegistrationCreateUpdateSerializer,
    ExamSubjectSerializer,
    StudentMarksSerializer,
    StudentMarksCreateUpdateSerializer,
    ExamResultSerializer,
    ExamAttendanceSerializer,
    ExamGradeSystemSerializer,
    ExamConfigurationSerializer,
    ExamDashboardStatsSerializer,
    ExamPerformanceReportSerializer,
    SubjectPerformanceSerializer,
    TopperListSerializer,
)
from academics.models import StudentEnrollment, AcademicClass, Subject


def _auto_create_exam_subjects(exam):
    """
    Create one ExamSubject per active subject in this exam's standard, with
    default marks and no date/time set — left blank so the admin assigns the
    real per-subject schedule from the Exam Timetable page.
    """
    subjects = Subject.objects.filter(
        standard=exam.academic_class.standard, is_active=True
    ).order_by('name')
    default_max_marks = exam.exam_type.max_marks
    default_passing_marks = exam.exam_type.passing_marks

    for subject in subjects:
        ExamSubject.objects.get_or_create(
            exam=exam,
            subject=subject,
            defaults={
                'max_marks': default_max_marks,
                'passing_marks': default_passing_marks,
                'exam_date': None,
                'start_time': None,
            }
        )


class ExamTypeViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Type CRUD operations
    """

    queryset = ExamType.objects.all()
    serializer_class = ExamTypeSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['is_terminal', 'is_mid_term', 'is_continuous_assessment', 'is_active']
    search_fields = ['name', 'code']
    ordering_fields = ['name', 'default_weightage', 'created_at']
    ordering = ['name']

    def get_queryset(self):
        queryset = super().get_queryset()

        # Filter by search query
        search = self.request.query_params.get('search', '')
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) | Q(code__icontains=search)
            )

        return queryset


class ExamViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam CRUD operations
    """

    queryset = Exam.objects.all()
    serializer_class = ExamSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['exam_type', 'academic_year', 'academic_class', 'status', 'is_active']
    search_fields = ['name', 'code']
    ordering_fields = ['start_date', 'end_date', 'name', 'created_at']
    ordering = ['-start_date']

    def get_queryset(self):
        queryset = super().get_queryset()

        # Filter by search query
        search = self.request.query_params.get('search', '')
        if search:
            queryset = queryset.filter(
                Q(name__icontains=search) | Q(code__icontains=search)
            )

        # Filter by date range
        from_date = self.request.query_params.get('from_date')
        to_date = self.request.query_params.get('to_date')
        if from_date:
            queryset = queryset.filter(start_date__gte=from_date)
        if to_date:
            queryset = queryset.filter(end_date__lte=to_date)

        # Optimize queries
        queryset = queryset.select_related('exam_type', 'academic_year', 'academic_class')

        return queryset

    @action(detail=False, methods=['post'])
    def bulk_create(self, request):
        """
        Create the same exam across multiple classes in a single call, so
        scheduling one exam for e.g. 10 sections doesn't require repeating
        the create form 10 times. Each class still gets its own Exam row
        (marks/registrations are class-scoped), but the admin does it once.
        """
        academic_class_ids = request.data.get('academic_classes', [])
        if not isinstance(academic_class_ids, list) or not academic_class_ids:
            return Response(
                {'error': 'academic_classes is required and must be a non-empty list'},
                status=status.HTTP_400_BAD_REQUEST
            )

        base_fields = {
            'name': request.data.get('name'),
            'code': request.data.get('code'),
            'exam_type': request.data.get('exam_type'),
            'academic_year': request.data.get('academic_year'),
            'start_date': request.data.get('start_date'),
            'end_date': request.data.get('end_date'),
            'weightage': request.data.get('weightage', 100),
            'instructions': request.data.get('instructions', ''),
            'is_active': request.data.get('is_active', True),
        }

        created_exams = []
        skipped = []

        for class_id in academic_class_ids:
            serializer = ExamSerializer(data={**base_fields, 'academic_class': class_id})
            if not serializer.is_valid():
                skipped.append({'academic_class': class_id, 'errors': serializer.errors})
                continue

            exam = serializer.save()

            # Auto-register students for this class, same as single-exam creation
            class_registration, _ = ExamClassRegistration.objects.get_or_create(
                exam=exam,
                academic_class=exam.academic_class,
                defaults={
                    'is_registered': True,
                    'auto_generate_hall_tickets': True,
                    'auto_generate_seat_numbers': True,
                }
            )
            class_registration.register_all_students()

            # Auto-create the exam subjects for this class too — no separate
            # "add subject to exam" step needed for the common case.
            _auto_create_exam_subjects(exam)

            created_exams.append(exam)

        return Response({
            'message': f'{len(created_exams)} exam(s) created, {len(skipped)} skipped',
            'created_count': len(created_exams),
            'skipped_count': len(skipped),
            'created': ExamSerializer(created_exams, many=True).data,
            'skipped': skipped,
        }, status=status.HTTP_207_MULTI_STATUS if skipped else status.HTTP_201_CREATED)

    @action(detail=True, methods=['post'])
    def update_status(self, request, pk=None):
        """
        Update exam status
        """
        exam = self.get_object()
        new_status = request.data.get('status')

        if new_status not in ['scheduled', 'ongoing', 'completed', 'cancelled', 'results_published']:
            return Response(
                {'error': 'Invalid status value'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam.status = new_status

        if new_status == 'results_published':
            exam.results_published_date = timezone.now()

        exam.save()
        serializer = self.get_serializer(exam)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def auto_register_students(self, request, pk=None):
        """
        Automatically register all students in the class for this exam
        """
        exam = self.get_object()

        # Get or create class registration
        class_registration, created = ExamClassRegistration.objects.get_or_create(
            exam=exam,
            academic_class=exam.academic_class,
            defaults={
                'is_registered': True,
                'auto_generate_hall_tickets': True,
                'auto_generate_seat_numbers': True
            }
        )

        created_count, updated_count = class_registration.register_all_students()

        return Response({
            'message': 'Student registration completed',
            'created_count': created_count,
            'updated_count': updated_count,
            'total_registered': exam.total_registered_students
        })

    @action(detail=True, methods=['get'])
    def registered_students(self, request, pk=None):
        """
        Get all registered students for this exam
        """
        exam = self.get_object()
        registrations = exam.student_registrations.filter(is_registered=True).select_related(
            'student_enrollment__student',
            'student_enrollment__academic_class'
        )

        # Pagination
        page = self.paginate_queryset(registrations)
        if page is not None:
            serializer = StudentExamRegistrationSerializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = StudentExamRegistrationSerializer(registrations, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def results(self, request, pk=None):
        """
        Get all results for this exam
        """
        exam = self.get_object()
        results = exam.student_results.all().select_related('student_enrollment__student')

        # Pagination
        page = self.paginate_queryset(results)
        if page is not None:
            serializer = ExamResultSerializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = ExamResultSerializer(results, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def calculate_results(self, request, pk=None):
        """
        Calculate and generate results for all students in the exam
        """
        exam = self.get_object()

        if exam.status == 'results_published':
            return Response(
                {'error': 'Results are already published for this exam'},
                status=status.HTTP_400_BAD_REQUEST
            )

        marks_status = exam.marks_entry_status
        if not marks_status['complete']:
            return Response(
                {
                    'error': (
                        f"Marks not updated yet — {marks_status['pending']} of "
                        f"{marks_status['total_expected']} entries are still pending. "
                        f"Please complete marks entry before calculating results."
                    ),
                    'marks_pending': True,
                    **marks_status,
                },
                status=status.HTTP_400_BAD_REQUEST
            )

        # Get all registered students
        registrations = exam.student_registrations.filter(is_registered=True)

        results_created = 0
        results_updated = 0

        for registration in registrations:
            student = registration.student_enrollment

            # Get all marks for this student in this exam
            marks_queryset = StudentMarks.objects.filter(
                student_enrollment=student,
                exam_subject__exam=exam
            )

            total_marks = marks_queryset.aggregate(
                total=Coalesce(Sum('obtained_marks'), 0, output_field=models.DecimalField())
            )['total']

            total_max_marks = marks_queryset.aggregate(
                total=Coalesce(Sum('exam_subject__max_marks'), 0)
            )['total']

            percentage = (total_marks / total_max_marks * 100) if total_max_marks > 0 else 0

            # Determine result status
            failed_subjects = marks_queryset.filter(
                obtained_marks__lt=F('exam_subject__passing_marks')
            ).count()

            is_absent = marks_queryset.filter(is_absent=True).count() == marks_queryset.count()

            if is_absent:
                result_status = 'absent'
            elif failed_subjects > 0:
                result_status = 'fail'
            else:
                result_status = 'pass'

            # Create or update result
            result, created = ExamResult.objects.update_or_create(
                student_enrollment=student,
                exam=exam,
                defaults={
                    'total_marks': total_marks,
                    'total_max_marks': total_max_marks,
                    'percentage': percentage,
                    'result_status': result_status
                }
            )

            if created:
                results_created += 1
            else:
                results_updated += 1

        # Update exam status
        exam.status = 'results_published'
        exam.results_published_date = timezone.now()
        exam.save()

        return Response({
            'message': 'Results calculated successfully',
            'results_created': results_created,
            'results_updated': results_updated,
            'total_students_processed': registrations.count()
        })


class ExamClassRegistrationViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Class Registration CRUD operations
    """

    queryset = ExamClassRegistration.objects.all()
    serializer_class = ExamClassRegistrationSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['exam', 'academic_class', 'is_registered']
    ordering_fields = ['registration_date']
    ordering = ['-registration_date']

    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.select_related('exam', 'academic_class', 'registered_by')

    @action(detail=True, methods=['post'])
    def register_all(self, request, pk=None):
        """
        Register all students in the class for this exam
        """
        class_registration = self.get_object()
        created_count, updated_count = class_registration.register_all_students()

        return Response({
            'message': 'All students registered successfully',
            'created_count': created_count,
            'updated_count': updated_count
        })

    @action(detail=True, methods=['post'])
    def unregister_all(self, request, pk=None):
        """
        Unregister all students in the class from this exam
        """
        class_registration = self.get_object()
        count = class_registration.unregister_all_students()

        return Response({
            'message': f'{count} students unregistered successfully',
            'unregistered_count': count
        })


class StudentExamRegistrationViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Student Exam Registration CRUD operations
    """

    queryset = StudentExamRegistration.objects.all()
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['student_enrollment', 'exam', 'is_registered', 'registration_type']
    search_fields = ['student_enrollment__student__first_name', 'student_enrollment__student__last_name', 'hall_ticket_number']
    ordering_fields = ['registration_date', 'hall_ticket_number']
    ordering = ['-registration_date']

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return StudentExamRegistrationCreateUpdateSerializer
        return StudentExamRegistrationSerializer

    def get_queryset(self):
        queryset = super().get_queryset()
        queryset = queryset.select_related(
            'student_enrollment__student',
            'student_enrollment__academic_class',
            'exam',
            'class_registration',
            'registered_by'
        )
        return queryset

    @action(detail=False, methods=['post'])
    def bulk_register(self, request):
        """
        Bulk register students for an exam
        """
        exam_id = request.data.get('exam_id')
        student_ids = request.data.get('student_ids', [])
        registration_type = request.data.get('registration_type', 'bulk_upload')

        if not exam_id:
            return Response(
                {'error': 'exam_id is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam = get_object_or_404(Exam, id=exam_id)

        created_count = 0
        errors = []

        for student_id in student_ids:
            try:
                registration, created = StudentExamRegistration.objects.get_or_create(
                    student_enrollment_id=student_id,
                    exam=exam,
                    defaults={
                        'is_registered': True,
                        'registration_type': registration_type
                    }
                )
                if created:
                    # Auto-generate hall ticket
                    registration.generate_hall_ticket()
                    registration.save()
                    created_count += 1
            except Exception as e:
                errors.append({'student_id': student_id, 'error': str(e)})

        return Response({
            'message': f'{created_count} students registered successfully',
            'created_count': created_count,
            'errors': errors
        }, status=status.HTTP_207_MULTI_STATUS if errors else status.HTTP_201_CREATED)

    @action(detail=True, methods=['post'])
    def generate_hall_ticket(self, request, pk=None):
        """
        Generate hall ticket for a student
        """
        registration = self.get_object()
        prefix = request.data.get('prefix')
        registration.generate_hall_ticket(prefix)
        registration.save()

        return Response({
            'hall_ticket_number': registration.hall_ticket_number
        })

    @action(detail=True, methods=['post'])
    def generate_seat_number(self, request, pk=None):
        """
        Generate seat number for a student
        """
        registration = self.get_object()
        prefix = request.data.get('prefix')
        registration.generate_seat_number(prefix)
        registration.save()

        return Response({
            'seat_number': registration.seat_number
        })

    @action(detail=True, methods=['get'])
    def exam_status(self, request, pk=None):
        """
        GET /exam/student-exam-registrations/{id}/exam_status/

        Returns full exam status for one registered student:
          - Registration info (hall ticket, seat)
          - Per-subject: exam date, max/pass marks, attendance, marks entered, grade, pass/fail
          - Overall result (if published)
        """
        registration = self.get_object()
        enrollment  = registration.student_enrollment
        exam        = registration.exam

        # All subjects for this exam
        subjects = ExamSubject.objects.filter(
            exam=exam, is_active=True
        ).select_related('subject', 'teacher_in_charge').order_by('exam_date', 'start_time')

        # Pre-fetch marks and attendance for this student in one query each
        marks_map = {
            m.exam_subject_id: m
            for m in StudentMarks.objects.filter(
                student_enrollment=enrollment,
                exam_subject__exam=exam,
            ).select_related('entered_by', 'exam_subject')
        }
        attendance_map = {
            a.exam_subject_id: a
            for a in ExamAttendance.objects.filter(
                student_enrollment=enrollment,
                exam_subject__exam=exam,
            )
        }

        subjects_data = []
        for subj in subjects:
            mark = marks_map.get(subj.id)
            att  = attendance_map.get(subj.id)

            # Attendance status
            if att is not None:
                att_status = 'present' if att.is_present else 'absent'
            else:
                att_status = 'not_recorded'

            # Marks block
            if mark:
                marks_block = {
                    'marks_entered':  True,
                    'obtained_marks': float(mark.obtained_marks) if mark.obtained_marks is not None else None,
                    'is_absent':      mark.is_absent,
                    'is_withheld':    mark.is_withheld,
                    'grade':          mark.grade or None,
                    'grade_point':    float(mark.grade_point) if mark.grade_point else None,
                    'is_passed':      mark.is_passed,
                    'percentage':     round(float(mark.percentage), 1) if mark.percentage else None,
                    'remarks':        mark.remarks or None,
                    'entered_by':     mark.entered_by.full_name if mark.entered_by else None,
                    'last_updated':   mark.last_updated.strftime('%d %b %Y, %I:%M %p') if mark.last_updated else None,
                }
            else:
                marks_block = {'marks_entered': False}

            subjects_data.append({
                'subject_id':       subj.subject.id,
                'subject_name':     subj.subject.name,
                'subject_code':     subj.subject.code,
                'exam_date':        subj.exam_date.strftime('%d %b %Y') if subj.exam_date else None,
                'start_time':       subj.start_time.strftime('%I:%M %p') if subj.start_time else None,
                'end_time':         subj.end_time.strftime('%I:%M %p') if subj.end_time else None,
                'max_marks':        float(subj.max_marks),
                'passing_marks':    float(subj.passing_marks),
                'room_number':      subj.room_number or None,
                'teacher_in_charge': subj.teacher_in_charge.full_name if subj.teacher_in_charge else None,
                'attendance':       att_status,
                'marks':            marks_block,
            })

        # Overall result
        result = ExamResult.objects.filter(student_enrollment=enrollment, exam=exam).first()
        overall_result = None
        if result:
            overall_result = {
                'total_marks':     float(result.total_marks) if result.total_marks else None,
                'max_marks':       float(result.total_max_marks) if result.total_max_marks else None,
                'percentage':      round(float(result.percentage), 1) if result.percentage else None,
                'grade':           result.overall_grade or None,
                'rank':            result.rank,
                'result_status':   result.result_status,
                'published':       True,
            }
        else:
            overall_result = {'published': False}

        student = enrollment.student
        return Response({
            'success': True,
            'data': {
                'registration': {
                    'id':               registration.id,
                    'hall_ticket':      registration.hall_ticket_number,
                    'seat_number':      registration.seat_number,
                    'is_registered':    registration.is_registered,
                    'registration_date': registration.registration_date.strftime('%d %b %Y') if registration.registration_date else None,
                },
                'student': {
                    'name':             student.full_name,
                    'roll_number':      enrollment.roll_number,
                    'admission_no':     getattr(student, 'admission_no', None),
                },
                'exam': {
                    'id':    exam.id,
                    'name':  exam.name,
                    'code':  exam.code,
                    'status': exam.status,
                    'start_date': exam.start_date.strftime('%d %b %Y') if exam.start_date else None,
                    'end_date':   exam.end_date.strftime('%d %b %Y') if exam.end_date else None,
                    'total_subjects': subjects.count(),
                    'marks_entered_count': sum(1 for s in subjects_data if s['marks']['marks_entered']),
                    'attendance_recorded_count': sum(1 for s in subjects_data if s['attendance'] != 'not_recorded'),
                },
                'subjects':       subjects_data,
                'overall_result': overall_result,
            }
        })

    @action(detail=True, methods=['post'])
    def toggle_registration(self, request, pk=None):
        """
        Toggle registration status
        """
        registration = self.get_object()
        registration.is_registered = not registration.is_registered
        registration.save()

        serializer = self.get_serializer(registration)
        return Response(serializer.data)


class ExamSubjectViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Subject CRUD operations
    """

    queryset = ExamSubject.objects.all()
    serializer_class = ExamSubjectSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['exam', 'subject', 'is_active']
    search_fields = ['subject__name', 'subject__code', 'room_number']
    ordering_fields = ['exam_date', 'start_time']
    ordering = ['exam_date', 'start_time']

    def get_queryset(self):
        queryset = super().get_queryset()
        queryset = queryset.select_related('exam', 'subject', 'teacher_in_charge')
        queryset = queryset.prefetch_related('invigilators')

        # filterset_fields above has no effect without DjangoFilterBackend
        # enabled, so these are handled manually.
        exam_id = self.request.query_params.get('exam')
        if exam_id:
            queryset = queryset.filter(exam_id=exam_id)

        subject_id = self.request.query_params.get('subject')
        if subject_id:
            queryset = queryset.filter(subject_id=subject_id)

        is_active = self.request.query_params.get('is_active')
        if is_active is not None:
            queryset = queryset.filter(is_active=is_active.lower() == 'true')

        return queryset

    @action(detail=True, methods=['get'])
    def marks(self, request, pk=None):
        """
        Get all marks for this exam subject
        """
        exam_subject = self.get_object()
        marks = exam_subject.student_marks.all().select_related(
            'student_enrollment__student',
            'entered_by'
        )

        page = self.paginate_queryset(marks)
        if page is not None:
            serializer = StudentMarksSerializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = StudentMarksSerializer(marks, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def bulk_enter_marks(self, request, pk=None):
        """
        Bulk enter marks for all students in this exam subject
        """
        exam_subject = self.get_object()
        marks_data = request.data.get('marks', [])

        updated_count = 0
        errors = []

        for mark_entry in marks_data:
            student_id = mark_entry.get('student_enrollment_id')
            obtained_marks = mark_entry.get('obtained_marks')
            is_absent = mark_entry.get('is_absent', False)

            try:
                mark, created = StudentMarks.objects.update_or_create(
                    student_enrollment_id=student_id,
                    exam_subject=exam_subject,
                    defaults={
                        'obtained_marks': None if is_absent else obtained_marks,
                        'is_absent': is_absent,
                        'entered_by': request.user.teacher if hasattr(request.user, 'teacher') else None
                    }
                )
                updated_count += 1
            except Exception as e:
                errors.append({'student_id': student_id, 'error': str(e)})

        return Response({
            'message': f'{updated_count} marks entries processed',
            'updated_count': updated_count,
            'errors': errors
        })


class StudentMarksViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Student Marks CRUD operations
    """

    queryset = StudentMarks.objects.all()
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['student_enrollment', 'exam_subject', 'is_absent', 'is_withheld']
    search_fields = ['student_enrollment__student__first_name', 'student_enrollment__student__last_name']
    ordering_fields = ['obtained_marks', 'entry_date']
    ordering = ['-entry_date']

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return StudentMarksCreateUpdateSerializer
        return StudentMarksSerializer

    def get_queryset(self):
        queryset = super().get_queryset()
        queryset = queryset.select_related(
            'student_enrollment__student',
            'exam_subject__subject',
            'exam_subject__exam',
            'entered_by'
        )
        return queryset

    @action(detail=True, methods=['post'])
    def apply_revaluation(self, request, pk=None):
        """
        Apply for revaluation
        """
        mark = self.get_object()
        mark.is_revaluation_applied = True
        mark.revaluation_reason = request.data.get('revaluation_reason', '')
        mark.revaluation_date = timezone.now()
        mark.save()

        return Response({
            'message': 'Revaluation applied successfully',
            'revaluation_reason': mark.revaluation_reason,
            'revaluation_date': mark.revaluation_date
        })

    @action(detail=True, methods=['post'])
    def update_revaluation_marks(self, request, pk=None):
        """
        Update revaluation marks
        """
        mark = self.get_object()
        new_marks = request.data.get('revaluation_marks')

        if new_marks is not None:
            if new_marks > mark.exam_subject.max_marks:
                return Response(
                    {'error': f'Marks cannot exceed {mark.exam_subject.max_marks}'},
                    status=status.HTTP_400_BAD_REQUEST
                )

            mark.revaluation_marks = new_marks
            mark.save()

        return Response({
            'message': 'Revaluation marks updated',
            'original_marks': mark.obtained_marks,
            'revaluation_marks': mark.revaluation_marks
        })


class ExamResultViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Result CRUD operations
    """

    queryset = ExamResult.objects.all()
    serializer_class = ExamResultSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['student_enrollment', 'exam', 'result_status']
    ordering_fields = ['percentage', 'rank']
    ordering = ['-percentage']

    def get_queryset(self):
        queryset = super().get_queryset()
        queryset = queryset.select_related(
            'student_enrollment__student',
            'student_enrollment__academic_class',
            'exam'
        )
        return queryset

    @action(detail=False, methods=['post'])
    def calculate_ranks(self, request):
        """
        Calculate ranks for an exam
        """
        exam_id = request.data.get('exam_id')

        if not exam_id:
            return Response(
                {'error': 'exam_id is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam = get_object_or_404(Exam, id=exam_id)
        results = exam.student_results.filter(result_status='pass').order_by('-percentage')

        rank = 1
        previous_percentage = None
        rank_increment = 1

        for result in results:
            if previous_percentage is not None and result.percentage < previous_percentage:
                rank += rank_increment
                rank_increment = 1
            elif previous_percentage is not None and result.percentage == previous_percentage:
                rank_increment += 1

            result.rank = rank
            result.save()
            previous_percentage = result.percentage

        return Response({
            'message': f'Ranks calculated for exam: {exam.name}',
            'total_students_ranked': results.count()
        })


class ExamAttendanceViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Attendance CRUD operations
    """

    queryset = ExamAttendance.objects.all()
    serializer_class = ExamAttendanceSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['exam_subject', 'is_present', 'is_late']
    ordering_fields = ['marked_at']
    ordering = ['-marked_at']

    def get_queryset(self):
        queryset = super().get_queryset()
        queryset = queryset.select_related(
            'student_enrollment__student',
            'exam_subject__subject',
            'exam_subject__exam',
            'marked_by'
        )
        return queryset

    @action(detail=False, methods=['post'])
    def bulk_mark_attendance(self, request):
        """
        Bulk mark attendance for an exam subject
        """
        exam_subject_id = request.data.get('exam_subject_id')
        attendance_data = request.data.get('attendance', [])

        if not exam_subject_id:
            return Response(
                {'error': 'exam_subject_id is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam_subject = get_object_or_404(ExamSubject, id=exam_subject_id)

        updated_count = 0
        errors = []

        for entry in attendance_data:
            student_id = entry.get('student_enrollment_id')
            is_present = entry.get('is_present', False)
            is_late = entry.get('is_late', False)
            late_minutes = entry.get('late_minutes', 0)

            try:
                attendance, created = ExamAttendance.objects.update_or_create(
                    student_enrollment_id=student_id,
                    exam_subject=exam_subject,
                    defaults={
                        'is_present': is_present,
                        'is_late': is_late,
                        'late_minutes': late_minutes if is_late else 0,
                        'marked_by': request.user.teacher if hasattr(request.user, 'teacher') else None
                    }
                )
                updated_count += 1
            except Exception as e:
                errors.append({'student_id': student_id, 'error': str(e)})

        return Response({
            'message': f'{updated_count} attendance records processed',
            'updated_count': updated_count,
            'errors': errors
        })


class ExamGradeSystemViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Grade System CRUD operations
    """

    queryset = ExamGradeSystem.objects.all()
    serializer_class = ExamGradeSystemSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['is_active']
    search_fields = ['name']
    ordering_fields = ['name']
    ordering = ['name']


class ExamConfigurationViewSet(viewsets.ModelViewSet):
    """
    ViewSet for Exam Configuration CRUD operations
    """

    queryset = ExamConfiguration.objects.all()
    serializer_class = ExamConfigurationSerializer
    permission_classes = [IsAuthenticated, IsAdminUser]
    filterset_fields = ['academic_year']
    ordering = ['-created_at']


# =====================================================
# DASHBOARD & REPORTING VIEWS
# =====================================================

class ExamDashboardViewSet(viewsets.GenericViewSet):
    """
    ViewSet for Exam Dashboard and Reports
    """

    permission_classes = [IsAuthenticated, IsAdminUser]

    @action(detail=False, methods=['get'])
    def stats(self, request):
        """
        Get exam dashboard statistics
        """
        academic_year_id = request.query_params.get('academic_year')

        queryset = Exam.objects.all()
        if academic_year_id:
            queryset = queryset.filter(academic_year_id=academic_year_id)

        total_exams = queryset.count()
        ongoing_exams = queryset.filter(status='ongoing').count()
        scheduled_exams = queryset.filter(status='scheduled').count()
        completed_exams = queryset.filter(status='completed').count()
        results_published = queryset.filter(status='results_published').count()

        # Total students registered
        total_students_registered = StudentExamRegistration.objects.filter(
            exam__in=queryset,
            is_registered=True
        ).count()

        # Total marks entries
        total_marks_entries = StudentMarks.objects.filter(
            exam_subject__exam__in=queryset
        ).count()

        # Exams by type
        exams_by_type = {}
        for exam in queryset:
            type_name = exam.exam_type.name
            exams_by_type[type_name] = exams_by_type.get(type_name, 0) + 1

        # Exams by class
        exams_by_class = {}
        for exam in queryset:
            class_name = str(exam.academic_class)
            exams_by_class[class_name] = exams_by_class.get(class_name, 0) + 1

        return Response({
            'total_exams': total_exams,
            'ongoing_exams': ongoing_exams,
            'scheduled_exams': scheduled_exams,
            'completed_exams': completed_exams,
            'results_published': results_published,
            'total_students_registered': total_students_registered,
            'total_marks_entries': total_marks_entries,
            'exams_by_type': exams_by_type,
            'exams_by_class': exams_by_class
        })

    @action(detail=False, methods=['get'])
    def performance_report(self, request):
        """
        Get exam performance report
        """
        exam_id = request.query_params.get('exam_id')

        if not exam_id:
            return Response(
                {'error': 'exam_id parameter is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam = get_object_or_404(Exam, id=exam_id)
        results = exam.student_results.all()

        total_students = results.count()
        total_appeared = results.exclude(result_status='absent').count()
        total_passed = results.filter(result_status='pass').count()
        total_failed = results.filter(result_status='fail').count()

        pass_percentage = (total_passed / total_appeared * 100) if total_appeared > 0 else 0

        # Calculate average percentage
        avg_percentage = results.aggregate(avg=Avg('percentage'))['avg'] or 0
        highest_percentage = results.aggregate(max=Coalesce(F('percentage'), 0))['max'] or 0
        lowest_percentage = results.aggregate(min=Coalesce(F('percentage'), 0))['min'] or 0

        # Subject-wise breakdown
        subject_wise = []
        exam_subjects = exam.exam_subjects.all()

        for exam_subject in exam_subjects:
            marks = StudentMarks.objects.filter(
                exam_subject=exam_subject,
                is_absent=False
            )
            subject_marks_count = marks.count()
            subject_passed = marks.filter(
                obtained_marks__gte=F('exam_subject__passing_marks')
            ).count()

            subject_wise.append({
                'subject_name': exam_subject.subject.name,
                'max_marks': exam_subject.max_marks,
                'average_marks': marks.aggregate(avg=Avg('obtained_marks'))['avg'] or 0,
                'highest_marks': marks.aggregate(max=Coalesce(F('obtained_marks'), 0))['max'] or 0,
                'lowest_marks': marks.aggregate(min=Coalesce(F('obtained_marks'), 0))['min'] or 0,
                'pass_percentage': (subject_passed / subject_marks_count * 100) if subject_marks_count > 0 else 0,
                'total_appeared': subject_marks_count,
                'total_passed': subject_passed,
                'total_failed': subject_marks_count - subject_passed
            })

        return Response({
            'exam_id': exam.id,
            'exam_name': exam.name,
            'academic_class': str(exam.academic_class),
            'total_students': total_students,
            'total_appeared': total_appeared,
            'total_passed': total_passed,
            'total_failed': total_failed,
            'pass_percentage': round(pass_percentage, 2),
            'average_percentage': round(avg_percentage, 2),
            'highest_percentage': round(highest_percentage, 2),
            'lowest_percentage': round(lowest_percentage, 2),
            'subject_wise_breakdown': subject_wise
        })

    @action(detail=False, methods=['get'])
    def toppers_list(self, request):
        """
        Get toppers list for an exam
        """
        exam_id = request.query_params.get('exam_id')
        limit = int(request.query_params.get('limit', 10))

        if not exam_id:
            return Response(
                {'error': 'exam_id parameter is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam = get_object_or_404(Exam, id=exam_id)
        results = exam.student_results.filter(result_status='pass').order_by('rank')[:limit]

        toppers = []
        for result in results:
            toppers.append({
                'rank': result.rank,
                'student_name': f"{result.student_enrollment.student.first_name or ''} {result.student_enrollment.student.last_name or ''}".strip(),
                'roll_number': result.student_enrollment.roll_number,
                'total_marks': result.total_marks,
                'percentage': result.percentage,
                'grade': result.overall_grade or 'N/A'
            })

        return Response({
            'exam_name': exam.name,
            'academic_class': str(exam.academic_class),
            'toppers': toppers
        })

    @action(detail=False, methods=['get'])
    def subject_performance(self, request):
        """
        Get subject-wise performance report
        """
        exam_id = request.query_params.get('exam_id')
        subject_id = request.query_params.get('subject_id')

        if not exam_id:
            return Response(
                {'error': 'exam_id parameter is required'},
                status=status.HTTP_400_BAD_REQUEST
            )

        exam_subjects = ExamSubject.objects.filter(exam_id=exam_id)

        if subject_id:
            exam_subjects = exam_subjects.filter(subject_id=subject_id)

        performance_data = []

        for exam_subject in exam_subjects:
            marks = StudentMarks.objects.filter(
                exam_subject=exam_subject,
                is_absent=False
            )

            marks_count = marks.count()
            passed = marks.filter(
                obtained_marks__gte=F('exam_subject__passing_marks')
            ).count()

            performance_data.append({
                'subject_name': exam_subject.subject.name,
                'max_marks': exam_subject.max_marks,
                'passing_marks': exam_subject.passing_marks,
                'average_marks': round(marks.aggregate(avg=Avg('obtained_marks'))['avg'] or 0, 2),
                'highest_marks': marks.aggregate(max=Coalesce(F('obtained_marks'), 0))['max'] or 0,
                'lowest_marks': marks.aggregate(min=Coalesce(F('obtained_marks'), 0))['min'] or 0,
                'pass_percentage': round((passed / marks_count * 100), 2) if marks_count > 0 else 0,
                'total_appeared': marks_count,
                'total_passed': passed,
                'total_failed': marks_count - passed,
                'grade_distribution': self._get_grade_distribution(marks)
            })

        return Response(performance_data)

    def _get_grade_distribution(self, marks_queryset):
        """
        Helper method to get grade distribution for marks
        """
        distribution = {
            'A+': 0, 'A': 0, 'B+': 0, 'B': 0, 'C+': 0, 'C': 0, 'D': 0, 'F': 0
        }

        for mark in marks_queryset:
            if mark.obtained_marks is not None:
                percentage = (mark.obtained_marks / mark.exam_subject.max_marks) * 100
                if percentage >= 90:
                    distribution['A+'] += 1
                elif percentage >= 80:
                    distribution['A'] += 1
                elif percentage >= 70:
                    distribution['B+'] += 1
                elif percentage >= 60:
                    distribution['B'] += 1
                elif percentage >= 50:
                    distribution['C+'] += 1
                elif percentage >= 40:
                    distribution['C'] += 1
                elif percentage >= 35:
                    distribution['D'] += 1
                else:
                    distribution['F'] += 1

        return distribution