from rest_framework.views import APIView
from rest_framework.response import Response
from decimal import Decimal, ROUND_HALF_UP
from rest_framework.pagination import PageNumberPagination
from rest_framework import status
from django.core.cache import cache
import re
from core.utils.ai import explain_reason, moderate_chat_content
from django.db import transaction
from datetime import date, timedelta
from transport.models import (
    TransportType,
    TransportVehicle,
    Route,
    Stop,
    StudentTransport,
    TeacherTransport,
)

from calendar import monthrange
from django.db.models.functions import TruncMonth
import os
import random
import string
import smtplib
from email.message import EmailMessage
from django.conf import settings
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from rest_framework_simplejwt.token_blacklist.models import (
    BlacklistedToken,
    OutstandingToken,
)
from django.db.models import Prefetch, Q, F, Case, When, IntegerField, Sum, Avg
from django.contrib.auth.models import User
from django.db import connections, models
from django.utils import timezone
from django.shortcuts import get_object_or_404
from master_admin.models import School, UserProfile  # Import from master_admin
from master_admin.utils import get_enabled_modules, create_user_session
from people.models import (
    Parent,
    Student,
    StudentParent,
    Teacher,
)  # These are in school DB
from academics.models import (
    AcademicYear,
    StudentEnrollment,
    AcademicClass,
    Section,
    SubjectTeacher,
    StudentSubject,
    StudentSubjectGroup,
    ClassSubject,
    Subject,
)
from academics.year_context import get_request_academic_year_id
from schedules.models import TimeTable, WeekDay
from announcements.models import CommonAnnouncement, StudentAnnouncement
from .serializers import (
    ParentLoginSerializer,
    VerifyOTPSerializer,
    StudentSerializer,
    ParentSerializer,
    ParentTimeTableSerializer,
    ParentAcademicClassForTimetableSerializer,
    ParentStudentListSerializer,
    EnrollmentFeeSummarySerializer,
    WeekDaySerializer,
    StudentDetailSerializer,
    CommonAnnouncementSerializer,
    StudentAnnouncementSerializer,
    ParentLeaveHistorySerializer,
    ParentLeaveDetailSerializer,
    ParentStudentAttendanceSerializer,
    ParentAttendanceOverviewSerializer,
    ParentMonthlyAttendanceSerializer,
    AttendanceCalendarDaySerializer,
    ParentAttendanceStatisticsSerializer,
    ParentLeaveRequestListSerializer,
    ParentLeaveRequestCreateSerializer,
    ParentLeaveRequestDetailSerializer,
    ParentLeaveRequestUpdateSerializer,
    TransportTypeSerializer,
    TransportVehicleSerializer,
    TransportVehicleDetailSerializer,
    RouteSerializer,
    RouteDetailSerializer,
    StopSerializer,
    StudentTransportSerializer,
    StudentTransportDetailSerializer,
)
from fee.models import (
    StudentFeeAssignment,
    FeePayment,
    ClassFeeStructure,
    PaymentFeeComponent,
    TransportFeePayment,
)
from teacher.teacher_fee_views import _is_transport_component
from academics.serializers import (
    AcademicYearSerializer,
    StudentEnrollmentSerializer,
    AcademicClassSerializer,
)

from tasks.models import (
    ClassTask,
    TaskItem,
    StudentTask,
    StudentTaskItem,
    TaskSubmission,
    SpecificStudentTask,
    SpecificStudentTaskAssignment,
    SpecificStudentTaskItem,
)
import logging

from attendance.models import (
    AttendanceSession,
    StudentAttendance,
    AttendanceSummary,
    AttendanceLeave,
)

from django.db import models
from django.db.models import Q, Count, OuterRef, Subquery, Prefetch
from django.db.models.functions import Coalesce
from datetime import datetime, timedelta
import uuid

logger = logging.getLogger(__name__)

from chat.models import (
    ChatRoom,
    Message,
    ChatParticipantTeacher,
    ChatParticipantParent,
    ChatParticipantStudent,
    MessageReadReceipt,
    MessageDelivery,
    ChatMention,
    ChatPoll,
    ChatPollVote,
    ChatInvitation,
)
from chat.utils import broadcast_new_message
from chat.serializers import (
    ChatRoomSerializer,
    MessageSerializer,
    ChatParticipantSettingsSerializer,
    ChatPollSerializer,
    ChatPollVoteSerializer,
    ChatInvitationSerializer,
)
from chat.managers import ChatRoomManager

from .views_exams import (
    ParentUpcomingExamsView,
    ParentPastExamsView,
    ParentExamDetailView,
    ParentExamSubjectDetailView,
    ParentExamResultView,
    ParentStudentExamRegistrationStatusView,
    ParentExamHallTicketView,
    ParentAllExamsSummaryView,
    ParentExamSubjectWiseResultView,
    ParentStudentClassTestsView,
)

# ── ZeptoMail config from settings.py ────────────────────────────────────────
ZEPTO_SMTP_SERVER = settings.ZEPTO_SMTP_SERVER
ZEPTO_PORT = settings.ZEPTO_PORT
ZEPTO_USERNAME = settings.ZEPTO_USERNAME
ZEPTO_PASSWORD = settings.ZEPTO_PASSWORD
ZEPTO_FROM_EMAIL = settings.ZEPTO_FROM_EMAIL
OTP_EXPIRY_MINUTES = settings.OTP_EXPIRY_MINUTES
# ─────────────────────────────────────────────────────────────────────────────


def generate_otp(length=6):
    """Generate a random numeric OTP."""
    return "".join(random.choices(string.digits, k=length))


def send_otp_email(to_email: str, otp: str, user_name: str = "") -> bool:
    """Send OTP email to parent via ZeptoMail SMTP. Returns True on success."""
    from datetime import datetime

    current_year = datetime.now().year
    greeting = f"Dear {user_name}," if user_name else "Dear Parent,"

    html_body = f"""
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
      </head>
      <body style="margin:0; padding:0; background-color:#f0f4ff; font-family: 'Poppins', Arial, sans-serif;">

        <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f0f4ff; padding: 40px 0;">
          <tr>
            <td align="center">
              <table width="580" cellpadding="0" cellspacing="0" style="background-color:#ffffff; border-radius:16px; overflow:hidden; box-shadow: 0 4px 24px rgba(0,80,255,0.10);">

                <!-- HEADER -->
                <tr>
                  <td align="center" style="background: linear-gradient(135deg, #0a0a2e 0%, #0d1b6e 60%, #1a3aad 100%); padding: 36px 40px 28px 40px;">
                    <img src="https://dayscholor.com/assets/logo-CLU4khl2.png"
                         alt="DayScholor"
                         width="200"
                         style="display:block;"
                    />
                  </td>
                </tr>

                <!-- BLUE DIVIDER LINE -->
                <tr>
                  <td style="background: linear-gradient(90deg, #1a3aad, #4f8ef7, #1a3aad); height: 4px; padding:0;"></td>
                </tr>

                <!-- BODY -->
                <tr>
                  <td style="padding: 40px 44px 32px 44px;">

                    <p style="margin:0 0 6px 0; font-size:22px; font-weight:700; color:#0d1b6e; font-family:'Poppins',Arial,sans-serif;">
                      Welcome Back! 👋
                    </p>

                    <p style="margin:0 0 24px 0; font-size:14px; color:#555555; font-family:'Poppins',Arial,sans-serif; line-height:1.6;">
                      {greeting}
                    </p>

                    <p style="margin:0 0 28px 0; font-size:14px; color:#555555; font-family:'Poppins',Arial,sans-serif; line-height:1.8;">
                      Stay connected with your child's education — track attendance,
                      fees, homework, timetables, and school announcements all in one place.
                      Use the code below to securely access your
                      <strong style="color:#0d1b6e;">DayScholor Parent Portal</strong>.
                    </p>

                    <!-- OTP BOX -->
                    <table width="100%" cellpadding="0" cellspacing="0">
                      <tr>
                        <td align="center" style="padding: 0 0 32px 0;">
                          <table cellpadding="0" cellspacing="0">
                            <tr>
                              <td align="center" style="background: linear-gradient(135deg, #0d1b6e, #1a3aad); border-radius:12px; padding: 3px;">
                                <table cellpadding="0" cellspacing="0">
                                  <tr>
                                    <td align="center" style="background:#ffffff; border-radius:10px; padding: 24px 56px;">
                                      <p style="margin:0 0 6px 0; font-size:11px; color:#888888; letter-spacing:3px; text-transform:uppercase; font-family:'Poppins',Arial,sans-serif; font-weight:500;">
                                        Your Login Code
                                      </p>
                                      <p style="margin:0; font-size:42px; font-weight:700; color:#0d1b6e; letter-spacing:14px; font-family:'Poppins',Arial,sans-serif;">
                                        {otp}
                                      </p>
                                    </td>
                                  </tr>
                                </table>
                              </td>
                            </tr>
                          </table>
                        </td>
                      </tr>
                    </table>

                    <!-- VALIDITY BADGE -->
                    <table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom:24px;">
                      <tr>
                        <td align="center">
                          <table cellpadding="0" cellspacing="0">
                            <tr>
                              <td style="background:#f0f4ff; border-left: 4px solid #1a3aad; border-radius: 4px; padding: 12px 20px;">
                                <p style="margin:0; font-size:13px; color:#0d1b6e; font-family:'Poppins',Arial,sans-serif;">
                                  ⏱ &nbsp;This code expires in <strong>{OTP_EXPIRY_MINUTES} minutes</strong>. Enter it promptly to login.
                                </p>
                              </td>
                            </tr>
                          </table>
                        </td>
                      </tr>
                    </table>

                    <p style="margin:0 0 8px 0; font-size:13px; color:#777777; font-family:'Poppins',Arial,sans-serif; line-height:1.6;">
                      🔒 &nbsp;For your security, never share this code with anyone.
                      <strong style="color:#0d1b6e;">DayScholor</strong> will never call or message you asking for this code.
                    </p>

                    <hr style="border:none; border-top:1px solid #eef0f8; margin: 28px 0;">

                    <p style="margin:0; font-size:12px; color:#aaaaaa; font-family:'Poppins',Arial,sans-serif; line-height:1.6;">
                      Didn't request this code? You can safely ignore this email —
                      your account remains secure and no action is needed.
                    </p>

                  </td>
                </tr>

                <!-- FOOTER -->
                <tr>
                  <td style="background: linear-gradient(135deg, #0a0a2e 0%, #0d1b6e 100%); padding: 24px 44px;">
                    <table width="100%" cellpadding="0" cellspacing="0">
                      <tr>
                        <td>
                          <p style="margin:0 0 4px 0; font-size:13px; color:#a0b4ff; font-family:'Poppins',Arial,sans-serif; font-weight:600;">
                            DayScholor
                          </p>
                          <p style="margin:0; font-size:11px; color:#6680cc; font-family:'Poppins',Arial,sans-serif;">
                            © {current_year} DayScholor. All rights reserved.
                          </p>
                        </td>
                        <td align="right">
                          <p style="margin:0; font-size:11px; color:#6680cc; font-family:'Poppins',Arial,sans-serif;">
                            dayscholor.com
                          </p>
                        </td>
                      </tr>
                    </table>
                  </td>
                </tr>

              </table>
            </td>
          </tr>
        </table>

      </body>
    </html>
    """

    msg = EmailMessage()
    msg["Subject"] = "Your DayScholor Login Code"
    msg["From"] = f"DayScholor <{ZEPTO_FROM_EMAIL}>"
    msg["To"] = to_email
    msg.set_content(
        f"{greeting}\n\n"
        f"Welcome back to DayScholor!\n\n"
        f"Your login code is: {otp}\n\n"
        f"Valid for {OTP_EXPIRY_MINUTES} minutes. Do not share this with anyone.\n\n"
        f"Didn't request this? You can safely ignore this email.\n\n"
        f"© {current_year} DayScholor — dayscholor.com"
    )
    msg.add_alternative(html_body, subtype="html")

    try:
        with smtplib.SMTP(ZEPTO_SMTP_SERVER, ZEPTO_PORT) as server:
            server.ehlo()
            server.starttls()
            server.ehlo()
            from master_admin.credentials import get_credential

            server.login(
                ZEPTO_USERNAME,
                get_credential("ZEPTO_PASSWORD", ZEPTO_PASSWORD),
            )
            server.send_message(msg)
        logger.info(f"OTP email sent successfully to {to_email}")
        return True
    except smtplib.SMTPAuthenticationError as e:
        logger.error(f"SMTP Authentication failed: {e}")
        return False
    except smtplib.SMTPException as e:
        logger.error(f"SMTP error: {e}")
        return False
    except Exception as e:
        logger.error(f"Unexpected error sending OTP: {e}")
        return False


class ParentLoginView(APIView):
    """
    API endpoint for parent login using email.
    Generates a real OTP and sends it via ZeptoMail SMTP.
    This queries the MASTER DB for User and School info.
    """

    permission_classes = [AllowAny]

    def post(self, request):
        serializer = ParentLoginSerializer(data=request.data)

        if serializer.is_valid():
            email = serializer.validated_data["email"]

            try:
                # 1. Check if user exists in MASTER DB
                user = User.objects.get(email=email)

                # 2. Check if user is staff - block staff accounts from parent login
                if user.is_staff:
                    logger.warning(f"Staff login attempt blocked for email: {email}")
                    return Response(
                        {
                            "success": False,
                            "message": "Access denied. Staff accounts cannot login as parents.",
                        },
                        status=status.HTTP_403_FORBIDDEN,
                    )

                # 3. Get user profile from MASTER DB
                try:
                    profile = UserProfile.objects.get(user=user)
                except UserProfile.DoesNotExist:
                    return Response(
                        {"success": False, "message": "User profile not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

                # 4. Get school info from MASTER DB
                if not profile.school:
                    return Response(
                        {
                            "success": False,
                            "message": "No school assigned to this user",
                        },
                        status=status.HTTP_404_NOT_FOUND,
                    )

                school = profile.school

                # 5. Generate OTP and send via ZeptoMail
                otp = "111111"
                user_name = f"{user.first_name} {user.last_name}".strip()
                # email_sent = send_otp_email(email, otp, user_name)
                email_sent = 1
                if not email_sent:
                    logger.error(f"Failed to send OTP email to {email}")
                    return Response(
                        {
                            "success": False,
                            "message": "Failed to send OTP. Please try again.",
                        },
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                    )

                # 6. Store OTP + school info in session
                request.session["pending_login"] = {
                    "email": email,
                    "user_id": user.id,
                    "school_id": school.id,
                    "school_db": school.db_name,
                    "otp": otp,  # stored server-side only
                    "otp_created_at": timezone.now().isoformat(),
                }

                logger.info(
                    f"OTP sent - User: {email}, request.session['pending_login'], School: {school.name}, DB: {school.db_name}"
                )

                return Response(
                    {
                        "success": True,
                        "message": "OTP sent successfully to your email",
                        "email": email,
                        "school": school.name,
                        # NOTE: otp is NOT returned in the response (security)
                    },
                    status=status.HTTP_200_OK,
                )

            except User.DoesNotExist:
                return Response(
                    {"success": False, "message": "No account found with this email"},
                    status=status.HTTP_404_NOT_FOUND,
                )

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class VerifyOTPView(APIView):
    """
    API endpoint to verify OTP and authenticate parent.
    Validates OTP from session (server-side) and checks expiry.
    Returns JWT tokens on success.
    """

    permission_classes = [AllowAny]

    def post(self, request):
        serializer = VerifyOTPSerializer(data=request.data)

        if serializer.is_valid():
            email = serializer.validated_data["email"]
            otp = serializer.validated_data["otp"]

            try:
                # 1. Get pending login info from session
                pending = request.session.get("pending_login")
                if not pending or pending.get("email") != email:
                    return Response(
                        {
                            "success": False,
                            "message": "Login session expired. Please login again.",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                # 2. Check OTP expiry
                from datetime import datetime, timezone as dt_timezone

                created_time = datetime.fromisoformat(pending["otp_created_at"])
                if created_time.tzinfo is None:
                    created_time = created_time.replace(tzinfo=dt_timezone.utc)
                elapsed_minutes = (timezone.now() - created_time).total_seconds() / 60

                if elapsed_minutes > OTP_EXPIRY_MINUTES:
                    del request.session["pending_login"]
                    logger.warning(
                        f"OTP expired for {email} after {elapsed_minutes:.1f} minutes"
                    )
                    return Response(
                        {
                            "success": False,
                            "message": "OTP has expired. Please login again.",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                # 3. Verify OTP value
                if otp != pending.get("otp", ""):
                    logger.warning(f"Invalid OTP attempt for {email}")
                    return Response(
                        {"success": False, "message": "Invalid OTP. Please try again."},
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                # 4. Get user from MASTER DB
                user = User.objects.get(id=pending["user_id"])

                # 5. Get school info from MASTER DB
                school = School.objects.get(id=pending["school_id"])

                # 6. Configure school DB connection
                school_db_alias = "school"
                connections[school_db_alias].settings_dict.update(
                    {
                        "NAME": school.db_name,
                        "USER": school.db_user,
                        "PASSWORD": school.db_password,
                        "HOST": school.db_host,
                        "PORT": str(school.db_port),  # ✅ FIXED HERE
                    }
                )
                connections[school_db_alias].close()

                # 7. Verify parent exists in SCHOOL DB
                try:
                    parent = Parent.objects.using(school_db_alias).get(
                        external_user_id=user.id
                    )
                except Parent.DoesNotExist:
                    return Response(
                        {
                            "success": False,
                            "message": "Parent profile not found in school database",
                        },
                        status=status.HTTP_404_NOT_FOUND,
                    )

                # 8. Generate JWT tokens with claims
                refresh = RefreshToken.for_user(user)
                refresh["school_id"] = school.id
                refresh["school_db"] = school.db_name
                refresh["user_type"] = "parent"
                refresh["parent_id"] = parent.id

                # 9. Record device session
                session_obj = create_user_session(
                    request=request,
                    user=user,
                    school=school,
                    user_type="PARENT",
                    external_id=parent.id,
                    refresh_token=refresh,
                )

                # Embed session_token in JWT so the auth layer can invalidate it instantly
                if session_obj:
                    refresh["session_token"] = session_obj

                # 10. Clear pending session
                del request.session["pending_login"]

                logger.info(f"OTP verified successfully for {email}")

                return Response(
                    {
                        "success": True,
                        "message": "OTP verified successfully",
                        "access_token": str(refresh.access_token),
                        "refresh_token": str(refresh),
                        "expires_in": 86400,
                        "enabled_modules": get_enabled_modules(school),
                        "session_token": str(session_obj) if session_obj else None,
                    },
                    status=status.HTTP_200_OK,
                )

            except (User.DoesNotExist, School.DoesNotExist):
                return Response(
                    {"success": False, "message": "Authentication failed"},
                    status=status.HTTP_404_NOT_FOUND,
                )

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ParentStudentsView(APIView):
    """
    API endpoint to get minimal student information for parent
    Returns only id, external_user_id, and student name
    Also includes current academic year and enrollment status
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        academic_year_id = get_request_academic_year_id(request)

        try:
            # Get parent from SCHOOL DB using external_user_id
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get all active student-parent relationships
            student_parents = parent.student_parents.filter(
                is_active=True
            ).select_related("student")

            students_data = []
            for sp in student_parents:
                student = sp.student

                # Get enrollment details for this student - filtered by academic year if provided
                enrollment_info = self._get_student_enrollment(student.id, academic_year_id)

                students_data.append(
                    {
                        "id": student.id,
                        "external_user_id": student.external_user_id,
                        "full_name": student.full_name
                        or f"{student.first_name} {student.last_name}".strip(),
                        "relationship": sp.relationship,
                        "is_primary_contact": sp.is_primary_contact,
                        "can_pickup": sp.can_pickup,
                        "current_academic_year": enrollment_info,
                        "profile_image": (
                            student.profile_image.url if student.profile_image else None
                        ),
                    }
                )

            return Response(
                {
                    "success": True,
                    "count": len(students_data),
                    "students": students_data,
                },
                status=status.HTTP_200_OK,
            )

        except Parent.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Parent profile not found in school database",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

    def _get_student_enrollment(self, student_id, academic_year_id=None):
        """Get enrollment details for a student, optionally filtered by academic year"""
        try:
            qs = StudentEnrollment.objects.filter(student_id=student_id, is_active=True)
            if academic_year_id:
                qs = qs.filter(academic_class__academic_year_id=academic_year_id)
            enrollment = (
                qs.select_related(
                    "academic_class",
                    "academic_class__standard",
                    "academic_class__section",
                    "academic_class__academic_year",
                )
                # Most recent academic year first — a student can have
                # multiple is_active=True enrollment rows across years
                # (one per year), and without ordering .first() can return
                # a stale prior-year enrollment.
                .order_by("-academic_class__academic_year__start_date")
                .first()
            )

            if enrollment:
                academic_class = enrollment.academic_class
                academic_year = academic_class.academic_year if academic_class else None

                return {
                    "enrolled": True,
                    "academic_year_id": academic_year.id if academic_year else None,
                    "academic_year_name": academic_year.name if academic_year else None,
                    "academic_year_code": academic_year.code if academic_year else None,
                    "class_id": academic_class.id if academic_class else None,
                    "class_name": str(academic_class) if academic_class else None,
                    "standard_id": (
                        academic_class.standard.id
                        if academic_class and academic_class.standard
                        else None
                    ),
                    "standard_name": (
                        academic_class.standard.name
                        if academic_class and academic_class.standard
                        else None
                    ),
                    "section_id": (
                        academic_class.section.id
                        if academic_class and academic_class.section
                        else None
                    ),
                    "section_name": (
                        academic_class.section.name
                        if academic_class and academic_class.section
                        else None
                    ),
                    "roll_number": enrollment.roll_number,
                    "enrollment_id": enrollment.id,
                }
            else:
                return {
                    "enrolled": False,
                    "message": "Student not enrolled in any active enrollment",
                }
        except Exception as e:
            logger.error(f"Error fetching enrollment for student {student_id}: {e}")
            return None


class ParentAcademicYearsView(APIView):
    """
    List academic years available to the parent's students (years their
    children have enrollments in), newest first. Powers the academic-year
    switcher in the app's Settings screen.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
            student_ids = list(
                parent.student_parents.filter(is_active=True).values_list(
                    "student_id", flat=True
                )
            )
            year_ids = (
                StudentEnrollment.objects.filter(student_id__in=student_ids)
                .values_list("academic_class__academic_year_id", flat=True)
                .distinct()
            )
            years = AcademicYear.objects.filter(id__in=year_ids).order_by(
                "-start_date"
            )
            # No enrollments yet — show every year so the picker still works
            if not years.exists():
                years = AcademicYear.objects.all().order_by("-start_date")

            active = AcademicYear.objects.filter(is_active=True).first()
            data = AcademicYearSerializer(years, many=True).data
            return Response(
                {
                    "success": True,
                    "count": len(data),
                    "active_year_id": active.id if active else None,
                    "years": data,
                },
                status=status.HTTP_200_OK,
            )
        except Parent.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Parent profile not found in school database",
                },
                status=status.HTTP_404_NOT_FOUND,
            )


class StudentDetailView(APIView):
    """
    API endpoint to get detailed information of a specific student
    Includes academic class, section, enrollment information,
    total pending fee amount, and today's timetable
    Uses existing serializers for consistent data formatting
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        student_id = request.query_params.get("student_id")
        academic_year_id = get_request_academic_year_id(request)

        if not student_id:
            return Response(
                {"success": False, "message": "student_id parameter is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Get parent from SCHOOL DB using external_user_id
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Check if student is linked to this parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access to this student",
                    },
                    status=status.HTTP_404_NOT_FOUND,
                )

            student = student_parent.student

            # Resolve academic year: use the passed ID if provided, otherwise auto-detect
            strict_year = bool(academic_year_id)
            if academic_year_id:
                current_academic_year = AcademicYear.objects.filter(
                    id=academic_year_id
                ).first()
                if not current_academic_year:
                    return Response(
                        {"success": False, "message": "Academic year not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
            else:
                current_academic_year = self._get_current_academic_year()

            # Get current enrollment details - strict=True skips fallback when year is explicit
            current_enrollment = self._get_current_enrollment(
                student_id, current_academic_year, strict=strict_year
            )

            # Get all enrollment history
            enrollment_history = self._get_enrollment_history(student_id)

            # Get total pending fee amount - FIXED TO USE ENROLLMENT ID
            total_pending_fee = 0.0
            if current_enrollment:
                total_pending_fee = self._get_total_pending_fee(current_enrollment)
                logger.info(
                    f"Total pending fee for student {student_id}: {total_pending_fee}"
                )

            # Get attendance percentage for the academic year
            attendance_percentage = self._get_attendance_percentage(
                current_enrollment, current_academic_year
            )

            # Get today's timetable (complete array)
            today_timetable = self._get_today_timetable(current_enrollment, request)
            today_periods_count = len(today_timetable)

            # Get homework counts
            homework_counts = self._get_homework_counts(student_id, current_enrollment)
            pending_homework_count = homework_counts["pending_homework_count"]
            today_homework_count = homework_counts["today_homework_count"]

            # Get unread message count for this student's chat
            unread_message_count = self._get_unread_message_count(student)

            # Serialize the data using existing serializers
            student_serializer = StudentSerializer(student)
            academic_year_serializer = (
                AcademicYearSerializer(current_academic_year)
                if current_academic_year
                else None
            )
            current_enrollment_serializer = (
                StudentEnrollmentSerializer(current_enrollment)
                if current_enrollment
                else None
            )
            enrollment_history_serializer = StudentEnrollmentSerializer(
                enrollment_history, many=True
            )

            # Build response
            student_data = student_serializer.data
            student_data["relationship_info"] = {
                "relationship": student_parent.relationship,
                "is_primary_contact": student_parent.is_primary_contact,
                "can_pickup": student_parent.can_pickup,
            }

            if academic_year_serializer:
                student_data["current_academic_year"] = academic_year_serializer.data

            if current_enrollment_serializer:
                student_data["current_enrollment"] = current_enrollment_serializer.data

            student_data["enrollment_history"] = enrollment_history_serializer.data

            # Add total pending fee amount
            student_data["total_pending_fee"] = total_pending_fee

            # Add attendance percentage
            student_data["attendance_percentage"] = attendance_percentage

            # Add today's timetable (complete array) and count
            student_data["today_timetable"] = today_timetable
            student_data["today_periods_count"] = today_periods_count

            # Add homework counts
            student_data["pending_homework_count"] = pending_homework_count
            student_data["today_homework_count"] = today_homework_count

            # Add unread message count
            student_data["unread_message_count"] = unread_message_count

            # Add school name
            school = getattr(request, 'school', None)
            student_data["school_name"] = school.name if school else None

            return Response(
                {"success": True, "student": student_data}, status=status.HTTP_200_OK
            )

        except Parent.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Parent profile not found in school database",
                },
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentDetailView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {
                    "success": False,
                    "message": "An error occurred while fetching student details",
                },
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_current_academic_year(self):
        """Get the currently active academic year"""
        try:
            today = timezone.now().date()
            academic_year = AcademicYear.objects.filter(
                is_active=True, start_date__lte=today, end_date__gte=today
            ).first()

            if academic_year:
                return academic_year
            else:
                # Fallback to any active academic year
                return AcademicYear.objects.filter(is_active=True).first()
        except Exception as e:
            logger.error(f"Error fetching current academic year: {e}")
            return None

    def _get_total_pending_fee(self, current_enrollment):
        """
        Get total pending fee amount - FIXED to use PaymentFeeComponent for accurate paid amount calculation
        """
        if not current_enrollment:
            logger.info("No current enrollment provided")
            return 0.0

        try:
            from django.db.models import Sum
            from decimal import Decimal

            logger.info(
                f"Starting fee calculation for enrollment ID: {current_enrollment.id}"
            )

            # Check enrollment details
            if not current_enrollment.academic_class:
                logger.info("No academic_class in enrollment!")
                return 0.0

            class_id = current_enrollment.academic_class.id
            logger.info(f"Class ID: {class_id}")

            # Get academic year
            academic_year = current_enrollment.academic_class.academic_year
            if not academic_year:
                logger.info("No academic_year found for class!")
                return 0.0

            logger.info(f"Academic Year: {academic_year.name}")

            # Get active terms
            active_terms = academic_year.terms.filter(is_active=True)
            logger.info(f"Active terms count: {active_terms.count()}")

            if not active_terms.exists():
                logger.info("No active terms found!")
                return 0.0

            # Use Decimal for calculations to match database values
            total_pending = Decimal("0.0")

            for term in active_terms:
                logger.info(f"Processing term: {term.name}")

                # Get class fee structures for this term
                class_fee_structures = ClassFeeStructure.objects.filter(
                    academic_class_id=class_id, academic_term=term, is_active=True
                ).select_related("fee_component")

                logger.info(
                    f"Class fee structures for term {term.id}: {class_fee_structures.count()}"
                )

                for class_fee in class_fee_structures:
                    logger.info(
                        f"Processing class fee ID: {class_fee.id} - {class_fee.fee_component.name if class_fee.fee_component else 'Unknown'}"
                    )

                    # Check for student assignment
                    student_assignment = StudentFeeAssignment.objects.filter(
                        enrollment=current_enrollment,
                        class_fee_structure=class_fee,
                        is_active=True,
                    ).first()

                    if student_assignment:
                        # If there's a student assignment, use it to calculate fee amount
                        fee_amount = student_assignment.calculate_final_amount()
                        logger.info(
                            f"Found student assignment, final amount: {fee_amount}"
                        )

                        # Get paid amount from PaymentFeeComponent through the assignment
                        paid = PaymentFeeComponent.objects.filter(
                            student_fee_assignment=student_assignment
                        ).aggregate(total=Sum("amount_paid"))["total"] or Decimal("0.0")

                        logger.info(f"Paid amount from PaymentFeeComponent: {paid}")

                    else:
                        # No student assignment, use class fee amount
                        fee_amount = class_fee.amount
                        logger.info(
                            f"No student assignment, using class fee amount: {fee_amount}"
                        )

                        # Get paid amount from PaymentFeeComponent linked to this enrollment and class fee
                        paid = PaymentFeeComponent.objects.filter(
                            payment__student_enrollment=current_enrollment,
                            class_fee_structure=class_fee,
                        ).aggregate(total=Sum("amount_paid"))["total"] or Decimal("0.0")

                        logger.info(f"Paid amount from PaymentFeeComponent: {paid}")

                    # Ensure both are Decimal for calculation
                    if not isinstance(fee_amount, Decimal):
                        fee_amount = Decimal(str(fee_amount))
                    if not isinstance(paid, Decimal):
                        paid = Decimal(str(paid))

                    pending = max(Decimal("0.0"), fee_amount - paid)
                    logger.info(
                        f"Fee amount: {fee_amount}, Paid: {paid}, Pending: {pending}"
                    )

                    # Now both are Decimal, so this works
                    total_pending += pending

            # Transport/bus fee (StudentTransport.fee_amount) — only counted
            # here if not already tracked via a transport-named FeeComponent
            # above (avoids double counting). Same guard as StudentFeeDetailsView.
            transport = StudentTransport.objects.filter(
                enrollment=current_enrollment, is_active=True
            ).first()
            if transport:
                all_class_fees = ClassFeeStructure.objects.filter(
                    academic_class_id=class_id, is_active=True
                ).select_related("fee_component")
                transport_tracked = any(
                    _is_transport_component(cf.fee_component) for cf in all_class_fees
                )
                if not transport_tracked:
                    transport_fee_amount = Decimal(str(transport.fee_amount or 0))
                    transport_paid = TransportFeePayment.objects.filter(
                        student_transport=transport
                    ).aggregate(total=Sum("amount_paid"))["total"] or Decimal("0.0")
                    if not isinstance(transport_paid, Decimal):
                        transport_paid = Decimal(str(transport_paid))
                    transport_pending = max(
                        Decimal("0.0"), transport_fee_amount - transport_paid
                    )
                    logger.info(
                        f"Transport fee: {transport_fee_amount}, paid: {transport_paid}, "
                        f"pending: {transport_pending}"
                    )
                    total_pending += transport_pending

            # Convert to float at the end for the API response
            result = float(total_pending)
            logger.info(f"Final total pending: {result}")
            return round(result, 2)

        except Exception as e:
            logger.error(f"Error in _get_total_pending_fee: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return 0.0

    def _get_current_enrollment(self, student_id, academic_year, strict=False):
        """Get enrollment for a student in the given academic year.
        strict=True: return None if not enrolled in that exact year (no fallback).
        strict=False: fall back to any active enrollment when year-specific one is missing.
        """
        try:
            if not academic_year:
                logger.info(f"No active academic year found for student {student_id}")
                return None

            logger.info(
                f"Finding enrollment for student {student_id} in academic year {academic_year.id}"
            )

            enrollment = (
                StudentEnrollment.objects.filter(
                    student_id=student_id,
                    academic_class__academic_year=academic_year,
                    is_active=True,
                )
                .select_related(
                    "academic_class",
                    "academic_class__standard",
                    "academic_class__section",
                    "academic_class__academic_year",
                )
                .first()
            )

            if enrollment:
                logger.info(
                    f"Found enrollment {enrollment.id} for student {student_id}"
                )
                logger.info(
                    f"Class ID: {enrollment.academic_class_id if enrollment.academic_class else None}"
                )
            elif not strict:
                logger.info(
                    f"No enrollment for student {student_id} in academic year {academic_year.id}, using fallback"
                )
                enrollment = (
                    StudentEnrollment.objects.filter(
                        student_id=student_id,
                        is_active=True,
                    )
                    .select_related(
                        "academic_class",
                        "academic_class__standard",
                        "academic_class__section",
                        "academic_class__academic_year",
                    )
                    .first()
                )
                if enrollment:
                    logger.info(
                        f"Found fallback enrollment {enrollment.id} for student {student_id}"
                    )
            else:
                logger.info(
                    f"No enrollment for student {student_id} in academic year {academic_year.id} (strict mode, no fallback)"
                )

            return enrollment

        except Exception as e:
            logger.error(
                f"Error fetching current enrollment for student {student_id}: {e}"
            )
            import traceback

            logger.error(traceback.format_exc())
            return None

    def _get_enrollment_history(self, student_id):
        """Get all enrollment history for a student across academic years"""
        try:
            enrollments = (
                StudentEnrollment.objects.filter(student_id=student_id, is_active=True)
                .select_related(
                    "academic_class",
                    "academic_class__standard",
                    "academic_class__section",
                    "academic_class__academic_year",
                )
                .order_by("-academic_class__academic_year__start_date")
                .all()
            )

            return enrollments
        except Exception as e:
            logger.error(
                f"Error fetching enrollment history for student {student_id}: {e}"
            )
            return []

    def _get_attendance_percentage(self, current_enrollment, academic_year):
        """
        Get overall attendance percentage for the student
        """
        if not current_enrollment:
            logger.info("No enrollment found for attendance calculation")
            return 0.0

        try:
            from attendance.models import StudentAttendance, AttendanceSummary

            # Try to get from AttendanceSummary first (most efficient)
            summaries = AttendanceSummary.objects.filter(enrollment=current_enrollment)

            if summaries.exists():
                # Calculate from summaries
                total_working_days = (
                    summaries.aggregate(total=models.Sum("total_working_days"))["total"]
                    or 0
                )

                present_days = (
                    summaries.aggregate(total=models.Sum("present_days"))["total"] or 0
                )

                late_days = (
                    summaries.aggregate(total=models.Sum("late_days"))["total"] or 0
                )

                half_days = (
                    summaries.aggregate(total=models.Sum("half_days"))["total"] or 0
                )

                if total_working_days == 0:
                    return 0.0

                effective_present = present_days + late_days + (half_days * 0.5)
                attendance_percentage = round(
                    (effective_present / total_working_days * 100), 2
                )

                logger.info(
                    f"Attendance for enrollment {current_enrollment.id}: {attendance_percentage}%"
                )
                return attendance_percentage

            # Fallback: Direct from StudentAttendance records
            attendance_records = StudentAttendance.objects.filter(
                enrollment=current_enrollment, is_active=True
            ).select_related("session")

            if not attendance_records.exists():
                logger.info(
                    f"No attendance records found for enrollment {current_enrollment.id}"
                )
                return 0.0

            # Count distinct sessions
            distinct_sessions = attendance_records.values("session").distinct().count()

            if distinct_sessions == 0:
                return 0.0

            # Count sessions by status
            present_sessions = (
                attendance_records.filter(status="PRESENT")
                .values("session")
                .distinct()
                .count()
            )
            late_sessions = (
                attendance_records.filter(status="LATE")
                .values("session")
                .distinct()
                .count()
            )
            half_day_sessions = (
                attendance_records.filter(status="HALF_DAY")
                .values("session")
                .distinct()
                .count()
            )

            effective_present = (
                present_sessions + late_sessions + (half_day_sessions * 0.5)
            )
            attendance_percentage = round(
                (effective_present / distinct_sessions * 100), 2
            )

            logger.info(
                f"Attendance for enrollment {current_enrollment.id}: {attendance_percentage}%"
            )
            return attendance_percentage

        except Exception as e:
            logger.error(f"Error calculating attendance percentage: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return 0.0

    def _get_today_timetable(self, current_enrollment, request):
        """
        Get today's complete timetable periods for the student's class
        Returns array of periods with subject names, timings, teacher names, etc.
        """
        if not current_enrollment:
            logger.info("No current enrollment found for timetable")
            return []

        if not current_enrollment.academic_class:
            logger.info("Student has no academic class assigned")
            return []

        try:
            from django.utils import timezone
            from schedules.models import WeekDay, TimeTable
            from .serializers import ParentTimeTableSerializer

            today = timezone.now().date()

            # Get today's weekday (0=Monday, 6=Sunday in Python)
            weekday_map = {
                0: "MON",
                1: "TUE",
                2: "WED",
                3: "THU",
                4: "FRI",
                5: "SAT",
                6: "SUN",
            }
            today_weekday_code = weekday_map[today.weekday()]

            # Get weekday from database
            try:
                weekday = WeekDay.objects.get(
                    day_code=today_weekday_code, is_active=True
                )
            except WeekDay.DoesNotExist:
                logger.warning(f"No weekday found for code: {today_weekday_code}")
                return []

            # Check if school is open on this day
            if not weekday.is_open:
                logger.info(f"School is closed on {weekday.day_name}")
                return []

            # Get timetables for this class and weekday
            # Use academic_class (the ForeignKey) not academic_class_id
            timetables = (
                TimeTable.objects.filter(
                    academic_class=current_enrollment.academic_class,
                    weekday=weekday,
                    is_active=True,
                )
                .select_related(
                    "subject", "subject_teacher__teacher", "substitute_teacher"
                )
                .order_by("period_number")
            )

            logger.info(
                f"Found {timetables.count()} periods for class {current_enrollment.academic_class.id} on {weekday.day_name}"
            )

            # Serialize using the same serializer as WeekdayTimetableView
            serializer = ParentTimeTableSerializer(
                timetables, many=True, context={"request": request}
            )

            return serializer.data

        except Exception as e:
            logger.error(f"Error in _get_today_timetable: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return []

    def _get_homework_counts(self, student_id, current_enrollment):
        """
        Get pending homework count and today's homework count for a student
        """
        pending_homework_count = 0
        today_homework_count = 0

        try:
            from django.db.models import Count, Q
            from django.utils import timezone

            today = timezone.now().date()

            # If no enrollment, return zeros
            if not current_enrollment or not current_enrollment.academic_class:
                logger.info(f"No enrollment or class found for student {student_id}")
                return {"pending_homework_count": 0, "today_homework_count": 0}

            class_id = current_enrollment.academic_class.id

            # Get class tasks (homework)
            class_tasks_qs = ClassTask.objects.filter(
                academic_class_id=class_id,
                is_active=True,
                # is_published=True  # Uncomment if you have this field
            )

            # Get specific tasks assigned to this student
            specific_tasks_qs = SpecificStudentTaskAssignment.objects.filter(
                student_id=student_id,
                specific_task__is_active=True,
            ).select_related("specific_task")

            # Get all task IDs from both querysets
            class_task_ids = list(class_tasks_qs.values_list("id", flat=True))

            # Check submission status for class tasks
            # Pending homework: Not submitted OR submitted but not graded/completed
            # For this example, we'll consider NOT_SUBMITTED as pending
            # Adjust based on your TaskSubmission model

            # Get submissions for class tasks
            if current_enrollment and class_task_ids:
                class_submissions = TaskSubmission.objects.filter(
                    enrollment=current_enrollment, task_id__in=class_task_ids
                ).values_list("task_id", "status")

                submitted_class_task_ids = set()
                for task_id, status in class_submissions:
                    # If status is SUBMITTED or GRADED, consider it completed
                    if status in ["SUBMITTED", "GRADED", "COMPLETED"]:
                        submitted_class_task_ids.add(task_id)

                # Pending class tasks: tasks not submitted
                pending_class_tasks = [
                    tid for tid in class_task_ids if tid not in submitted_class_task_ids
                ]
            else:
                pending_class_tasks = class_task_ids if class_task_ids else []

            # Check submission status for specific tasks
            pending_specific_tasks = []
            if specific_tasks_qs:
                for assignment in specific_tasks_qs:
                    # If status is PENDING or OVERDUE, consider it pending homework
                    if assignment.status in ["PENDING", "OVERDUE"]:
                        pending_specific_tasks.append(assignment.id)

            # Calculate pending homework count
            pending_homework_count = len(pending_class_tasks) + len(
                pending_specific_tasks
            )

            # Calculate today's homework count (tasks due today)
            # Class tasks due today
            today_class_tasks = class_tasks_qs.filter(due_date=today).count()

            # Specific tasks due today
            today_specific_tasks = specific_tasks_qs.filter(
                specific_task__due_date=today
            ).count()

            today_homework_count = today_class_tasks + today_specific_tasks

            logger.info(
                f"Student {student_id} - Pending: {pending_homework_count}, "
                f"Today: {today_homework_count}, "
                f"Class pending: {len(pending_class_tasks)}, "
                f"Specific pending: {len(pending_specific_tasks)}"
            )

        except Exception as e:
            logger.error(
                f"Error in _get_homework_counts for student {student_id}: {str(e)}"
            )
            import traceback

            logger.error(traceback.format_exc())

        return {
            "pending_homework_count": pending_homework_count,
            "today_homework_count": today_homework_count,
        }

    def _get_unread_message_count(self, student):
        """Get total unread chat message count for this student across all their rooms"""
        try:
            student_id_str = str(student.id)
            rooms = ChatRoom.objects.filter(students=student, is_active=True)
            total_unread = 0
            for room in rooms:
                messages = Message.objects.filter(room=room, is_deleted=False)
                read_messages = MessageReadReceipt.objects.filter(
                    user_type="student", user_id=student_id_str, message__in=messages
                ).values_list("message_id", flat=True)
                unread = (
                    messages.exclude(id__in=read_messages)
                    .exclude(sender_type="student", sender_id=student_id_str)
                    .count()
                )
                total_unread += unread
            return total_unread
        except Exception as e:
            logger.error(
                f"Error getting unread message count for student {student.id}: {e}"
            )
            return 0


class StudentListView(APIView):
    """
    API endpoint to get list of all students linked to the parent
    Uses StudentParent through model - KEPT FOR BACKWARD COMPATIBILITY
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get parent from SCHOOL DB using external_user_id
            parent = Parent.objects.get(external_user_id=request.user.id)

            # ✅ CORRECT: Get students through StudentParent relationship
            student_parents = parent.student_parents.filter(is_active=True)
            students = [sp.student for sp in student_parents]

            # Or using select_related for efficiency:
            # students = [sp.student for sp in parent.student_parents.select_related('student').filter(is_active=True)]

            serializer = StudentSerializer(students, many=True)

            # Also include relationship details
            student_details = []
            for sp in student_parents:
                student_data = StudentSerializer(sp.student).data
                student_data["relationship"] = sp.relationship
                student_data["is_primary_contact"] = sp.is_primary_contact
                student_data["can_pickup"] = sp.can_pickup
                student_details.append(student_data)

            return Response(
                {"success": True, "count": len(students), "students": student_details},
                status=status.HTTP_200_OK,
            )

        except Parent.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Parent profile not found in school database",
                },
                status=status.HTTP_404_NOT_FOUND,
            )


class ParentProfileView(APIView):
    """
    API endpoint to get and update parent profile
    This queries the SCHOOL DB (middleware routes to correct school DB)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get parent from SCHOOL DB (middleware handles DB routing)
            parent = Parent.objects.get(external_user_id=request.user.id)
            serializer = ParentSerializer(parent)

            school = getattr(request, "school", None)
            modules = get_enabled_modules(school) if school else []

            return Response(
                {
                    "success": True,
                    "profile": serializer.data,
                    "enabled_modules": modules,
                },
                status=status.HTTP_200_OK,
            )

        except Parent.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Parent profile not found in school database",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

    def patch(self, request):
        """Partially update parent profile in SCHOOL DB"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
            serializer = ParentSerializer(parent, data=request.data, partial=True)

            if serializer.is_valid():
                serializer.save()
                return Response(
                    {
                        "success": True,
                        "message": "Profile updated successfully",
                        "profile": serializer.data,
                    },
                    status=status.HTTP_200_OK,
                )

            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )


class ParentLogoutView(APIView):
    """
    🔥 PROPER LOGOUT - Blacklists ALL refresh tokens for this user
    Also invalidates current access token by blacklisting its refresh token
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        user = request.user

        try:
            # Get the refresh token from request body if provided
            refresh_token = request.data.get("refresh")

            tokens_blacklisted = 0

            # Option 1: Blacklist specific refresh token from request
            if refresh_token:
                try:
                    token = RefreshToken(refresh_token)
                    token.blacklist()
                    tokens_blacklisted += 1
                    logger.info(
                        f"Specific refresh token blacklisted for user {user.id}"
                    )
                except (TokenError, InvalidToken, AttributeError) as e:
                    logger.warning(f"Failed to blacklist specific token: {e}")

            # Option 2: Blacklist ALL outstanding refresh tokens for this user
            outstanding_tokens = OutstandingToken.objects.filter(user=user)

            for token in outstanding_tokens:
                try:
                    # Check if already blacklisted
                    BlacklistedToken.objects.get_or_create(token=token)
                    tokens_blacklisted += 1
                except Exception as e:
                    logger.error(f"Failed to blacklist token {token.jti}: {e}")

            logger.info(
                f"User {user.id} logged out. {tokens_blacklisted} tokens revoked."
            )

            return Response(
                {
                    "success": True,
                    "message": "Successfully logged out",
                    "revoked_sessions": tokens_blacklisted,
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Logout error: {str(e)}")
            return Response(
                {"success": False, "message": "Logout failed"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ClassTimetableView(APIView):
    """
    API endpoint to get timetable for a specific class.
    Accepts enrollment_id (preferred, year-scoped) or class_id + optional academic_year_id.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        class_id = request.query_params.get("class_id")
        academic_year_id = request.query_params.get("academic_year_id")

        if not enrollment_id and not class_id:
            return Response(
                {"success": False, "message": "Either enrollment_id or class_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Resolve academic class — enrollment_id takes priority (already year-scoped)
            if enrollment_id:
                try:
                    enrollment = StudentEnrollment.objects.select_related(
                        "academic_class", "academic_class__standard",
                        "academic_class__section", "academic_class__academic_year",
                    ).get(id=enrollment_id, is_active=True)
                except StudentEnrollment.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Enrollment not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
                academic_class = enrollment.academic_class
            else:
                filters = {"id": class_id, "is_active": True}
                if academic_year_id:
                    filters["academic_year_id"] = academic_year_id
                academic_class = (
                    AcademicClass.objects.filter(**filters)
                    .select_related("standard", "section", "academic_year")
                    .first()
                )

            if not academic_class:
                return Response(
                    {"success": False, "message": "Class not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get all active weekdays in correct order (Monday to Sunday)
            weekdays = WeekDay.objects.filter(is_active=True).order_by(
                Case(
                    When(day_code="MON", then=1),
                    When(day_code="TUE", then=2),
                    When(day_code="WED", then=3),
                    When(day_code="THU", then=4),
                    When(day_code="FRI", then=5),
                    When(day_code="SAT", then=6),
                    When(day_code="SUN", then=7),
                    output_field=IntegerField(),
                )
            )

            # Get all timetables for this class
            timetables = (
                TimeTable.objects.filter(academic_class=academic_class, is_active=True)
                .select_related(
                    "subject",
                    "weekday",
                    "subject_teacher__teacher",
                    "substitute_teacher",
                )
                .order_by("weekday", "period_number")
            )

            # Group timetables by weekday
            timetable_by_weekday = []
            for weekday in weekdays:
                weekday_timetables = timetables.filter(weekday=weekday)
                if weekday_timetables.exists():
                    timetable_by_weekday.append(
                        {
                            "weekday_id": weekday.id,
                            "weekday_name": weekday.day_name,
                            "weekday_code": weekday.day_code,
                            "periods": ParentTimeTableSerializer(
                                weekday_timetables,
                                many=True,
                                context={"request": request},
                            ).data,
                        }
                    )

            # Class info
            class_info = ParentAcademicClassForTimetableSerializer(
                academic_class, context={"request": request}
            ).data

            return Response(
                {
                    "success": True,
                    "class_info": class_info,
                    "timetable": timetable_by_weekday,
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Error in ClassTimetableView: {str(e)}")
            return Response(
                {"success": False, "message": "Failed to fetch timetable"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class WeekdayTimetableView(APIView):
    """
    API endpoint to get timetable for a specific class and weekday.
    Accepts enrollment_id (preferred, year-scoped) or class_id + optional academic_year_id.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        class_id = request.query_params.get("class_id")
        weekday_id = request.query_params.get("weekday_id")
        academic_year_id = request.query_params.get("academic_year_id")

        if not weekday_id:
            return Response(
                {"success": False, "message": "weekday_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if not enrollment_id and not class_id:
            return Response(
                {"success": False, "message": "Either enrollment_id or class_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Resolve academic class — enrollment_id takes priority (already year-scoped)
            if enrollment_id:
                try:
                    enrollment = StudentEnrollment.objects.select_related(
                        "academic_class"
                    ).get(id=enrollment_id, is_active=True)
                except StudentEnrollment.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Enrollment not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
                academic_class = enrollment.academic_class
                class_id = academic_class.id
            else:
                filters = {"id": class_id, "is_active": True}
                if academic_year_id:
                    filters["academic_year_id"] = academic_year_id
                academic_class = AcademicClass.objects.filter(**filters).first()

            if not academic_class:
                return Response(
                    {"success": False, "message": "Class not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get weekday
            weekday = WeekDay.objects.filter(id=weekday_id, is_active=True).first()
            if not weekday:
                return Response(
                    {"success": False, "message": "Weekday not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get timetables for this class and weekday
            timetables = (
                TimeTable.objects.filter(
                    academic_class_id=class_id, weekday=weekday, is_active=True
                )
                .select_related(
                    "subject", "subject_teacher__teacher", "substitute_teacher"
                )
                .order_by("period_number")
            )

            return Response(
                {
                    "success": True,
                    "class_id": class_id,
                    "weekday_info": {
                        "id": weekday.id,
                        "name": weekday.day_name,
                        "code": weekday.day_code,
                        "is_open": weekday.is_open,
                        "start_time": weekday.start_time,
                        "end_time": weekday.end_time,
                    },
                    "periods": ParentTimeTableSerializer(
                        timetables, many=True, context={"request": request}
                    ).data,
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Error in WeekdayTimetableView: {str(e)}")
            return Response(
                {"success": False, "message": "Failed to fetch timetable"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class StudentFeeDetailsView(APIView):
    """
    API endpoint to get current fee details for a student
    Returns:
    - Student info
    - Current fee structure with component-wise breakdown
    - Pending amounts per component
    - Overall summary (total fees, paid, pending)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id parameter is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent exists and has access
            parent = self._verify_parent_access(request.user.id, enrollment_id, academic_year_id)
            if not parent:
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get enrollment with related data
            enrollment = self._get_enrollment(enrollment_id, academic_year_id)
            if not enrollment:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get academic year and class info
            academic_year = enrollment.academic_class.academic_year
            class_id = enrollment.academic_class.id

            # Get all active terms
            active_terms = academic_year.terms.filter(is_active=True)

            if not active_terms.exists():
                return Response(
                    {
                        "success": True,
                        "message": "No active terms found",
                        "data": self._build_empty_response(enrollment),
                    },
                    status=status.HTTP_200_OK,
                )

            # Process fee components
            components_data = []
            total_fees = 0
            total_paid = 0
            total_pending = 0

            for term in active_terms:
                term_components = self._process_term_fees(term, enrollment, class_id)

                for comp in term_components:
                    components_data.append(comp)
                    total_fees += comp["total_amount"]
                    total_paid += comp["paid_amount"]
                    total_pending += comp["pending_amount"]

            transport_component = self._get_transport_component(
                enrollment, class_id, active_terms
            )
            if transport_component:
                components_data.append(transport_component)
                total_fees += transport_component["total_amount"]
                total_paid += transport_component["paid_amount"]
                total_pending += transport_component["pending_amount"]

            # Build response
            response_data = {
                "enrollment_id": enrollment.id,
                "student_id": enrollment.student.id,
                "student_name": self._get_student_name(enrollment.student),
                "class_name": self._get_class_name(enrollment.academic_class),
                "academic_year": academic_year.name,
                "summary": {
                    "total_fees": round(total_fees, 2),
                    "total_paid": round(total_paid, 2),
                    "total_pending": round(total_pending, 2),
                    "has_pending": total_pending > 0,
                    "payment_status": self._get_payment_status(
                        total_pending, total_fees
                    ),
                },
                "components": components_data,
            }

            return Response(
                {"success": True, "data": response_data},
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Error in StudentFeeDetailsView: {str(e)}")
            return Response(
                {"success": False, "message": f"An error occurred: {str(e)}"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _verify_parent_access(self, user_id, enrollment_id, academic_year_id=None):
        """Verify parent exists and has access to the student.
        Year validation is intentionally skipped here — it belongs in _get_enrollment.
        """
        try:
            parent = Parent.objects.get(external_user_id=user_id)
            enrollment = StudentEnrollment.objects.get(id=enrollment_id)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            return parent if student_parent else None
        except:
            return None

    def _get_enrollment(self, enrollment_id, academic_year_id=None):
        """Get enrollment with related data, optionally validated against academic year"""
        try:
            filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                filters["academic_class__academic_year_id"] = academic_year_id
            return StudentEnrollment.objects.select_related(
                "student",
                "academic_class",
                "academic_class__standard",
                "academic_class__section",
                "academic_class__academic_year",
            ).get(**filters)
        except StudentEnrollment.DoesNotExist:
            return None

    def _process_term_fees(self, term, enrollment, class_id):
        """Process fee components for a specific term"""
        components = []

        class_fees = ClassFeeStructure.objects.filter(
            academic_class_id=class_id, academic_term=term, is_active=True
        ).select_related("fee_component")

        for class_fee in class_fees:
            # Check for student-specific assignment
            student_assignment = StudentFeeAssignment.objects.filter(
                enrollment=enrollment, class_fee_structure=class_fee, is_active=True
            ).first()

            # Calculate amounts
            if student_assignment:
                total_amount = float(student_assignment.calculate_final_amount())
                discount_info = {
                    "type": student_assignment.discount_type,
                    "value": (
                        float(student_assignment.discount_value)
                        if student_assignment.discount_value
                        else None
                    ),
                }
                due_date = student_assignment.due_date_override or class_fee.due_date
            else:
                total_amount = float(class_fee.amount)
                discount_info = None
                due_date = class_fee.due_date

            # Get paid amount from PaymentFeeComponent
            paid_amount = self._get_paid_amount(
                enrollment, class_fee, student_assignment
            )

            pending_amount = max(0, total_amount - paid_amount)

            # Determine component status
            status = self._get_component_status(paid_amount, total_amount, due_date)

            components.append(
                {
                    "component_id": class_fee.fee_component.id,
                    "component_name": class_fee.fee_component.name,
                    "component_code": class_fee.fee_component.code,
                    "term_id": term.id,
                    "term_name": term.name,
                    "due_date": due_date,
                    "due_date_formatted": (
                        due_date.strftime("%d %b %Y") if due_date else None
                    ),
                    "total_amount": round(total_amount, 2),
                    "paid_amount": round(paid_amount, 2),
                    "pending_amount": round(pending_amount, 2),
                    "status": status,
                    "status_display": self._get_status_display(status),
                    "is_overdue": due_date
                    and due_date < timezone.now().date()
                    and pending_amount > 0,
                    "discount": discount_info,
                    "is_mandatory": class_fee.fee_component.is_mandatory,
                }
            )

        return components

    def _get_paid_amount(self, enrollment, class_fee, student_assignment):
        """Get paid amount for a fee component"""
        try:
            if student_assignment:
                paid = (
                    PaymentFeeComponent.objects.filter(
                        student_fee_assignment=student_assignment
                    ).aggregate(total=models.Sum("amount_paid"))["total"]
                    or 0
                )
            else:
                paid = (
                    PaymentFeeComponent.objects.filter(
                        class_fee_structure=class_fee,
                        payment__student_enrollment=enrollment,
                    ).aggregate(total=models.Sum("amount_paid"))["total"]
                    or 0
                )

            return float(paid)
        except:
            return 0.0

    def _get_transport_component(self, enrollment, class_id, active_terms):
        """
        Synthetic fee-component entry for transport/bus fee, so it shows up
        in the same components list (partial-payment selection + full-payment
        total) as regular fee components. Uses a negative component_id
        (-StudentTransport.id) since real FeeComponent PKs are always
        positive — ProcessFeePaymentView branches on this sign to route the
        payment to TransportFeePayment instead of PaymentFeeComponent.
        Returns None if there's no transport assignment, or if transport fee
        is already tracked via a real "transport"-named FeeComponent (avoids
        double counting — that case already flows through _process_term_fees).
        """
        st = (
            StudentTransport.objects.filter(enrollment=enrollment, is_active=True)
            .select_related("vehicle", "route")
            .first()
        )
        if not st:
            return None

        fee_amount = float(st.fee_amount or 0)
        if fee_amount <= 0:
            return None

        class_fees = ClassFeeStructure.objects.filter(
            academic_class_id=class_id, academic_term__in=active_terms, is_active=True
        ).select_related("fee_component")
        if any(_is_transport_component(cf.fee_component) for cf in class_fees):
            return None

        paid_amount = float(
            TransportFeePayment.objects.filter(student_transport=st).aggregate(
                total=models.Sum("amount_paid")
            )["total"]
            or 0
        )
        pending_amount = max(0.0, fee_amount - paid_amount)
        due_date = st.due_date
        component_status = self._get_component_status(paid_amount, fee_amount, due_date)

        return {
            "component_id": -st.id,
            "component_name": "Transport Fee",
            "component_code": "TRANSPORT",
            "term_id": None,
            "term_name": None,
            "due_date": due_date or date(9999, 12, 31),
            "due_date_formatted": (
                due_date.strftime("%d %b %Y") if due_date else "No due date"
            ),
            "total_amount": round(fee_amount, 2),
            "paid_amount": round(paid_amount, 2),
            "pending_amount": round(pending_amount, 2),
            "status": component_status,
            "status_display": self._get_status_display(component_status),
            "is_overdue": bool(
                due_date and due_date < timezone.now().date() and pending_amount > 0
            ),
            "discount": None,
            "is_mandatory": True,
            "is_transport": True,
        }

    def _get_component_status(self, paid, total, due_date):
        """Determine component payment status"""
        if paid <= 0:
            return "PENDING"
        elif paid >= total:
            return "PAID"
        elif due_date and due_date < timezone.now().date():
            return "OVERDUE"
        else:
            return "PARTIAL"

    def _get_status_display(self, status):
        """Get display text for status"""
        status_map = {
            "PENDING": "Pending",
            "PARTIAL": "Partially Paid",
            "PAID": "Paid",
            "OVERDUE": "Overdue",
        }
        return status_map.get(status, status)

    def _get_payment_status(self, pending, total):
        """Get overall payment status"""
        if pending <= 0:
            return "PAID"
        elif pending >= total:
            return "PENDING"
        else:
            return "PARTIAL"

    def _get_student_name(self, student):
        """Get formatted student name"""
        return student.full_name or f"{student.first_name} {student.last_name}".strip()

    def _get_class_name(self, academic_class):
        """Get formatted class name"""
        if not academic_class:
            return None
        standard = academic_class.standard.name if academic_class.standard else ""
        section = academic_class.section.name if academic_class.section else ""
        return f"{standard} - {section}".strip(" -")

    def _build_empty_response(self, enrollment):
        """Build empty response when no fees found"""
        return {
            "enrollment_id": enrollment.id,
            "student_id": enrollment.student.id,
            "student_name": self._get_student_name(enrollment.student),
            "class_name": self._get_class_name(enrollment.academic_class),
            "academic_year": (
                enrollment.academic_class.academic_year.name
                if enrollment.academic_class
                else None
            ),
            "summary": {
                "total_fees": 0,
                "total_paid": 0,
                "total_pending": 0,
                "has_pending": False,
                "payment_status": "NO_FEES",
            },
            "components": [],
        }


class StudentPaymentHistoryView(APIView):
    """
    API endpoint to get payment history for a student
    Returns all payments with component-wise allocations
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        page = int(request.query_params.get("page", 1))
        page_size = int(request.query_params.get("page_size", 10))

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id parameter is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get all payments for this enrollment
            payments = FeePayment.objects.filter(
                student_enrollment=enrollment
            ).order_by("-payment_date", "-created_at")

            # Pagination
            start = (page - 1) * page_size
            end = start + page_size
            total_count = payments.count()
            paginated_payments = payments[start:end]

            payment_history = []
            for payment in paginated_payments:
                payment_history.append(self._format_payment(payment))

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollment_id": enrollment.id,
                        "student_name": self._get_student_name(enrollment.student),
                        "payments": payment_history,
                        "pagination": {
                            "current_page": page,
                            "page_size": page_size,
                            "total_pages": (total_count + page_size - 1) // page_size,
                            "total_count": total_count,
                            "has_next": end < total_count,
                            "has_previous": page > 1,
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentPaymentHistoryView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _format_payment(self, payment):
        """Format a single payment record"""
        allocations = payment.payment_components.select_related(
            "fee_component", "academic_term"
        ).all()

        return {
            "payment_id": payment.id,
            "receipt_number": payment.receipt_number,
            "amount_paid": float(payment.amount_paid),
            "payment_date": payment.payment_date,
            "payment_date_formatted": payment.payment_date.strftime("%d %b %Y"),
            "payment_method": payment.get_payment_method_display(),
            "payment_type": payment.get_payment_type_display(),
            "status": payment.get_status_display(),
            "principal_amount": float(payment.principal_amount),
            "late_fee_amount": float(payment.late_fee_amount),
            "balance_before": float(payment.balance_before),
            "balance_after": float(payment.balance_after),
            "notes": payment.notes,
            "allocations": [
                {
                    "component_id": alloc.fee_component.id,
                    "component_name": alloc.fee_component.name,
                    "amount": float(alloc.amount_paid),
                    "term": alloc.academic_term.name if alloc.academic_term else None,
                }
                for alloc in allocations
            ]
            + [
                {
                    "component_id": None,
                    "component_name": "Transport Fee",
                    "amount": float(t.amount_paid),
                    "term": None,
                }
                for t in payment.transport_payments.all()
            ],
        }

    def _get_student_name(self, student):
        return student.full_name or f"{student.first_name} {student.last_name}".strip()


class PaymentReceiptView(APIView):
    """
    API endpoint to get a single payment receipt
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        payment_id = request.query_params.get("payment_id")
        receipt_number = request.query_params.get("receipt_number")

        if not payment_id and not receipt_number:
            return Response(
                {
                    "success": False,
                    "message": "Either payment_id or receipt_number is required",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Get payment
            if payment_id:
                payment = FeePayment.objects.get(id=payment_id)
            else:
                payment = FeePayment.objects.get(receipt_number=receipt_number)

            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            student_parent = parent.student_parents.filter(
                student=payment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Format receipt
            receipt = self._format_receipt(payment)

            return Response({"success": True, "data": receipt})

        except FeePayment.DoesNotExist:
            return Response(
                {"success": False, "message": "Payment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in PaymentReceiptView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _format_receipt(self, payment):
        """Format payment receipt"""
        allocations = payment.payment_components.select_related(
            "fee_component", "academic_term"
        ).all()

        # Get student and class info
        student_name = (
            payment.student.full_name
            or f"{payment.student.first_name} {payment.student.last_name}".strip()
        )

        class_name = None
        if payment.student_enrollment.academic_class:
            std = payment.student_enrollment.academic_class.standard
            sec = payment.student_enrollment.academic_class.section
            standard = std.name if std else ""
            section = sec.name if sec else ""
            class_name = f"{standard} - {section}".strip(" -")

        return {
            "receipt_number": payment.receipt_number,
            "receipt_date": payment.payment_date,
            "receipt_date_formatted": payment.payment_date.strftime("%d %b %Y"),
            "student_details": {
                "student_id": payment.student.id,
                "student_name": student_name,
                "class_name": class_name,
                "enrollment_id": payment.student_enrollment.id,
            },
            "payment_details": {
                "amount_paid": float(payment.amount_paid),
                "payment_method": payment.get_payment_method_display(),
                "payment_type": payment.get_payment_type_display(),
                "status": payment.get_status_display(),
                "principal_amount": float(payment.principal_amount),
                "late_fee_amount": float(payment.late_fee_amount),
                "balance_before": float(payment.balance_before),
                "balance_after": float(payment.balance_after),
                "notes": payment.notes,
            },
            "components": [
                {
                    "component_name": alloc.fee_component.name,
                    "amount": float(alloc.amount_paid),
                    "term": alloc.academic_term.name if alloc.academic_term else None,
                }
                for alloc in allocations
            ]
            + [
                {
                    "component_name": "Transport Fee",
                    "amount": float(t.amount_paid),
                    "term": None,
                }
                for t in payment.transport_payments.all()
            ],
            "total_in_words": self._number_to_words(payment.amount_paid),
        }

    def _number_to_words(self, amount):
        """Simple number to words converter"""
        return f"Rupees {float(amount)} only"


class StudentFeeSummaryView(APIView):
    """
    API endpoint to get a quick summary of fees for all students linked to the parent
    Returns total fees, paid amount, and balance for each student
    Now uses ClassFeeStructure to get fees based on student's class
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        academic_year_id = request.query_params.get("academic_year")

        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get all active student-parent relationships
            student_parents = parent.student_parents.filter(
                is_active=True
            ).select_related("student")

            student_summaries = []

            for sp in student_parents:
                student = sp.student

                # Get enrollment for this student, filtered by academic year if provided
                enrollment_qs = StudentEnrollment.objects.filter(
                    student=student, is_active=True
                )
                if academic_year_id:
                    enrollment_qs = enrollment_qs.filter(
                        academic_class__academic_year_id=academic_year_id
                    )
                current_enrollment = (
                    enrollment_qs
                    .select_related("academic_class", "academic_class__academic_year")
                    .first()
                )

                if current_enrollment and current_enrollment.academic_class:
                    class_id = current_enrollment.academic_class_id
                    academic_year = current_enrollment.academic_class.academic_year

                    # Get active terms
                    active_terms = academic_year.terms.filter(is_active=True)

                    total_fees = 0
                    total_paid = 0
                    overdue = False

                    # For each term, get class fee structures and student assignments
                    for term in active_terms:
                        # Get class fee structures for this class and term
                        class_fee_structures = ClassFeeStructure.objects.filter(
                            academic_class_id=class_id,
                            academic_term=term,
                            is_active=True,
                        )

                        for class_fee in class_fee_structures:
                            # Check for student-specific assignment
                            student_assignment = StudentFeeAssignment.objects.filter(
                                enrollment=current_enrollment,
                                class_fee_structure=class_fee,
                                is_active=True,
                            ).first()

                            # Calculate fee amount (with discount if applicable)
                            if student_assignment:
                                fee_amount = student_assignment.calculate_final_amount()

                                # Check for overdue for this specific assignment
                                effective_due_date = (
                                    student_assignment.due_date_override
                                    or class_fee.due_date
                                )
                                if (
                                    effective_due_date
                                    and effective_due_date < timezone.now().date()
                                ):
                                    payments = student_assignment.payments.filter(
                                        status="completed"
                                    )
                                    paid_for_this = (
                                        payments.aggregate(
                                            total=models.Sum("amount_paid")
                                        )["total"]
                                        or 0
                                    )
                                    if paid_for_this < fee_amount:
                                        overdue = True
                            else:
                                fee_amount = class_fee.amount

                                # Check for overdue for class fee structure
                                if (
                                    class_fee.due_date
                                    and class_fee.due_date < timezone.now().date()
                                ):
                                    # Check if any payments exist for this class fee structure
                                    # This is more complex - you might need to track payments differently
                                    # For summary, we'll just check if there's any payment record
                                    has_payment = FeePayment.objects.filter(
                                        student_enrollment=current_enrollment,
                                        fee_assignments__class_fee_structure=class_fee,
                                        status="completed",
                                    ).exists()
                                    if not has_payment:
                                        overdue = True

                            total_fees += fee_amount

                    # Get total paid from payments
                    total_paid = (
                        FeePayment.objects.filter(
                            student_enrollment=current_enrollment, status="completed"
                        ).aggregate(total=models.Sum("amount_paid"))["total"]
                        or 0
                    )

                    balance = total_fees - total_paid

                    student_summaries.append(
                        {
                            "student_id": student.id,
                            "student_name": student.full_name
                            or f"{student.first_name} {student.last_name}".strip(),
                            "relationship": sp.relationship,
                            "enrollment_id": current_enrollment.id,
                            "class_id": class_id,
                            "class_name": current_enrollment.academic_class.name,
                            "academic_year": academic_year.name,
                            "total_fees": total_fees,
                            "total_paid": total_paid,
                            "balance": balance,
                            "has_overdue": overdue,
                            "payment_status": (
                                "overdue"
                                if overdue and balance > 0
                                else (
                                    "paid"
                                    if balance <= 0
                                    else "partial" if total_paid > 0 else "pending"
                                )
                            ),
                        }
                    )
                else:
                    # Student not enrolled in current academic year or no class assigned
                    student_summaries.append(
                        {
                            "student_id": student.id,
                            "student_name": student.full_name
                            or f"{student.first_name} {student.last_name}".strip(),
                            "relationship": sp.relationship,
                            "enrollment_id": None,
                            "class_id": None,
                            "class_name": None,
                            "academic_year": None,
                            "message": "Not enrolled in current academic year or no class assigned",
                            "total_fees": 0,
                            "total_paid": 0,
                            "balance": 0,
                            "has_overdue": False,
                            "payment_status": "not_enrolled",
                        }
                    )

            return Response(
                {
                    "success": True,
                    "count": len(student_summaries),
                    "summaries": student_summaries,
                },
                status=status.HTTP_200_OK,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentFeeSummaryView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {
                    "success": False,
                    "message": "An error occurred while fetching fee summaries",
                },
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class WeekdayListView(APIView):
    """
    API endpoint to get all active weekdays
    Returns list of weekdays with their details
    Mobile app can use this to populate weekday dropdown before calling weekday-timetable API
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get all active weekdays in correct order (Monday to Sunday)
            weekdays = WeekDay.objects.filter(is_active=True).order_by(
                Case(
                    When(day_code="MON", then=1),
                    When(day_code="TUE", then=2),
                    When(day_code="WED", then=3),
                    When(day_code="THU", then=4),
                    When(day_code="FRI", then=5),
                    When(day_code="SAT", then=6),
                    When(day_code="SUN", then=7),
                    output_field=IntegerField(),
                )
            )

            serializer = WeekDaySerializer(weekdays, many=True)

            return Response(
                {
                    "success": True,
                    "count": weekdays.count(),
                    "weekdays": serializer.data,
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Error in WeekdayListView: {str(e)}")
            return Response(
                {"success": False, "message": "Failed to fetch weekdays"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class StudentAnnouncementsView(APIView):
    """
    API endpoint to get announcements for a student using enrollment_id
    Returns:
    - Common announcements (for entire school)
    - Student-specific announcements (based on student's class and standard_type)
    All announcements are filtered to exclude expired ones
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        class_id = request.query_params.get(
            "class_id"
        )  # Optional: can pass class_id directly
        include_expired = request.query_params.get("include_expired", "false").lower() == "true"

        if not enrollment_id and not class_id:
            return Response(
                {
                    "success": False,
                    "message": "Either enrollment_id or class_id parameter is required",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent exists
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
                logger.info(f"Parent found: {parent.id}")
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            student_info = None
            target_class_id = None
            target_standard_type = (
                None  # This will store the standard_type from Standard model
            )

            # If enrollment_id is provided, get class_id from enrollment
            if enrollment_id:
                try:
                    enrollment = StudentEnrollment.objects.select_related(
                        "student",
                        "academic_class",
                        "academic_class__standard",  # This gives us access to standard_type
                        "academic_class__section",
                    ).get(id=enrollment_id, is_active=True)

                    # Verify this student is linked to the parent
                    student_parent = parent.student_parents.filter(
                        student=enrollment.student, is_active=True
                    ).first()

                    if not student_parent:
                        return Response(
                            {
                                "success": False,
                                "message": "You don't have access to this student's announcements",
                            },
                            status=status.HTTP_403_FORBIDDEN,
                        )

                    # Get class ID and standard_type from enrollment
                    if enrollment.academic_class:
                        target_class_id = enrollment.academic_class.id

                        # Get the standard_type from the related Standard model
                        if enrollment.academic_class.standard:
                            target_standard_type = (
                                enrollment.academic_class.standard.standard_type
                            )
                            logger.info(
                                f"Standard type from database: {target_standard_type}"
                            )
                        else:
                            logger.warning("Academic class has no standard assigned")

                        # Build class display name from standard and section
                        standard_name = (
                            enrollment.academic_class.standard.name
                            if enrollment.academic_class.standard
                            else ""
                        )
                        section_name = (
                            enrollment.academic_class.section.name
                            if enrollment.academic_class.section
                            else ""
                        )
                        class_display_name = f"{standard_name} - {section_name}".strip(
                            " -"
                        )

                        student_info = {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_id": target_class_id,
                            "class_display_name": class_display_name,
                            "standard": standard_name,
                            "standard_type": target_standard_type,  # Include in response
                            "section": section_name,
                            "enrollment_id": enrollment.id,
                        }
                        logger.info(
                            f"Found enrollment for student {enrollment.student.id}, class_id: {target_class_id}, standard_type: {target_standard_type}"
                        )

                except StudentEnrollment.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Enrollment not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
            else:
                # Use directly provided class_id
                target_class_id = class_id
                # Verify the class exists
                try:
                    academic_class = AcademicClass.objects.select_related(
                        "standard", "section"
                    ).get(id=target_class_id, is_active=True)

                    standard_name = (
                        academic_class.standard.name if academic_class.standard else ""
                    )
                    section_name = (
                        academic_class.section.name if academic_class.section else ""
                    )
                    class_display_name = f"{standard_name} - {section_name}".strip(" -")

                    # Get standard_type
                    if academic_class.standard:
                        target_standard_type = academic_class.standard.standard_type

                    student_info = {
                        "class_id": target_class_id,
                        "class_display_name": class_display_name,
                        "standard": standard_name,
                        "standard_type": target_standard_type,
                        "section": section_name,
                    }
                except AcademicClass.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Class not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

            # Get current date for expiry check
            today = timezone.now().date()

            # ============================================================
            # 1. FETCH COMMON ANNOUNCEMENTS (for entire school)
            # ============================================================
            common_q = CommonAnnouncement.objects.filter(is_active=True)
            if not include_expired:
                common_q = common_q.filter(
                    Q(expire_on__isnull=True) | Q(expire_on__gte=today)
                )
            common_announcements = (
                common_q
                .select_related("announcement_type")
                .order_by(
                    # Order by priority (high first) then by created date
                    Case(
                        When(priority="high", then=1),
                        When(priority="medium", then=2),
                        When(priority="low", then=3),
                        output_field=IntegerField(),
                    ),
                    "-created_at",
                )
            )

            # ============================================================
            # 2. FETCH STUDENT ANNOUNCEMENTS
            # ============================================================

            # First, let's check ALL student announcements (no filters)
            all_student_announcements = StudentAnnouncement.objects.filter(
                is_active=True
            ).select_related("announcement_type", "academic_class__standard")

            logger.info(
                f"TOTAL student announcements in DB: {all_student_announcements.count()}"
            )

            # Log all announcements for debugging
            for ann in all_student_announcements:
                logger.info(
                    f"Announcement ID: {ann.id}, Title: {ann.title}, is_for_all: {ann.is_for_all_students}, class_id: {ann.academic_class_id}, section: {ann.section}"
                )

            # Now build the filtered query
            student_announcements_query = StudentAnnouncement.objects.filter(is_active=True)
            if not include_expired:
                student_announcements_query = student_announcements_query.filter(
                    Q(expire_on__isnull=True) | Q(expire_on__gte=today)
                )

            # Build the filter for student announcements based on targeting
            if target_class_id:
                # Create a more inclusive filter
                class_filters = Q()

                # 1. Announcements for all students
                class_filters = class_filters | Q(is_for_all_students=True)

                # 2. Announcements for this specific class
                class_filters = class_filters | Q(academic_class_id=target_class_id)

                # 3. Announcements for this standard_type (using section field which stores standard_type)
                if target_standard_type:
                    logger.info(
                        f"Adding section filter for standard_type: {target_standard_type}"
                    )
                    # The section field in StudentAnnouncement stores the standard_type
                    # Values: 'nursery', 'primary', 'higher_secondary'
                    class_filters = class_filters | Q(section=target_standard_type)
                else:
                    logger.info("No standard_type to filter by")

                # Apply the combined filter
                student_announcements_query = student_announcements_query.filter(
                    class_filters
                )

                logger.info(
                    f"After applying filters - class_id: {target_class_id}, standard_type: {target_standard_type}"
                )
            else:
                # If no class_id, just get announcements for all students
                student_announcements_query = student_announcements_query.filter(
                    is_for_all_students=True
                )

            # Log the query count before ordering
            logger.info(
                f"Student announcements count after filters: {student_announcements_query.count()}"
            )

            # Apply ordering
            student_announcements = student_announcements_query.select_related(
                "announcement_type",
                "academic_class__standard",
                "academic_class__section",
            ).order_by(
                Case(
                    When(priority="high", then=1),
                    When(priority="medium", then=2),
                    When(priority="low", then=3),
                    output_field=IntegerField(),
                ),
                "-created_at",
            )

            # Log the final count
            logger.info(
                f"Final student announcements count: {student_announcements.count()}"
            )

            # ============================================================
            # 3. SERIALIZE THE DATA
            # ============================================================
            common_serializer = CommonAnnouncementSerializer(
                common_announcements, many=True, context={"request": request}
            )

            student_serializer = StudentAnnouncementSerializer(
                student_announcements, many=True, context={"request": request}
            )

            # Prepare response
            response_data = {
                "success": True,
                "student_info": student_info,
                "announcements": {
                    "common": common_serializer.data,
                    "student_specific": student_serializer.data,
                },
                "summary": {
                    "total_announcements": common_announcements.count()
                    + student_announcements.count(),
                    "common_count": common_announcements.count(),
                    "student_specific_count": student_announcements.count(),
                },
            }

            logger.info(
                f"Returning {common_announcements.count()} common and {student_announcements.count()} student announcements"
            )
            return Response(response_data, status=status.HTTP_200_OK)

        except Exception as e:
            logger.error(f"Error in StudentAnnouncementsView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": f"An error occurred: {str(e)}"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ProcessFeePaymentView(APIView):
    """
    API endpoint to process fee payments.
    Supports both full and partial/component-wise payments.
    All monetary arithmetic uses Decimal throughout to avoid type mixing errors.
    """

    permission_classes = [IsAuthenticated]

    # ── Internal helper: safe Decimal conversion ──────────────────────────────
    @staticmethod
    def _to_decimal(value):
        """Safely convert any numeric value (float, int, Decimal, None) to Decimal."""
        if value is None:
            return Decimal("0.0")
        if isinstance(value, Decimal):
            return value
        return Decimal(str(value))

    # ─────────────────────────────────────────────────────────────────────────
    # PUBLIC ENDPOINT
    # ─────────────────────────────────────────────────────────────────────────

    def post(self, request):
        try:
            data = request.data
            enrollment_id = data.get("enrollment_id")
            payment_type = data.get("payment_type", "full")
            amount_paid = data.get("amount_paid")
            payment_method = data.get("payment_method", "online")
            components = data.get("components", [])
            notes = data.get("notes", "")

            # ── Basic validation ──────────────────────────────────────────────
            if not enrollment_id:
                return Response(
                    {"success": False, "message": "enrollment_id is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            if not amount_paid or float(amount_paid) <= 0:
                return Response(
                    {"success": False, "message": "Valid amount_paid is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            amount_paid_decimal = self._to_decimal(amount_paid)

            # ── Verify parent ─────────────────────────────────────────────────
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
                logger.info(f"[PAYMENT] Parent id={parent.id} processing payment")
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # ── Verify enrollment & access ────────────────────────────────────
            try:
                enrollment = StudentEnrollment.objects.select_related(
                    "student", "academic_class", "academic_class__academic_year"
                ).get(id=enrollment_id, is_active=True)

                student_parent = parent.student_parents.filter(
                    student=enrollment.student, is_active=True
                ).first()

                if not student_parent:
                    return Response(
                        {
                            "success": False,
                            "message": "You don't have access to this student",
                        },
                        status=status.HTTP_403_FORBIDDEN,
                    )

            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # ── Balance before payment ────────────────────────────────────────
            balance_before = self._get_current_balance(enrollment)
            logger.info(f"[PAYMENT] balance_before={balance_before}")

            # ── Validate amounts ──────────────────────────────────────────────
            if payment_type in ["partial", "component"]:
                if not components:
                    return Response(
                        {
                            "success": False,
                            "message": "Components list is required for partial/component payment",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                total_component_amount = sum(float(c["amount"]) for c in components)
                if abs(total_component_amount - float(amount_paid_decimal)) > 0.01:
                    return Response(
                        {
                            "success": False,
                            "message": f"Component total ({total_component_amount}) doesn't match amount paid ({amount_paid_decimal})",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                for comp in components:
                    component_id = comp.get("component_id")
                    comp_amount = float(comp.get("amount", 0))

                    if not component_id:
                        return Response(
                            {
                                "success": False,
                                "message": "Each component must have component_id",
                            },
                            status=status.HTTP_400_BAD_REQUEST,
                        )

                    pending = self._get_component_pending(enrollment, component_id)
                    if comp_amount > pending + 0.01:
                        return Response(
                            {
                                "success": False,
                                "message": f"Amount {comp_amount} exceeds pending {pending} for component {component_id}",
                            },
                            status=status.HTTP_400_BAD_REQUEST,
                        )

            elif payment_type == "full":
                if float(amount_paid_decimal) > balance_before + 0.01:
                    return Response(
                        {
                            "success": False,
                            "message": f"Amount {amount_paid_decimal} exceeds total pending {balance_before}",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

            # ── Process payment inside atomic transaction ──────────────────────
            fee_assignments_processed = []
            component_allocations = []
            payment = None
            balance_after = balance_before  # fallback

            try:
                with transaction.atomic():

                    # Create FeePayment record
                    payment = FeePayment(
                        student_enrollment=enrollment,
                        student=enrollment.student,
                        amount_paid=amount_paid_decimal,
                        payment_date=timezone.now().date(),
                        payment_method=payment_method,
                        payment_type=payment_type,
                        status="completed",
                        principal_amount=amount_paid_decimal,
                        late_fee_amount=Decimal("0.0"),
                        balance_before=self._to_decimal(balance_before),
                        balance_after=Decimal("0.0"),  # updated below
                        notes=notes,
                        recorded_by_name=f"Parent: {parent.id}",
                    )
                    payment.save()
                    logger.info(
                        f"[PAYMENT] FeePayment created id={payment.id} receipt={payment.receipt_number}"
                    )

                    # ── PARTIAL / COMPONENT PAYMENT ───────────────────────────
                    if payment_type in ["partial", "component"]:
                        for comp in components:
                            component_id = comp["component_id"]
                            comp_amount = self._to_decimal(comp["amount"])

                            if int(component_id) < 0:
                                transport = StudentTransport.objects.filter(
                                    id=-int(component_id),
                                    enrollment=enrollment,
                                    is_active=True,
                                ).first()
                                if not transport:
                                    logger.warning(
                                        f"[PAYMENT] No StudentTransport for component_id={component_id}, skipping"
                                    )
                                    continue
                                TransportFeePayment.objects.create(
                                    student_transport=transport,
                                    payment=payment,
                                    amount_paid=comp_amount,
                                    payment_date=timezone.now().date(),
                                    payment_method=payment_method,
                                    notes=notes,
                                )
                                logger.info(
                                    f"[PAYMENT] ✅ TransportFeePayment component_id={component_id} amount={comp_amount}"
                                )
                                component_allocations.append(
                                    {
                                        "component_id": component_id,
                                        "amount": float(comp_amount),
                                    }
                                )
                                continue

                            class_fee = ClassFeeStructure.objects.filter(
                                academic_class=enrollment.academic_class,
                                fee_component_id=component_id,
                                is_active=True,
                            ).first()

                            if not class_fee:
                                logger.warning(
                                    f"[PAYMENT] No ClassFeeStructure for component_id={component_id}, skipping"
                                )
                                continue

                            student_assignment = StudentFeeAssignment.objects.filter(
                                enrollment=enrollment,
                                class_fee_structure=class_fee,
                                is_active=True,
                            ).first()

                            pfc = PaymentFeeComponent.objects.create(
                                payment=payment,
                                fee_component_id=component_id,
                                amount_paid=comp_amount,
                                class_fee_structure=class_fee,
                                student_fee_assignment=student_assignment,
                                academic_term=class_fee.academic_term,
                            )
                            logger.info(
                                f"[PAYMENT] ✅ PFC id={pfc.id} component_id={component_id} amount={comp_amount}"
                            )

                            component_allocations.append(
                                {
                                    "component_id": component_id,
                                    "amount": float(comp_amount),
                                }
                            )

                            if student_assignment:
                                payment.fee_assignments.add(student_assignment)
                                fee_assignments_processed.append(student_assignment.id)

                    # ── FULL PAYMENT ──────────────────────────────────────────
                    else:
                        pending_components = self._get_all_pending_components(
                            enrollment
                        )

                        logger.info(
                            f"[PAYMENT] Full payment — {len(pending_components)} pending components:"
                        )
                        for pc in pending_components:
                            logger.info(
                                f"  → component_id={pc['component_id']} "
                                f"name={pc['component_name']} "
                                f"pending={pc['pending_amount']} "
                                f"due={pc['due_date']}"
                            )

                        if not pending_components:
                            logger.warning("[PAYMENT] No pending components found")
                        else:
                            pending_components.sort(
                                key=lambda x: x["due_date"] or "9999-12-31"
                            )
                            remaining = self._to_decimal(amount_paid_decimal)

                            for comp in pending_components:
                                if remaining <= Decimal("0.005"):
                                    logger.info(
                                        "[PAYMENT] Remaining exhausted, stopping"
                                    )
                                    break

                                amount_to_pay = min(
                                    self._to_decimal(comp["pending_amount"]),
                                    remaining,
                                ).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

                                if amount_to_pay <= Decimal("0"):
                                    continue

                                if comp.get("is_transport"):
                                    transport = StudentTransport.objects.filter(
                                        id=-comp["component_id"],
                                        enrollment=enrollment,
                                        is_active=True,
                                    ).first()
                                    if not transport:
                                        continue
                                    TransportFeePayment.objects.create(
                                        student_transport=transport,
                                        payment=payment,
                                        amount_paid=amount_to_pay,
                                        payment_date=timezone.now().date(),
                                        payment_method=payment_method,
                                        notes=notes,
                                    )
                                    logger.info(
                                        f"[PAYMENT] ✅ TransportFeePayment id={transport.id} "
                                        f"amount={amount_to_pay} allocated (full payment)"
                                    )
                                else:
                                    class_fee = comp["class_fee_structure"]
                                    student_assignment = comp["student_assignment"]

                                    logger.info(
                                        f"[PAYMENT] Allocating {amount_to_pay} → "
                                        f"component_id={comp['component_id']} "
                                        f"name={comp['component_name']} "
                                        f"class_fee_id={class_fee.id} "
                                        f"assignment_id={student_assignment.id if student_assignment else None}"
                                    )

                                    pfc = PaymentFeeComponent.objects.create(
                                        payment=payment,
                                        fee_component_id=comp["component_id"],
                                        amount_paid=amount_to_pay,
                                        class_fee_structure=class_fee,
                                        student_fee_assignment=student_assignment,
                                        academic_term=class_fee.academic_term,
                                    )
                                    logger.info(
                                        f"[PAYMENT] ✅ PFC id={pfc.id} created successfully"
                                    )

                                    if student_assignment:
                                        payment.fee_assignments.add(student_assignment)
                                        fee_assignments_processed.append(
                                            student_assignment.id
                                        )

                                component_allocations.append(
                                    {
                                        "component_id": comp["component_id"],
                                        "amount": float(amount_to_pay),
                                    }
                                )

                                remaining = (remaining - amount_to_pay).quantize(
                                    Decimal("0.01"), rounding=ROUND_HALF_UP
                                )
                                logger.info(
                                    f"[PAYMENT] Remaining after allocation: {remaining}"
                                )

                            if remaining > Decimal("0.01"):
                                logger.warning(
                                    f"[PAYMENT] ⚠ Unallocated remaining: {remaining}"
                                )

                    # ── Update balance_after inside same transaction ───────────
                    balance_after = self._get_current_balance(enrollment)
                    payment.balance_after = self._to_decimal(balance_after)
                    payment.save(update_fields=["balance_after"])
                    logger.info(
                        f"[PAYMENT] balance_after={balance_after} "
                        f"components_processed={len(component_allocations)}"
                    )

            except Exception as e:
                import traceback

                logger.error(f"[PAYMENT] ❌ Transaction rolled back: {str(e)}")
                logger.error(traceback.format_exc())
                return Response(
                    {"success": False, "message": f"Payment failed: {str(e)}"},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                )

            # ── Receipt (outside transaction, read-only) ──────────────────────
            receipt_data = self._generate_receipt(payment)

            logger.info(
                f"[PAYMENT] ✅ Done — payment_id={payment.id} "
                f"components={len(component_allocations)} "
                f"balance_after={balance_after}"
            )

            return Response(
                {
                    "success": True,
                    "message": "Payment processed successfully",
                    "data": {
                        "payment_id": payment.id,
                        "receipt_number": payment.receipt_number,
                        "amount_paid": float(payment.amount_paid),
                        "payment_date": payment.payment_date,
                        "payment_method": payment.get_payment_method_display(),
                        "payment_type": payment.get_payment_type_display(),
                        "balance_before": float(payment.balance_before),
                        "balance_after": float(balance_after),
                        "components_processed": len(component_allocations),
                        "component_allocations": component_allocations,
                        "receipt": receipt_data,
                    },
                },
                status=status.HTTP_201_CREATED,
            )

        except Exception as e:
            import traceback

            logger.error(f"[PAYMENT] Outer error: {str(e)}")
            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": f"Payment failed: {str(e)}"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    # ─────────────────────────────────────────────────────────────────────────
    # HELPER METHODS — all at class level
    # ─────────────────────────────────────────────────────────────────────────

    def _get_transport_pending(self, enrollment):
        """
        Returns (StudentTransport|None, Decimal pending). None/0 if there's no
        transport assignment, or transport fee is already tracked via a real
        "transport"-named FeeComponent (that portion flows through the normal
        ClassFeeStructure path instead, so we don't double count it here).
        """
        transport = StudentTransport.objects.filter(
            enrollment=enrollment, is_active=True
        ).first()
        if not transport:
            return None, Decimal("0.0")

        class_fees = ClassFeeStructure.objects.filter(
            academic_class=enrollment.academic_class, is_active=True
        ).select_related("fee_component")
        if any(_is_transport_component(cf.fee_component) for cf in class_fees):
            return None, Decimal("0.0")

        fee_amount = self._to_decimal(transport.fee_amount)
        paid = self._to_decimal(
            TransportFeePayment.objects.filter(
                student_transport=transport
            ).aggregate(total=models.Sum("amount_paid"))["total"]
        )
        return transport, max(Decimal("0.0"), fee_amount - paid)

    def _get_current_balance(self, enrollment):
        """
        Returns current outstanding balance as float.
        All internal arithmetic uses Decimal via _to_decimal() helper.
        """
        try:
            total_fees = Decimal("0.0")
            total_paid = Decimal("0.0")

            class_fees = ClassFeeStructure.objects.filter(
                academic_class=enrollment.academic_class, is_active=True
            )

            for class_fee in class_fees:
                student_assignment = StudentFeeAssignment.objects.filter(
                    enrollment=enrollment,
                    class_fee_structure=class_fee,
                    is_active=True,
                ).first()

                if student_assignment:
                    fee_amount = self._to_decimal(
                        student_assignment.calculate_final_amount()
                    )
                    paid_raw = PaymentFeeComponent.objects.filter(
                        student_fee_assignment=student_assignment
                    ).aggregate(total=models.Sum("amount_paid"))["total"]
                else:
                    fee_amount = self._to_decimal(class_fee.amount)
                    paid_raw = PaymentFeeComponent.objects.filter(
                        class_fee_structure=class_fee,
                        payment__student_enrollment=enrollment,
                    ).aggregate(total=models.Sum("amount_paid"))["total"]

                total_fees += fee_amount
                total_paid += self._to_decimal(paid_raw)

            _, transport_pending = self._get_transport_pending(enrollment)

            return float(total_fees - total_paid + transport_pending)

        except Exception as e:
            logger.error(f"[PAYMENT] Error in _get_current_balance: {e}")
            import traceback

            logger.error(traceback.format_exc())
            return 0.0

    def _get_component_pending(self, enrollment, component_id):
        """Returns pending amount for a single fee component as float."""
        try:
            if int(component_id) < 0:
                transport, pending = self._get_transport_pending(enrollment)
                if transport and transport.id == -int(component_id):
                    return float(pending)
                return 0.0

            class_fee = ClassFeeStructure.objects.filter(
                academic_class=enrollment.academic_class,
                fee_component_id=component_id,
                is_active=True,
            ).first()

            if not class_fee:
                return 0.0

            student_assignment = StudentFeeAssignment.objects.filter(
                enrollment=enrollment,
                class_fee_structure=class_fee,
                is_active=True,
            ).first()

            if student_assignment:
                fee_amount = self._to_decimal(
                    student_assignment.calculate_final_amount()
                )
                paid_raw = PaymentFeeComponent.objects.filter(
                    student_fee_assignment=student_assignment
                ).aggregate(total=models.Sum("amount_paid"))["total"]
            else:
                fee_amount = self._to_decimal(class_fee.amount)
                paid_raw = PaymentFeeComponent.objects.filter(
                    class_fee_structure=class_fee,
                    payment__student_enrollment=enrollment,
                ).aggregate(total=models.Sum("amount_paid"))["total"]

            paid = self._to_decimal(paid_raw)
            return float(max(Decimal("0.0"), fee_amount - paid))

        except Exception as e:
            logger.error(f"[PAYMENT] Error in _get_component_pending: {e}")
            return 0.0

    def _get_all_pending_components(self, enrollment):
        """
        Returns list of pending component dicts with pre-fetched ORM objects
        so the payment loop never re-queries the DB per component.
        """
        try:
            pending_components = []

            class_fees = ClassFeeStructure.objects.filter(
                academic_class=enrollment.academic_class, is_active=True
            ).select_related("fee_component", "academic_term")

            for class_fee in class_fees:
                student_assignment = StudentFeeAssignment.objects.filter(
                    enrollment=enrollment,
                    class_fee_structure=class_fee,
                    is_active=True,
                ).first()

                if student_assignment:
                    fee_amount = self._to_decimal(
                        student_assignment.calculate_final_amount()
                    )
                    paid_raw = PaymentFeeComponent.objects.filter(
                        student_fee_assignment=student_assignment
                    ).aggregate(total=models.Sum("amount_paid"))["total"]
                    due_date = (
                        student_assignment.due_date_override or class_fee.due_date
                    )
                else:
                    fee_amount = self._to_decimal(class_fee.amount)
                    paid_raw = PaymentFeeComponent.objects.filter(
                        class_fee_structure=class_fee,
                        payment__student_enrollment=enrollment,
                    ).aggregate(total=models.Sum("amount_paid"))["total"]
                    due_date = class_fee.due_date

                paid = self._to_decimal(paid_raw)
                pending = fee_amount - paid

                if pending > Decimal("0.0"):
                    pending_components.append(
                        {
                            "component_id": class_fee.fee_component.id,
                            "component_name": class_fee.fee_component.name,
                            "pending_amount": float(pending),
                            "due_date": str(due_date) if due_date else None,
                            "class_fee_structure": class_fee,  # pre-fetched
                            "student_assignment": student_assignment,  # pre-fetched
                        }
                    )

            transport, transport_pending = self._get_transport_pending(enrollment)
            if transport and transport_pending > Decimal("0.0"):
                pending_components.append(
                    {
                        "component_id": -transport.id,
                        "component_name": "Transport Fee",
                        "pending_amount": float(transport_pending),
                        "due_date": str(transport.due_date) if transport.due_date else None,
                        "class_fee_structure": None,
                        "student_assignment": None,
                        "is_transport": True,
                    }
                )

            return pending_components

        except Exception as e:
            logger.error(f"[PAYMENT] Error in _get_all_pending_components: {e}")
            import traceback

            logger.error(traceback.format_exc())
            return []

    def _generate_receipt(self, payment):
        """Builds receipt dict from a committed FeePayment instance."""
        try:
            allocations = payment.payment_components.select_related(
                "fee_component", "academic_term"
            ).all()

            student_name = (
                payment.student.full_name
                or f"{payment.student.first_name} {payment.student.last_name}".strip()
            )

            class_name = None
            if payment.student_enrollment.academic_class:
                standard = (
                    payment.student_enrollment.academic_class.standard.name
                    if payment.student_enrollment.academic_class.standard
                    else ""
                )
                section = (
                    payment.student_enrollment.academic_class.section.name
                    if payment.student_enrollment.academic_class.section
                    else ""
                )
                class_name = f"{standard} - {section}".strip(" -")

            return {
                "receipt_number": payment.receipt_number,
                "date": payment.payment_date.strftime("%d-%b-%Y"),
                "student_name": student_name,
                "class_name": class_name,
                "total_amount": float(payment.amount_paid),
                "payment_method": payment.get_payment_method_display(),
                "payment_type": payment.get_payment_type_display(),
                "components": [
                    {
                        "name": a.fee_component.name,
                        "amount": float(a.amount_paid),
                        "term": a.academic_term.name if a.academic_term else None,
                    }
                    for a in allocations
                ]
                + [
                    {
                        "name": "Transport Fee",
                        "amount": float(t.amount_paid),
                        "term": None,
                    }
                    for t in payment.transport_payments.all()
                ],
                "amount_in_words": f"Rupees {float(payment.amount_paid)} only",
            }

        except Exception as e:
            logger.error(f"[PAYMENT] Error in _generate_receipt: {e}")
            return {
                "receipt_number": getattr(payment, "receipt_number", "N/A"),
                "error": "Receipt generation failed",
            }


class GetPendingComponentsView(APIView):
    """
    API endpoint to get pending amounts for all fee components
    Useful for showing what can be paid in partial payments
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)

            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class", "academic_class__academic_year"
            ).get(id=enrollment_id, is_active=True)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                logger.error(
                    f"Student {enrollment.student.id} not linked to parent {parent.id}"
                )
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get all class fee structures for this enrollment's class
            class_fees = ClassFeeStructure.objects.filter(
                academic_class=enrollment.academic_class, is_active=True
            ).select_related("fee_component", "academic_term")

            components = []
            total_pending = 0
            total_fees = 0
            total_paid = 0

            for class_fee in class_fees:
                # Check for student assignment
                student_assignment = StudentFeeAssignment.objects.filter(
                    enrollment=enrollment, class_fee_structure=class_fee, is_active=True
                ).first()

                if student_assignment:
                    fee_amount = student_assignment.calculate_final_amount()

                    paid = (
                        PaymentFeeComponent.objects.filter(
                            student_fee_assignment=student_assignment
                        ).aggregate(total=models.Sum("amount_paid"))["total"]
                        or 0
                    )

                    discount_info = {
                        "type": student_assignment.discount_type,
                        "value": (
                            float(student_assignment.discount_value)
                            if student_assignment.discount_value
                            else None
                        ),
                    }

                    due_date = (
                        student_assignment.due_date_override or class_fee.due_date
                    )
                else:
                    fee_amount = class_fee.amount

                    paid = (
                        PaymentFeeComponent.objects.filter(
                            class_fee_structure=class_fee,
                            payment__student_enrollment=enrollment,
                        ).aggregate(total=models.Sum("amount_paid"))["total"]
                        or 0
                    )

                    discount_info = None
                    due_date = class_fee.due_date

                pending = max(0, float(fee_amount) - float(paid))

                component_data = {
                    "component_id": class_fee.fee_component.id,
                    "component_name": class_fee.fee_component.name,
                    "component_code": class_fee.fee_component.code,
                    "category": (
                        class_fee.fee_component.category.name
                        if class_fee.fee_component.category
                        else None
                    ),
                    "term_id": class_fee.academic_term.id,
                    "term_name": class_fee.academic_term.name,
                    "due_date": due_date,
                    "due_date_formatted": (
                        due_date.strftime("%d-%b-%Y") if due_date else None
                    ),
                    "total_amount": float(fee_amount),
                    "paid_amount": float(paid),
                    "pending_amount": pending,
                    "discount": discount_info,
                    "is_overdue": (
                        due_date < timezone.now().date() if due_date else False
                    ),
                    "is_mandatory": class_fee.fee_component.is_mandatory,
                }

                components.append(component_data)

                total_fees += float(fee_amount)
                total_paid += float(paid)
                total_pending += pending

            # Sort components: overdue first, then by due date
            components.sort(
                key=lambda x: (not x["is_overdue"], x["due_date"] or "9999-12-31")
            )

            # Get enrollment details
            class_name = None
            if enrollment.academic_class:
                standard = (
                    enrollment.academic_class.standard.name
                    if enrollment.academic_class.standard
                    else ""
                )
                section = (
                    enrollment.academic_class.section.name
                    if enrollment.academic_class.section
                    else ""
                )
                class_name = f"{standard} - {section}".strip(" -")

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollment_id": enrollment.id,
                        "student_id": enrollment.student.id,
                        "student_name": enrollment.student.full_name
                        or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                        "class_name": class_name,
                        "academic_year": (
                            enrollment.academic_class.academic_year.name
                            if enrollment.academic_class
                            else None
                        ),
                        "total_fees": round(total_fees, 2),
                        "total_paid": round(total_paid, 2),
                        "total_pending": round(total_pending, 2),
                        "components": components,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in GetPendingComponentsView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class GetPaymentReceiptView(APIView):
    """
    API endpoint to get receipt details for a specific payment
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        payment_id = request.query_params.get("payment_id")
        receipt_number = request.query_params.get("receipt_number")

        if not payment_id and not receipt_number:
            return Response(
                {
                    "success": False,
                    "message": "Either payment_id or receipt_number is required",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Get payment
            if payment_id:
                payment = FeePayment.objects.get(id=payment_id)
            else:
                payment = FeePayment.objects.get(receipt_number=receipt_number)

            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)

            student_parent = parent.student_parents.filter(
                student=payment.student, is_active=True
            ).first()

            if not student_parent:
                logger.error(
                    f"Access denied: Student {payment.student.id} not linked to parent {parent.id}"
                )
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get allocations
            allocations = payment.payment_components.select_related(
                "fee_component", "academic_term"
            ).all()

            # Get student name
            student_name = (
                payment.student.full_name
                or f"{payment.student.first_name} {payment.student.last_name}".strip()
            )

            # Get class name
            class_name = None 
            if payment.student_enrollment.academic_class:
                standard = (
                    payment.student_enrollment.academic_class.standard.name
                    if payment.student_enrollment.academic_class.standard
                    else ""
                )
                section = (
                    payment.student_enrollment.academic_class.section.name
                    if payment.student_enrollment.academic_class.section
                    else ""
                )
                class_name = f"{standard} - {section}".strip(" -")

            # Get school info from request context
            school = getattr(request, 'school', None)

            receipt_data = {
                "receipt_number": payment.receipt_number,
                "payment_date": payment.payment_date.strftime("%d-%b-%Y"),
                "payment_time": payment.created_at.strftime("%H:%M:%S"),
                "student_name": student_name,
                "student_id": payment.student.id,
                "enrollment_id": payment.student_enrollment.id,
                "class_name": class_name,
                "amount_paid": float(payment.amount_paid),
                "payment_method": payment.get_payment_method_display(),
                "payment_type": payment.get_payment_type_display(),
                "status": payment.get_status_display(),
                "principal_amount": float(payment.principal_amount),
                "late_fee_amount": float(payment.late_fee_amount),
                "balance_before": float(payment.balance_before),
                "balance_after": float(payment.balance_after),
                "notes": payment.notes,
                "recorded_by": payment.recorded_by_name,
                "components": [
                    {
                        "component_name": a.fee_component.name,
                        "component_code": a.fee_component.code,
                        "amount": float(a.amount_paid),
                        "term": a.academic_term.name if a.academic_term else None,
                    }
                    for a in allocations
                ]
                + [
                    {
                        "component_name": "Transport Fee",
                        "component_code": "TRANSPORT",
                        "amount": float(t.amount_paid),
                        "term": None,
                    }
                    for t in payment.transport_payments.all()
                ],
                "created_at": payment.created_at.strftime("%d-%b-%Y %H:%M:%S"),
                "school_name": school.name if school else None,
                "school_address": school.address_line1 if school else None,
                "school_phone": school.phone if school else None,
                "school_email": school.email if school else None,
                "school_established_year": school.established_year if school else None,
            }

            return Response({"success": True, "data": receipt_data})

        except FeePayment.DoesNotExist:
            return Response(
                {"success": False, "message": "Payment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in GetPaymentReceiptView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from django.db import transaction, models
from django.db.models import Q, Count
from django.utils import timezone
from people.models import Parent, Student
from academics.models import AcademicClass, StudentEnrollment
from tasks.models import (
    ClassTask,
    TaskItem,
    StudentTask,
    StudentTaskItem,
    TaskSubmission,
    SpecificStudentTask,
    SpecificStudentTaskAssignment,
    SpecificStudentTaskItem,
)
import logging

logger = logging.getLogger(__name__)


# =====================================================
# HOMEWORK LIST — no questions, submission status only
# =====================================================


class StudentHomeworkListView(APIView):
    """
    Returns task list only (no questions).
    Checks TaskSubmission table for submission status per task.

    Query Parameters:
        enrollment_id   (int, preferred) — year-scoped; class_id derived automatically
        class_id        (int) — use with optional academic_year_id to filter by year
        academic_year_id (int, optional) — validates class belongs to this year
        date            (str, optional) — YYYY-MM-DD exact due date
        from_date       (str, optional) — YYYY-MM-DD
        to_date         (str, optional) — YYYY-MM-DD
        subject_id      (int, optional)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        class_id = request.query_params.get("class_id")
        academic_year_id = request.query_params.get("academic_year_id")
        from_date = request.query_params.get("from_date")
        to_date = request.query_params.get("to_date")
        single_day = request.query_params.get("date")
        subject_id = request.query_params.get("subject_id")

        if not enrollment_id and not class_id:
            return Response(
                {"success": False, "message": "Either enrollment_id or class_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # ── Verify parent ──────────────────────────────────────────
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            child_student_ids = parent.student_parents.filter(
                is_active=True
            ).values_list("student_id", flat=True)

            # ── Resolve enrollment + class ─────────────────────────────
            enrollment = None
            if enrollment_id:
                # enrollment_id is year-scoped — derive class from it
                try:
                    enrollment = StudentEnrollment.objects.select_related(
                        "student", "academic_class", "academic_class__standard",
                        "academic_class__section",
                    ).get(
                        id=enrollment_id,
                        student_id__in=child_student_ids,
                        is_active=True,
                    )
                except StudentEnrollment.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Enrollment not found or does not belong to your child"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
                academic_class = enrollment.academic_class
                class_id = academic_class.id
            else:
                # class_id path — optionally filter by academic year
                filters = {"id": class_id, "is_active": True}
                if academic_year_id:
                    filters["academic_year_id"] = academic_year_id
                try:
                    academic_class = AcademicClass.objects.select_related(
                        "standard", "section"
                    ).get(**filters)
                except AcademicClass.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Class not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

                # Verify parent has a child in this class
                has_access = StudentEnrollment.objects.filter(
                    academic_class_id=class_id,
                    student_id__in=child_student_ids,
                    is_active=True,
                ).exists()

                if not has_access:
                    return Response(
                        {"success": False, "message": "You don't have a child enrolled in this class"},
                        status=status.HTTP_403_FORBIDDEN,
                    )

            # ══════════════════════════════════════════════════════════
            # CLASS TASKS
            # ══════════════════════════════════════════════════════════

            class_tasks_qs = (
                ClassTask.objects.filter(
                    academic_class_id=class_id,
                    # is_published=True,
                    is_active=True,
                )
                .select_related("subject", "task_type", "posted_by")
                .annotate(
                    total_questions=Count(
                        "task_items", filter=Q(task_items__is_active=True)
                    )
                )
                .order_by("-due_date", "-created_at")
            )

            # Date filters
            if single_day:
                class_tasks_qs = class_tasks_qs.filter(due_date=single_day)
            else:
                if from_date:
                    class_tasks_qs = class_tasks_qs.filter(due_date__gte=from_date)
                if to_date:
                    class_tasks_qs = class_tasks_qs.filter(due_date__lte=to_date)

            if subject_id:
                class_tasks_qs = class_tasks_qs.filter(subject_id=subject_id)

            # ── Submission status map — one query for all tasks ────────
            submission_map = {}
            if enrollment:
                submissions = TaskSubmission.objects.filter(
                    enrollment=enrollment,
                    task__in=class_tasks_qs.values_list("id", flat=True),
                ).values("task_id", "status", "submitted_at", "marks_obtained")
                submission_map = {s["task_id"]: s for s in submissions}

            # ── Build class tasks response ─────────────────────────────
            class_tasks_data = []
            for task in class_tasks_qs:
                submission = submission_map.get(task.id)
                class_tasks_data.append(
                    {
                        "id": task.id,
                        "type": "class_task",
                        "title": task.title,
                        "description": task.description,
                        "document": (
                            request.build_absolute_uri(task.document.url)
                            if task.document
                            else None
                        ),
                        "subject": {
                            "id": task.subject.id if task.subject else None,
                            "name": task.subject.name if task.subject else None,
                            "code": task.subject.code if task.subject else None,
                        },
                        "task_type": {
                            "id": task.task_type.id if task.task_type else None,
                            "name": task.task_type.name if task.task_type else None,
                            "code": task.task_type.code if task.task_type else None,
                        },
                        "posted_by": {
                            "id": task.posted_by.id if task.posted_by else None,
                            "name": (
                                f"{task.posted_by.first_name} {task.posted_by.last_name}".strip()
                                if task.posted_by
                                else None
                            ),
                        },
                        "total_marks": task.total_marks,
                        "due_date": task.due_date,
                        "total_questions": task.total_questions,
                        "created_at": task.created_at,
                        "submission": {
                            "is_submitted": submission is not None,
                            "status": (
                                submission["status"] if submission else "NOT_SUBMITTED"
                            ),
                            "submitted_at": (
                                submission["submitted_at"] if submission else None
                            ),
                            "marks_obtained": (
                                float(submission["marks_obtained"])
                                if submission and submission["marks_obtained"]
                                else None
                            ),
                        },
                    }
                )

            # ══════════════════════════════════════════════════════════
            # SPECIFIC TASKS
            # ══════════════════════════════════════════════════════════

            specific_tasks_data = []

            if enrollment and enrollment.student:
                specific_tasks_qs = (
                    SpecificStudentTaskAssignment.objects.filter(
                        student=enrollment.student,
                        specific_task__is_active=True,
                    )
                    .select_related(
                        "specific_task",
                        "specific_task__subject",
                        "specific_task__created_by",
                    )
                    .annotate(
                        total_questions=Count(
                            "specific_task__task_items",
                            filter=Q(specific_task__task_items__assignment=None),
                        )
                    )
                    .order_by("-specific_task__due_date", "-specific_task__created_at")
                )

                if single_day:
                    specific_tasks_qs = specific_tasks_qs.filter(
                        specific_task__due_date=single_day
                    )
                else:
                    if from_date:
                        specific_tasks_qs = specific_tasks_qs.filter(
                            specific_task__due_date__gte=from_date
                        )
                    if to_date:
                        specific_tasks_qs = specific_tasks_qs.filter(
                            specific_task__due_date__lte=to_date
                        )

                if subject_id:
                    specific_tasks_qs = specific_tasks_qs.filter(
                        specific_task__subject_id=subject_id
                    )

                for assignment in specific_tasks_qs:
                    task = assignment.specific_task
                    specific_tasks_data.append(
                        {
                            "id": task.id,
                            "type": "specific_task",
                            "assignment_id": assignment.id,
                            "title": task.title,
                            "description": task.description,
                            "document": (
                                request.build_absolute_uri(task.document.url)
                                if task.document
                                else None
                            ),
                            "subject": {
                                "id": task.subject.id if task.subject else None,
                                "name": task.subject.name if task.subject else None,
                                "code": task.subject.code if task.subject else None,
                            },
                            "posted_by": {
                                "id": task.created_by.id if task.created_by else None,
                                "name": (
                                    f"{task.created_by.first_name} {task.created_by.last_name}".strip()
                                    if task.created_by
                                    else None
                                ),
                            },
                            "total_marks": task.total_marks,
                            "due_date": task.due_date,
                            "total_questions": assignment.total_questions,
                            "created_at": task.created_at,
                            "submission": {
                                "is_submitted": assignment.status != "PENDING",
                                "status": assignment.status,
                                "submitted_at": None,
                                "marks_obtained": None,
                            },
                        }
                    )

            return Response(
                {
                    "success": True,
                    "class_info": {
                        "id": academic_class.id,
                        "standard": (
                            academic_class.standard.name
                            if academic_class.standard
                            else None
                        ),
                        "section": (
                            academic_class.section.name
                            if academic_class.section
                            else None
                        ),
                    },
                    "filters_applied": {
                        "class_id": class_id,
                        "enrollment_id": enrollment_id,
                        "date": single_day,
                        "from_date": from_date,
                        "to_date": to_date,
                        "subject_id": subject_id,
                    },
                    "class_tasks": {
                        "count": len(class_tasks_data),
                        "data": class_tasks_data,
                    },
                    "specific_tasks": {
                        "count": len(specific_tasks_data),
                        "fetched": enrollment_id is not None,
                        "data": specific_tasks_data,
                    },
                }
            )

        except Exception as e:
            logger.error(f"Error in StudentHomeworkListView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# OPEN TASK — creates StudentTask on first open
# returns questions + any saved answers
# =====================================================


class OpenTaskView(APIView):
    """
    Called when student taps/opens a task.

    For class_task:
      - Creates StudentTask (get_or_create) on first open
      - Returns questions + task_item_id per question (used when saving answer)
      - Returns previously saved answers if student is returning

    For specific_task:
      - Assignment already exists from teacher side
      - Answer copy rows already created by teacher when assigning
      - Returns questions + answer_item_id per question (used when saving answer)
      - Returns previously saved answers if any

    POST {
        "task_id":       1,
        "task_type":     "class_task",   // or "specific_task"
        "enrollment_id": 11
    }
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        task_id = request.data.get("task_id")
        task_type = request.data.get("task_type", "class_task")
        enrollment_id = request.data.get("enrollment_id")

        if not all([task_id, enrollment_id]):
            return Response(
                {"success": False, "message": "task_id and enrollment_id are required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # ── Verify parent ──────────────────────────────────────────
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            child_student_ids = parent.student_parents.filter(
                is_active=True
            ).values_list("student_id", flat=True)

            # ── Validate enrollment ────────────────────────────────────
            try:
                enrollment = StudentEnrollment.objects.select_related(
                    "student", "academic_class"
                ).get(
                    id=enrollment_id,
                    student_id__in=child_student_ids,
                    is_active=True,
                )
            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            if task_type == "class_task":
                return self._open_class_task(task_id, enrollment, request)
            elif task_type == "specific_task":
                return self._open_specific_task(task_id, enrollment, request)
            else:
                return Response(
                    {
                        "success": False,
                        "message": "task_type must be 'class_task' or 'specific_task'",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

        except Exception as e:
            logger.error(f"Error in OpenTaskView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _open_class_task(self, task_id, enrollment, request):
        # ── Verify task ────────────────────────────────────────────────
        try:
            task = ClassTask.objects.select_related(
                "subject", "task_type", "posted_by"
            ).get(
                id=task_id,
                academic_class=enrollment.academic_class,
                # is_published=True,
                is_active=True,
            )
        except ClassTask.DoesNotExist:
            return Response(
                {"success": False, "message": "Task not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # ── Create StudentTask on first open ───────────────────────────
        with transaction.atomic():
            student_task, is_first_open = StudentTask.objects.get_or_create(
                class_task=task,
                student=enrollment.student,
                defaults={
                    "enrollment": enrollment,
                    "status": "PENDING",
                    "is_active": True,
                },
            )

        # ── All questions ──────────────────────────────────────────────
        questions = TaskItem.objects.filter(
            class_task=task,
            is_active=True,
        ).order_by("order")

        # ── Previously saved answers ───────────────────────────────────
        # key: task_item_id → answer data
        saved_answers = {}
        if not is_first_open:
            answers = StudentTaskItem.objects.filter(
                student_task=student_task,
            ).values(
                "id",  # StudentTaskItem.id
                "task_item_id",  # matches question.id
                "answer_text",
                "answer_file",
                "marks_obtained",
                "teacher_remark",
                "submitted_at",
            )
            saved_answers = {a["task_item_id"]: a for a in answers}

        # ── Submission status ──────────────────────────────────────────
        submission = TaskSubmission.objects.filter(
            task=task,
            enrollment=enrollment,
        ).first()

        return Response(
            {
                "success": True,
                "type": "class_task",
                "student_task_id": student_task.id,
                "is_first_open": is_first_open,
                "task_status": student_task.status,
                "task_info": {
                    "id": task.id,
                    "title": task.title,
                    "description": task.description,
                    "document": (
                        request.build_absolute_uri(task.document.url)
                        if task.document
                        else None
                    ),
                    "subject": {
                        "id": task.subject.id if task.subject else None,
                        "name": task.subject.name if task.subject else None,
                        "code": task.subject.code if task.subject else None,
                    },
                    "task_type": {
                        "id": task.task_type.id if task.task_type else None,
                        "name": task.task_type.name if task.task_type else None,
                        "code": task.task_type.code if task.task_type else None,
                    },
                    "posted_by": {
                        "id": task.posted_by.id if task.posted_by else None,
                        "name": (
                            f"{task.posted_by.first_name} {task.posted_by.last_name}".strip()
                            if task.posted_by
                            else None
                        ),
                    },
                    "total_marks": task.total_marks,
                    "due_date": task.due_date,
                },
                # questions list — frontend stores task_item_id per question
                # and sends it back when saving answers
                "questions": [
                    {
                        "task_item_id": q.id,  # ← KEY: send this when saving answer
                        "order": q.order,
                        "question_text": q.question_text,
                        "marks": q.marks,
                        "attachment": (
                            request.build_absolute_uri(q.attachment.url)
                            if q.attachment
                            else None
                        ),
                        # previously saved answer — null on first open
                        "saved_answer": (
                            {
                                "answer_id": saved_answers[q.id]["id"],
                                "answer_text": saved_answers[q.id]["answer_text"],
                                "answer_file": saved_answers[q.id]["answer_file"],
                                "marks_obtained": saved_answers[q.id]["marks_obtained"],
                                "teacher_remark": saved_answers[q.id]["teacher_remark"],
                                "saved_at": saved_answers[q.id]["submitted_at"],
                            }
                            if q.id in saved_answers
                            else None
                        ),
                    }
                    for q in questions
                ],
                "submission": {
                    "is_submitted": submission is not None,
                    "status": submission.status if submission else "NOT_SUBMITTED",
                    "submitted_at": submission.submitted_at if submission else None,
                    "marks_obtained": (
                        float(submission.marks_obtained)
                        if submission and submission.marks_obtained
                        else None
                    ),
                    "remarks": submission.remarks if submission else None,
                },
            }
        )

    def _open_specific_task(self, task_id, enrollment, request):
        # ── Get assignment for this student ────────────────────────────
        try:
            assignment = SpecificStudentTaskAssignment.objects.select_related(
                "specific_task",
                "specific_task__subject",
                "specific_task__created_by",
            ).get(
                specific_task_id=task_id,
                student=enrollment.student,
                specific_task__is_active=True,
            )
        except SpecificStudentTaskAssignment.DoesNotExist:
            return Response(
                {"success": False, "message": "Task not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        task = assignment.specific_task

        # ── Master questions (assignment=None) ─────────────────────────
        questions = SpecificStudentTaskItem.objects.filter(
            specific_task=task,
            assignment=None,
        ).order_by("order")

        # ── Student's answer copy rows (assignment=SET) ────────────────
        # These were pre-created by teacher when assigning
        # key: order → answer data (match by order since no direct FK to master)
        answer_copies = SpecificStudentTaskItem.objects.filter(
            specific_task=task,
            assignment=assignment,
        ).values(
            "id",  # ← answer_item_id to send when saving answer
            "order",
            "answer_text",
            "answer_file",
            "marks_obtained",
            "teacher_remark",
            "submitted_at",
        )
        answer_map = {a["order"]: a for a in answer_copies}

        return Response(
            {
                "success": True,
                "type": "specific_task",
                "assignment_id": assignment.id,
                "task_status": assignment.status,
                "task_info": {
                    "id": task.id,
                    "title": task.title,
                    "description": task.description,
                    "document": (
                        request.build_absolute_uri(task.document.url)
                        if task.document
                        else None
                    ),
                    "subject": {
                        "id": task.subject.id if task.subject else None,
                        "name": task.subject.name if task.subject else None,
                        "code": task.subject.code if task.subject else None,
                    },
                    "posted_by": {
                        "id": task.created_by.id if task.created_by else None,
                        "name": (
                            f"{task.created_by.first_name} {task.created_by.last_name}".strip()
                            if task.created_by
                            else None
                        ),
                    },
                    "total_marks": task.total_marks,
                    "due_date": task.due_date,
                },
                # questions list — frontend stores answer_item_id per question
                # and sends it back when saving answers
                "questions": [
                    {
                        "answer_item_id": answer_map.get(q.order, {}).get(
                            "id"
                        ),  # ← KEY: send this when saving
                        "order": q.order,
                        "question_text": q.question_text,
                        "marks": q.marks,
                        "attachment": (
                            request.build_absolute_uri(q.attachment.url)
                            if q.attachment
                            else None
                        ),
                        # previously saved answer — null if not yet answered
                        "saved_answer": (
                            {
                                "answer_text": answer_map[q.order]["answer_text"],
                                "answer_file": answer_map[q.order]["answer_file"],
                                "marks_obtained": answer_map[q.order]["marks_obtained"],
                                "teacher_remark": answer_map[q.order]["teacher_remark"],
                                "saved_at": answer_map[q.order]["submitted_at"],
                            }
                            if answer_map.get(q.order)
                            and (
                                answer_map[q.order]["answer_text"]
                                or answer_map[q.order]["answer_file"]
                            )
                            else None
                        ),
                    }
                    for q in questions
                ],
                "submission": {
                    "is_submitted": assignment.status != "PENDING",
                    "status": assignment.status,
                    "marks_obtained": None,
                },
            }
        )


# =====================================================
# SAVE ANSWER — auto-save draft per question
# =====================================================


class SaveAnswerView(APIView):
    """
    Save/update a single question answer — auto-saves draft.
    Called every time student types or changes an answer.
    Does NOT mark as submitted.

    For class_task:
    POST {
        "task_id":       1,
        "task_type":     "class_task",
        "enrollment_id": 11,
        "task_item_id":  3,         // from open response questions[].task_item_id
        "answer_text":   "My answer",
        "answer_file":   <file>     // optional multipart
    }

    For specific_task:
    POST {
        "task_id":        2,
        "task_type":      "specific_task",
        "enrollment_id":  11,
        "answer_item_id": 7,        // from open response questions[].answer_item_id
        "answer_text":    "My answer",
        "answer_file":    <file>    // optional multipart
    }
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        task_id = request.data.get("task_id")
        task_type = request.data.get("task_type", "class_task")
        enrollment_id = request.data.get("enrollment_id")
        answer_text = request.data.get("answer_text", "")
        answer_file = request.FILES.get("answer_file")

        if not all([task_id, enrollment_id]):
            return Response(
                {"success": False, "message": "task_id and enrollment_id are required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # ── Verify parent ──────────────────────────────────────────
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            child_student_ids = parent.student_parents.filter(
                is_active=True
            ).values_list("student_id", flat=True)

            # ── Validate enrollment ────────────────────────────────────
            try:
                enrollment = StudentEnrollment.objects.select_related(
                    "student", "academic_class"
                ).get(
                    id=enrollment_id,
                    student_id__in=child_student_ids,
                    is_active=True,
                )
            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # ── Route by task type ─────────────────────────────────────
            if task_type == "class_task":
                task_item_id = request.data.get("task_item_id")
                if not task_item_id:
                    return Response(
                        {
                            "success": False,
                            "message": "task_item_id is required for class_task",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                return self._save_class_task_answer(
                    task_id, enrollment, task_item_id, answer_text, answer_file
                )

            elif task_type == "specific_task":
                answer_item_id = request.data.get("answer_item_id")
                if not answer_item_id:
                    return Response(
                        {
                            "success": False,
                            "message": "answer_item_id is required for specific_task",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                return self._save_specific_task_answer(
                    task_id, enrollment, answer_item_id, answer_text, answer_file
                )

            else:
                return Response(
                    {
                        "success": False,
                        "message": "task_type must be 'class_task' or 'specific_task'",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

        except Exception as e:
            logger.error(f"Error in SaveAnswerView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _save_class_task_answer(
        self, task_id, enrollment, task_item_id, answer_text, answer_file
    ):
        # ── Verify task ────────────────────────────────────────────────
        try:
            task = ClassTask.objects.get(
                id=task_id,
                academic_class=enrollment.academic_class,
                # is_published=True,
                is_active=True,
            )
        except ClassTask.DoesNotExist:
            return Response(
                {"success": False, "message": "Task not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # ── Verify question belongs to this task ───────────────────────
        try:
            task_item = TaskItem.objects.get(
                id=task_item_id,
                class_task=task,
                is_active=True,
            )
        except TaskItem.DoesNotExist:
            return Response(
                {"success": False, "message": "Question not found in this task"},
                status=status.HTTP_404_NOT_FOUND,
            )

        with transaction.atomic():
            # StudentTask should already exist from OpenTaskView
            # get_or_create as safety fallback
            student_task, _ = StudentTask.objects.get_or_create(
                class_task=task,
                student=enrollment.student,
                defaults={
                    "enrollment": enrollment,
                    "status": "PENDING",
                    "is_active": True,
                },
            )

            # Block edits after final submission
            if student_task.status == "SUBMITTED":
                return Response(
                    {
                        "success": False,
                        "message": "Task already submitted, cannot edit answers",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Get or create answer row for this question
            student_answer, created = StudentTaskItem.objects.get_or_create(
                student_task=student_task,
                task_item=task_item,
            )

            student_answer.answer_text = answer_text
            student_answer.submitted_at = timezone.now()
            if answer_file:
                student_answer.answer_file = answer_file
            student_answer.save()

        return Response(
            {
                "success": True,
                "message": "Answer saved",
                "answer_id": student_answer.id,  # StudentTaskItem.id
                "task_item_id": task_item.id,
                "question_order": task_item.order,
                "is_new": created,
            }
        )

    def _save_specific_task_answer(
        self, task_id, enrollment, answer_item_id, answer_text, answer_file
    ):
        # ── Verify assignment exists for this student ──────────────────
        try:
            assignment = SpecificStudentTaskAssignment.objects.get(
                specific_task_id=task_id,
                student=enrollment.student,
                specific_task__is_active=True,
            )
        except SpecificStudentTaskAssignment.DoesNotExist:
            return Response(
                {"success": False, "message": "Task assignment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Block edits after final submission
        if assignment.status == "SUBMITTED":
            return Response(
                {
                    "success": False,
                    "message": "Task already submitted, cannot edit answers",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        # ── Get the answer copy row ────────────────────────────────────
        # answer_item_id = SpecificStudentTaskItem.id where assignment=SET
        # This row was pre-created by teacher when assigning task
        try:
            answer_item = SpecificStudentTaskItem.objects.get(
                id=answer_item_id,
                assignment=assignment,  # must belong to THIS student
                specific_task_id=task_id,
            )
        except SpecificStudentTaskItem.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Answer item not found for this assignment",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

        # Update answer directly on the copy row
        answer_item.answer_text = answer_text
        answer_item.submitted_at = timezone.now()
        if answer_file:
            answer_item.answer_file = answer_file
        answer_item.save()

        return Response(
            {
                "success": True,
                "message": "Answer saved",
                "answer_item_id": answer_item.id,
                "question_order": answer_item.order,
            }
        )


# =====================================================
# FINAL SUBMIT — marks task as submitted, no more edits
# =====================================================


class SubmitHomeworkView(APIView):
    """
    Final submission — no more edits after this.
    Creates TaskSubmission record (what teacher sees).

    POST {
        "task_id":       1,
        "task_type":     "class_task",   // or "specific_task"
        "enrollment_id": 11
    }
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        task_id = request.data.get("task_id")
        task_type = request.data.get("task_type", "class_task")
        enrollment_id = request.data.get("enrollment_id")

        if not all([task_id, enrollment_id]):
            return Response(
                {"success": False, "message": "task_id and enrollment_id are required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # ── Verify parent ──────────────────────────────────────────
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            child_student_ids = parent.student_parents.filter(
                is_active=True
            ).values_list("student_id", flat=True)

            # ── Validate enrollment ────────────────────────────────────
            try:
                enrollment = StudentEnrollment.objects.select_related(
                    "student", "academic_class"
                ).get(
                    id=enrollment_id,
                    student_id__in=child_student_ids,
                    is_active=True,
                )
            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            if task_type == "class_task":
                return self._submit_class_task(task_id, enrollment)
            elif task_type == "specific_task":
                return self._submit_specific_task(task_id, enrollment)
            else:
                return Response(
                    {
                        "success": False,
                        "message": "task_type must be 'class_task' or 'specific_task'",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

        except Exception as e:
            logger.error(f"Error in SubmitHomeworkView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _submit_class_task(self, task_id, enrollment):
        # ── Verify task ────────────────────────────────────────────────
        try:
            task = ClassTask.objects.get(
                id=task_id,
                academic_class=enrollment.academic_class,
                # is_published=True,
                is_active=True,
            )
        except ClassTask.DoesNotExist:
            return Response(
                {"success": False, "message": "Task not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # ── StudentTask must exist ─────────────────────────────────────
        try:
            student_task = StudentTask.objects.get(
                class_task=task,
                student=enrollment.student,
                is_active=True,
            )
        except StudentTask.DoesNotExist:
            return Response(
                {
                    "success": False,
                    "message": "Please open the task and save at least one answer before submitting",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Already submitted
        if student_task.status == "SUBMITTED":
            return Response(
                {"success": False, "message": "Task already submitted"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Must have at least one answer saved
        has_answers = (
            StudentTaskItem.objects.filter(
                student_task=student_task,
            )
            .filter(
                Q(answer_text__isnull=False, answer_text__gt="")
                | Q(answer_file__isnull=False, answer_file__gt="")
            )
            .exists()
        )

        if not has_answers:
            return Response(
                {
                    "success": False,
                    "message": "Please answer at least one question before submitting",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        now = timezone.now()

        # Calculate delay days
        delay_days = None
        submission_status = "SUBMITTED"
        if task.due_date and now.date() > task.due_date:
            delay_days = (now.date() - task.due_date).days
            submission_status = "LATE"

        with transaction.atomic():
            # Mark StudentTask submitted
            student_task.status = "SUBMITTED"
            student_task.submitted_at = now
            student_task.save(update_fields=["status", "submitted_at", "updated_at"])

            # Create TaskSubmission — teacher sees this
            submission, _ = TaskSubmission.objects.update_or_create(
                task=task,
                enrollment=enrollment,
                defaults={
                    "submitted_at": now,
                    "status": submission_status,
                    "submission_delay_days": delay_days,
                    "is_active": True,
                },
            )

        return Response(
            {
                "success": True,
                "message": "Task submitted successfully"
                + (" (submitted late)" if delay_days else ""),
                "submission_id": submission.id,
                "status": submission.status,
                "submitted_at": submission.submitted_at,
                "is_late": submission_status == "LATE",
                "delay_days": delay_days,
            },
            status=status.HTTP_201_CREATED,
        )

    def _submit_specific_task(self, task_id, enrollment):
        # ── Verify assignment ──────────────────────────────────────────
        try:
            assignment = SpecificStudentTaskAssignment.objects.select_related(
                "specific_task"
            ).get(
                specific_task_id=task_id,
                student=enrollment.student,
                specific_task__is_active=True,
            )
        except SpecificStudentTaskAssignment.DoesNotExist:
            return Response(
                {"success": False, "message": "Task assignment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        if assignment.status == "SUBMITTED":
            return Response(
                {"success": False, "message": "Task already submitted"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Must have at least one answer
        has_answers = (
            SpecificStudentTaskItem.objects.filter(
                assignment=assignment,
            )
            .filter(
                Q(answer_text__isnull=False, answer_text__gt="")
                | Q(answer_file__isnull=False, answer_file__gt="")
            )
            .exists()
        )

        if not has_answers:
            return Response(
                {
                    "success": False,
                    "message": "Please answer at least one question before submitting",
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        with transaction.atomic():
            assignment.status = "SUBMITTED"
            assignment.save(update_fields=["status"])

        return Response(
            {
                "success": True,
                "message": "Task submitted successfully",
                "assignment_id": assignment.id,
                "status": assignment.status,
            },
            status=status.HTTP_201_CREATED,
        )


class ParentAttendanceOverviewView(APIView):
    """
    GET /parent/attendance/overview/

    Get overall attendance summary for a student.
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional, defaults to current)
        month: int (optional, 1-12)
        year: int (optional)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        month = request.query_params.get("month")
        year = request.query_params.get("year")
        academic_year_id = request.query_params.get("academic_year_id")

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class", "academic_class__academic_year"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get academic year
            if academic_year_id:
                academic_year = AcademicYear.objects.get(
                    id=academic_year_id, is_active=True
                )
            else:
                today = timezone.now().date()
                academic_year = AcademicYear.objects.filter(
                    is_active=True, start_date__lte=today, end_date__gte=today
                ).first()

                if not academic_year:
                    academic_year = AcademicYear.objects.filter(is_active=True).first()

            # Build base queryset
            attendance_qs = StudentAttendance.objects.filter(
                enrollment=enrollment,
                session__academic_class=enrollment.academic_class,
                is_active=True,
            ).select_related("session")

            # Apply month/year filters
            if month and year:
                attendance_qs = attendance_qs.filter(
                    session__date__month=month, session__date__year=year
                )
            elif month:
                attendance_qs = attendance_qs.filter(
                    session__date__month=month, session__date__year=timezone.now().year
                )
            elif year:
                attendance_qs = attendance_qs.filter(session__date__year=year)

            # Calculate statistics
            total_days = attendance_qs.count()
            present_days = attendance_qs.filter(status="PRESENT").count()
            absent_days = attendance_qs.filter(status="ABSENT").count()
            late_days = attendance_qs.filter(status="LATE").count()
            leave_days = attendance_qs.filter(status="LEAVE").count()
            half_days = attendance_qs.filter(status="HALF_DAY").count()

            # Calculate attendance percentage (late counts as present, half day as 0.5)
            effective_present = present_days + late_days + (half_days * 0.5)
            attendance_percentage = (
                round((effective_present / total_days * 100), 2)
                if total_days > 0
                else 0
            )

            # Check if below minimum
            min_percentage = enrollment.academic_class.min_attendance_percentage or 75.0
            is_below_minimum = attendance_percentage < min_percentage

            # Get recent attendance (last 10 days)
            recent_attendance = attendance_qs.order_by("-session__date")[:10]

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                            "academic_year": (
                                academic_year.name if academic_year else None
                            ),
                        },
                        "summary": {
                            "total_working_days": total_days,
                            "present_days": present_days,
                            "absent_days": absent_days,
                            "late_days": late_days,
                            "leave_days": leave_days,
                            "half_days": half_days,
                            "attendance_percentage": attendance_percentage,
                            "is_below_minimum": is_below_minimum,
                            "minimum_required": min_percentage,
                        },
                        "recent_attendance": ParentStudentAttendanceSerializer(
                            recent_attendance, many=True
                        ).data,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentAttendanceOverviewView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# PARENT ATTENDANCE DETAILS (with filters)
# =====================================================


class ParentAttendanceDetailsView(APIView):
    """
    GET /parent/attendance/details/

    Get detailed attendance records with filters.
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional) — validates enrollment belongs to this year
        from_date: date (optional)
        to_date: date (optional)
        status: string (optional) - PRESENT, ABSENT, LATE, LEAVE, HALF_DAY
        month: int (optional) - 1-12
        year: int (optional)
        page: int (default 1)
        page_size: int (default 20)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        from_date = request.query_params.get("from_date")
        to_date = request.query_params.get("to_date")
        status_filter = request.query_params.get("status")
        month = request.query_params.get("month")
        year = request.query_params.get("year")
        page = int(request.query_params.get("page", 1))
        page_size = int(request.query_params.get("page_size", 20))

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Build queryset
            attendance_qs = StudentAttendance.objects.filter(
                enrollment=enrollment,
                session__academic_class=enrollment.academic_class,
                is_active=True,
            ).select_related("session")

            # Apply date filters
            if from_date:
                attendance_qs = attendance_qs.filter(session__date__gte=from_date)
            if to_date:
                attendance_qs = attendance_qs.filter(session__date__lte=to_date)

            # Apply month/year filters
            if month and year:
                attendance_qs = attendance_qs.filter(
                    session__date__month=month, session__date__year=year
                )
            elif month:
                attendance_qs = attendance_qs.filter(
                    session__date__month=month, session__date__year=timezone.now().year
                )
            elif year:
                attendance_qs = attendance_qs.filter(session__date__year=year)

            # Apply status filter
            if status_filter:
                attendance_qs = attendance_qs.filter(status=status_filter)

            # Order by date
            attendance_qs = attendance_qs.order_by("-session__date")

            # Pagination
            total_count = attendance_qs.count()
            start = (page - 1) * page_size
            end = start + page_size
            paginated_records = attendance_qs[start:end]

            # Calculate statistics for filtered results
            stats = attendance_qs.aggregate(
                present=Count("id", filter=Q(status="PRESENT")),
                absent=Count("id", filter=Q(status="ABSENT")),
                late=Count("id", filter=Q(status="LATE")),
                leave=Count("id", filter=Q(status="LEAVE")),
                half_day=Count("id", filter=Q(status="HALF_DAY")),
            )

            total_filtered = total_count
            effective_present = (
                stats["present"] + stats["late"] + (stats["half_day"] * 0.5)
            )
            attendance_percentage = (
                round((effective_present / total_filtered * 100), 2)
                if total_filtered > 0
                else 0
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "filters_applied": {
                            "from_date": from_date,
                            "to_date": to_date,
                            "status": status_filter,
                            "month": month,
                            "year": year,
                        },
                        "statistics": {
                            "total_records": total_filtered,
                            "present": stats["present"],
                            "absent": stats["absent"],
                            "late": stats["late"],
                            "leave": stats["leave"],
                            "half_day": stats["half_day"],
                            "attendance_percentage": attendance_percentage,
                        },
                        "attendance_records": ParentStudentAttendanceSerializer(
                            paginated_records, many=True
                        ).data,
                        "pagination": {
                            "current_page": page,
                            "page_size": page_size,
                            "total_pages": (total_count + page_size - 1) // page_size,
                            "total_count": total_count,
                            "has_next": end < total_count,
                            "has_previous": page > 1,
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentAttendanceDetailsView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# PARENT MONTHLY ATTENDANCE
# =====================================================


class ParentMonthlyAttendanceView(APIView):
    """
    GET /parent/attendance/monthly-summary/

    Get monthly attendance summary for a student.
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional) — validates enrollment belongs to this year
        year: int (optional, defaults to current year)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        year = request.query_params.get("year", timezone.now().year)

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get or create monthly summaries
            summaries = AttendanceSummary.objects.filter(
                enrollment=enrollment, year=year
            ).order_by("-month")

            # If no summaries exist, calculate from attendance records
            if not summaries.exists():
                self._calculate_monthly_summaries(enrollment, year)
                summaries = AttendanceSummary.objects.filter(
                    enrollment=enrollment, year=year
                ).order_by("-month")

            # Prepare monthly data
            monthly_data = []
            for summary in summaries:
                monthly_data.append(
                    {
                        "month": summary.month,
                        "month_name": self._get_month_name(summary.month),
                        "year": summary.year,
                        "total_working_days": summary.total_working_days,
                        "present": summary.present_days,
                        "absent": summary.absent_days,
                        "late": summary.late_days,
                        "leave": summary.leave_days,
                        "half_days": summary.half_days,
                        "attendance_percentage": float(summary.attendance_percentage),
                        "is_below_minimum": summary.is_below_minimum,
                    }
                )

            # Calculate yearly statistics
            yearly_stats = summaries.aggregate(
                total_days=Sum("total_working_days"),
                total_present=Sum("present_days"),
                total_late=Sum("late_days"),
                total_leave=Sum("leave_days"),
                total_half=Sum("half_days"),
            )

            total_days = yearly_stats["total_days"] or 0
            total_present = yearly_stats["total_present"] or 0
            total_late = yearly_stats["total_late"] or 0
            total_half = yearly_stats["total_half"] or 0

            effective_present = total_present + total_late + (total_half * 0.5)
            yearly_percentage = (
                round((effective_present / total_days * 100), 2)
                if total_days > 0
                else 0
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "year": year,
                        "yearly_summary": {
                            "total_working_days": total_days,
                            "total_present": total_present,
                            "total_absent": (
                                total_days
                                - total_present
                                - total_late
                                - total_leave
                                - total_half
                            ),
                            "total_late": total_late,
                            "total_leave": total_leave,
                            "total_half_days": total_half,
                            "attendance_percentage": yearly_percentage,
                        },
                        "monthly_summaries": monthly_data,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentMonthlyAttendanceView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_month_name(self, month):
        months = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
        ]
        return months[month - 1] if 1 <= month <= 12 else str(month)

    def _calculate_monthly_summaries(self, enrollment, year):
        """Calculate monthly summaries from attendance records"""
        for month in range(1, 13):
            # Get attendance records for this month
            records = StudentAttendance.objects.filter(
                enrollment=enrollment,
                session__date__month=month,
                session__date__year=year,
                is_active=True,
            )

            if records.exists():
                summary, created = AttendanceSummary.objects.get_or_create(
                    enrollment=enrollment,
                    student=enrollment.student,
                    academic_class=enrollment.academic_class,
                    month=month,
                    year=year,
                )
                summary.compute()  # This will recalculate all stats


# =====================================================
# PARENT ATTENDANCE CALENDAR
# =====================================================


class ParentAttendanceCalendarView(APIView):
    """
    GET /parent/attendance/calendar/

    Get attendance data formatted for calendar view.
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional) — validates enrollment belongs to this year
        month: int (required) - 1-12
        year: int (optional, defaults to current year)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        month = request.query_params.get("month")
        year = request.query_params.get("year", timezone.now().year)

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if not month:
            return Response(
                {"success": False, "message": "month is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            month = int(month)
            year = int(year)

            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get attendance records for the month
            attendance_records = StudentAttendance.objects.filter(
                enrollment=enrollment,
                session__date__month=month,
                session__date__year=year,
                is_active=True,
            ).select_related("session")

            # Create map of date -> attendance status
            attendance_map = {}
            for record in attendance_records:
                attendance_map[record.session.date] = {
                    "status": record.status,
                    "remarks": record.remarks,
                    "late_minutes": record.late_minutes,
                }

            # Get all days in month
            num_days = monthrange(year, month)[1]
            calendar_data = []

            for day in range(1, num_days + 1):
                current_date = date(year, month, day)
                weekday = current_date.weekday()  # 0=Monday, 6=Sunday
                weekday_name = cal.day_name[weekday]

                attendance = attendance_map.get(current_date)

                # Determine if it's a weekend (Saturday=5, Sunday=6)
                is_weekend = weekday >= 5

                calendar_data.append(
                    {
                        "date": current_date,
                        "day": day,
                        "weekday": weekday,
                        "weekday_name": weekday_name,
                        "is_weekend": is_weekend,
                        "attendance": (
                            {
                                "status": attendance["status"] if attendance else None,
                                "status_display": (
                                    self._get_status_display(attendance["status"])
                                    if attendance
                                    else None
                                ),
                                "remarks": (
                                    attendance["remarks"] if attendance else None
                                ),
                                "late_minutes": (
                                    attendance["late_minutes"] if attendance else None
                                ),
                            }
                            if attendance
                            else None
                        ),
                    }
                )

            # Calculate monthly statistics
            total_days = len(calendar_data)
            present_days = len(
                [
                    d
                    for d in calendar_data
                    if d["attendance"] and d["attendance"]["status"] == "PRESENT"
                ]
            )
            absent_days = len(
                [
                    d
                    for d in calendar_data
                    if d["attendance"] and d["attendance"]["status"] == "ABSENT"
                ]
            )
            late_days = len(
                [
                    d
                    for d in calendar_data
                    if d["attendance"] and d["attendance"]["status"] == "LATE"
                ]
            )
            leave_days = len(
                [
                    d
                    for d in calendar_data
                    if d["attendance"] and d["attendance"]["status"] == "LEAVE"
                ]
            )

            effective_present = present_days + late_days
            attendance_percentage = (
                round((effective_present / total_days * 100), 2)
                if total_days > 0
                else 0
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "month": month,
                        "year": year,
                        "month_name": self._get_month_name(month),
                        "statistics": {
                            "total_days": total_days,
                            "present": present_days,
                            "absent": absent_days,
                            "late": late_days,
                            "leave": leave_days,
                            "attendance_percentage": attendance_percentage,
                        },
                        "calendar": calendar_data,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentAttendanceCalendarView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_month_name(self, month):
        months = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
        ]
        return months[month - 1] if 1 <= month <= 12 else str(month)

    def _get_status_display(self, status):
        status_map = {
            "PRESENT": "Present",
            "ABSENT": "Absent",
            "LATE": "Late",
            "LEAVE": "On Leave",
            "HALF_DAY": "Half Day",
        }
        return status_map.get(status, status)


# =====================================================
# PARENT ATTENDANCE STATISTICS (with filters)
# =====================================================


class ParentAttendanceStatisticsView(APIView):
    """
    GET /parent/attendance/statistics/

    Get attendance statistics with various filters (holidays, late, etc.)
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional) — validates enrollment belongs to this year
        period: string (optional) - week, month, term, year (default: month)
        status_filter: string (optional) - present, absent, late, leave, all
        month: int (optional)
        year: int (optional)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        period = request.query_params.get("period", "month")
        status_filter = request.query_params.get("status_filter", "all")
        month = request.query_params.get("month")
        year = request.query_params.get("year")

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Determine date range based on period
            today = timezone.now().date()
            if period == "week":
                start_date = today - timedelta(days=today.weekday())
                end_date = start_date + timedelta(days=6)
            elif period == "month":
                if month and year:
                    start_date = date(int(year), int(month), 1)
                    end_date = date(
                        int(year), int(month), monthrange(int(year), int(month))[1]
                    )
                else:
                    start_date = date(today.year, today.month, 1)
                    end_date = date(
                        today.year, today.month, monthrange(today.year, today.month)[1]
                    )
            elif period == "term":
                # Get current term from academic year
                academic_year = enrollment.academic_class.academic_year
                current_term = academic_year.terms.filter(
                    is_active=True, start_date__lte=today, end_date__gte=today
                ).first()
                if current_term:
                    start_date = current_term.start_date
                    end_date = current_term.end_date
                else:
                    start_date = date(today.year, 1, 1)
                    end_date = date(today.year, 12, 31)
            else:  # year
                if year:
                    start_date = date(int(year), 1, 1)
                    end_date = date(int(year), 12, 31)
                else:
                    start_date = date(today.year, 1, 1)
                    end_date = date(today.year, 12, 31)

            # Get attendance records in date range
            attendance_qs = StudentAttendance.objects.filter(
                enrollment=enrollment,
                session__date__gte=start_date,
                session__date__lte=end_date,
                is_active=True,
            )

            # Apply status filter
            if status_filter != "all":
                attendance_qs = attendance_qs.filter(status=status_filter.upper())

            # Calculate statistics by month for trend analysis
            monthly_stats = (
                attendance_qs.annotate(month=TruncMonth("session__date"))
                .values("month")
                .annotate(
                    present=Count("id", filter=Q(status="PRESENT")),
                    absent=Count("id", filter=Q(status="ABSENT")),
                    late=Count("id", filter=Q(status="LATE")),
                    leave=Count("id", filter=Q(status="LEAVE")),
                    total=Count("id"),
                )
                .order_by("month")
            )

            # Calculate percentages
            trend_data = []
            for stat in monthly_stats:
                total = stat["total"]
                if total > 0:
                    trend_data.append(
                        {
                            "month": stat["month"].strftime("%B %Y"),
                            "present_percentage": round(
                                (stat["present"] / total * 100), 2
                            ),
                            "absent_percentage": round(
                                (stat["absent"] / total * 100), 2
                            ),
                            "late_percentage": round((stat["late"] / total * 100), 2),
                            "leave_percentage": round((stat["leave"] / total * 100), 2),
                        }
                    )

            # Overall statistics for the period
            total_records = attendance_qs.count()
            present_count = attendance_qs.filter(status="PRESENT").count()
            absent_count = attendance_qs.filter(status="ABSENT").count()
            late_count = attendance_qs.filter(status="LATE").count()
            leave_count = attendance_qs.filter(status="LEAVE").count()

            present_percentage = (
                round((present_count / total_records * 100), 2)
                if total_records > 0
                else 0
            )
            absent_percentage = (
                round((absent_count / total_records * 100), 2)
                if total_records > 0
                else 0
            )
            late_percentage = (
                round((late_count / total_records * 100), 2) if total_records > 0 else 0
            )
            leave_percentage = (
                round((leave_count / total_records * 100), 2)
                if total_records > 0
                else 0
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "period_info": {
                            "period": period,
                            "start_date": start_date,
                            "end_date": end_date,
                            "filter_applied": status_filter,
                        },
                        "statistics": {
                            "total_days": total_records,
                            "present": present_count,
                            "absent": absent_count,
                            "late": late_count,
                            "leave": leave_count,
                            "present_percentage": present_percentage,
                            "absent_percentage": absent_percentage,
                            "late_percentage": late_percentage,
                            "leave_percentage": leave_percentage,
                        },
                        "trend_data": trend_data,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentAttendanceStatisticsView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# PARENT LEAVE HISTORY
# =====================================================
class ParentLeaveHistoryView(APIView):
    """
    GET /parent/attendance/leaves/

    Get leave history for a student.
    Query Parameters:
        enrollment_id: int (required)
        academic_year_id: int (optional) — validates enrollment belongs to this year
        status: string (optional) - PENDING, APPROVED, REJECTED
        from_date: date (optional)
        to_date: date (optional)
        page: int (default 1)
        page_size: int (default 20)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        status_filter = request.query_params.get("status")
        from_date = request.query_params.get("from_date")
        to_date = request.query_params.get("to_date")
        page = int(request.query_params.get("page", 1))
        page_size = int(request.query_params.get("page_size", 20))

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Build queryset
            leaves_qs = (
                AttendanceLeave.objects.filter(
                    enrollment=enrollment, student=enrollment.student, is_active=True
                )
                .select_related("approved_by")
                .order_by("-from_date")
            )

            # Apply filters
            if status_filter:
                leaves_qs = leaves_qs.filter(status=status_filter.upper())
            if from_date:
                leaves_qs = leaves_qs.filter(from_date__gte=from_date)
            if to_date:
                leaves_qs = leaves_qs.filter(to_date__lte=to_date)

            # Calculate total days by iterating (since PostgreSQL doesn't support direct date difference sum)
            leaves_list = list(leaves_qs)
            total_days_sum = sum(
                (leave.to_date - leave.from_date).days + 1 for leave in leaves_list
            )

            # Calculate counts
            total_count = len(leaves_list)
            pending_count = sum(1 for leave in leaves_list if leave.status == "PENDING")
            approved_count = sum(
                1 for leave in leaves_list if leave.status == "APPROVED"
            )
            rejected_count = sum(
                1 for leave in leaves_list if leave.status == "REJECTED"
            )

            # Pagination
            start = (page - 1) * page_size
            end = start + page_size
            paginated_leaves = leaves_list[start:end]

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "summary": {
                            "total_leaves": total_count,
                            "pending": pending_count,
                            "approved": approved_count,
                            "rejected": rejected_count,
                            "total_days_taken": total_days_sum,
                        },
                        "leaves": ParentLeaveHistorySerializer(
                            paginated_leaves, many=True
                        ).data,
                        "pagination": {
                            "current_page": page,
                            "page_size": page_size,
                            "total_pages": (total_count + page_size - 1) // page_size,
                            "total_count": total_count,
                            "has_next": end < total_count,
                            "has_previous": page > 1,
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveHistoryView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# PARENT LEAVE DETAIL
# =====================================================


class ParentLeaveDetailView(APIView):
    """
    GET /parent/attendance/leaves/<leave_id>/

    Get detailed information for a specific leave request.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, leave_id):
        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)

            leave = AttendanceLeave.objects.select_related(
                "student", "enrollment", "enrollment__academic_class", "approved_by"
            ).get(id=leave_id, is_active=True)

            # Check if this leave belongs to a child of this parent
            student_parent = parent.student_parents.filter(
                student=leave.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this leave request",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            return Response(
                {"success": True, "data": ParentLeaveDetailSerializer(leave).data}
            )

        except AttendanceLeave.DoesNotExist:
            return Response(
                {"success": False, "message": "Leave request not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveDetailView: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ExplainLeaveReasonView(APIView):
    """
    POST /api/parent/attendance/leave-requests/explain-reason/

    Body:
    {
        "reason": "my son not feeling good fever"
    }
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            reason = request.data.get("reason", "").strip()

            # ✅ Validation
            if not reason:
                return Response(
                    {"success": False, "message": "Reason is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            if len(reason) > 500:
                return Response(
                    {"success": False, "message": "Reason too long"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # ✅ AI call (Gemini)
            explained_text = explain_reason(reason)

            return Response(
                {
                    "success": True,
                    "data": {
                        "original_reason": reason,
                        "explained_reason": explained_text,
                    },
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# PARENT LEAVE REQUEST MANAGEMENT
# =====================================================


class ParentLeaveRequestListView(APIView):
    """
    GET /parent/attendance/leave-requests/ - List leave requests with filters
    POST /parent/attendance/leave-requests/ - Create new leave request

    GET Query Parameters:
        enrollment_id: int (required)
        status: string (optional) - PENDING, APPROVED, REJECTED
        leave_type: string (optional) - MEDICAL, FAMILY, EMERGENCY, SPORTS, OTHER
        from_date: date (optional)
        to_date: date (optional)
        page: int (default 1)
        page_size: int (default 20)

    POST Body (multipart/form-data):
        enrollment_id: int (required)
        leave_type: string (required)
        from_date: date (required)
        to_date: date (required)
        reason: text (required)
        document: file (optional)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        enrollment_id = request.query_params.get("enrollment_id")
        academic_year_id = request.query_params.get("academic_year_id")
        status_filter = request.query_params.get("status")
        leave_type_filter = request.query_params.get("leave_type")
        from_date = request.query_params.get("from_date")
        to_date = request.query_params.get("to_date")
        page = int(request.query_params.get("page", 1))
        page_size = int(request.query_params.get("page_size", 20))

        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_filters = {"id": enrollment_id, "is_active": True}
            if academic_year_id:
                enrollment_filters["academic_class__academic_year_id"] = academic_year_id
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(**enrollment_filters)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Build queryset
            leaves_qs = (
                AttendanceLeave.objects.filter(
                    enrollment=enrollment, student=enrollment.student, is_active=True
                )
                .select_related("approved_by")
                .order_by("-created_at")
            )

            # Apply filters
            if status_filter:
                leaves_qs = leaves_qs.filter(status=status_filter.upper())
            if leave_type_filter:
                leaves_qs = leaves_qs.filter(leave_type=leave_type_filter.upper())
            if from_date:
                leaves_qs = leaves_qs.filter(from_date__gte=from_date)
            if to_date:
                leaves_qs = leaves_qs.filter(to_date__lte=to_date)

            # Pagination
            total_count = leaves_qs.count()
            start = (page - 1) * page_size
            end = start + page_size
            paginated_leaves = leaves_qs[start:end]

            # Calculate summary statistics
            summary = leaves_qs.aggregate(
                total=Count("id"),
                pending=Count("id", filter=Q(status="PENDING")),
                approved=Count("id", filter=Q(status="APPROVED")),
                rejected=Count("id", filter=Q(status="REJECTED")),
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": enrollment.student.id,
                            "student_name": enrollment.student.full_name
                            or f"{enrollment.student.first_name} {enrollment.student.last_name}".strip(),
                            "class_name": str(enrollment.academic_class),
                            "roll_number": enrollment.roll_number,
                        },
                        "summary": {
                            "total_requests": summary["total"] or 0,
                            "pending": summary["pending"] or 0,
                            "approved": summary["approved"] or 0,
                            "rejected": summary["rejected"] or 0,
                        },
                        "filters_applied": {
                            "status": status_filter,
                            "leave_type": leave_type_filter,
                            "from_date": from_date,
                            "to_date": to_date,
                        },
                        "leave_requests": ParentLeaveRequestListSerializer(
                            paginated_leaves, many=True
                        ).data,
                        "pagination": {
                            "current_page": page,
                            "page_size": page_size,
                            "total_pages": (total_count + page_size - 1) // page_size,
                            "total_count": total_count,
                            "has_next": end < total_count,
                            "has_previous": page > 1,
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveRequestListView GET: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def post(self, request):
        try:
            # Handle multipart/form-data
            if request.content_type and "multipart/form-data" in request.content_type:
                data = request.data.copy()
            else:
                data = request.data

            # Validate required fields
            enrollment_id = data.get("enrollment_id")
            if not enrollment_id:
                return Response(
                    {"success": False, "message": "enrollment_id is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment = StudentEnrollment.objects.select_related(
                "student", "academic_class"
            ).get(id=enrollment_id, is_active=True)

            student_parent = parent.student_parents.filter(
                student=enrollment.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this student",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Check for overlapping pending leave requests
            from_date = data.get("from_date")
            to_date = data.get("to_date")

            if from_date and to_date:
                overlapping = AttendanceLeave.objects.filter(
                    enrollment=enrollment,
                    status="PENDING",
                    is_active=True,
                    from_date__lte=to_date,
                    to_date__gte=from_date,
                ).exists()

                if overlapping:
                    return Response(
                        {
                            "success": False,
                            "message": "You already have a pending leave request for these dates",
                        },
                        status=status.HTTP_400_BAD_REQUEST,
                    )

            # Create serializer with data
            serializer = ParentLeaveRequestCreateSerializer(data=data)

            if serializer.is_valid():
                # Add student and enrollment to the data
                leave = AttendanceLeave(
                    enrollment=enrollment,
                    student=enrollment.student,
                    leave_type=serializer.validated_data["leave_type"],
                    from_date=serializer.validated_data["from_date"],
                    to_date=serializer.validated_data["to_date"],
                    reason=serializer.validated_data.get("reason", ""),
                    status="PENDING",
                    is_active=True,
                )

                # Handle document upload
                if "document" in request.FILES:
                    leave.document = request.FILES["document"]
                elif "document" in data and data["document"]:
                    leave.document = data["document"]

                leave.save()

                logger.info(
                    f"Leave request created: {leave.id} for student {enrollment.student.id}"
                )

                return Response(
                    {
                        "success": True,
                        "message": "Leave request submitted successfully",
                        "data": ParentLeaveRequestDetailSerializer(leave).data,
                    },
                    status=status.HTTP_201_CREATED,
                )

            return Response(
                {"success": False, "errors": serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveRequestListView POST: {str(e)}")
            import traceback

            logger.error(traceback.format_exc())
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ParentLeaveRequestDetailView(APIView):
    """
    GET /parent/attendance/leave-requests/<leave_id>/ - Get leave request details
    PUT/PATCH /parent/attendance/leave-requests/<leave_id>/ - Update leave request (only if pending)
    DELETE /parent/attendance/leave-requests/<leave_id>/ - Delete leave request (only if pending)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, leave_id):
        try:
            # Verify parent access
            parent = Parent.objects.get(external_user_id=request.user.id)

            leave = AttendanceLeave.objects.select_related(
                "student", "enrollment", "enrollment__academic_class", "approved_by"
            ).get(id=leave_id, is_active=True)

            # Check if this leave belongs to a child of this parent
            student_parent = parent.student_parents.filter(
                student=leave.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this leave request",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            return Response(
                {
                    "success": True,
                    "data": ParentLeaveRequestDetailSerializer(leave).data,
                }
            )

        except AttendanceLeave.DoesNotExist:
            return Response(
                {"success": False, "message": "Leave request not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveRequestDetailView GET: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def patch(self, request, leave_id):
        """Update leave request (only if pending)"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            leave = AttendanceLeave.objects.get(id=leave_id, is_active=True)

            # Check access
            student_parent = parent.student_parents.filter(
                student=leave.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this leave request",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Check if can update (only pending requests)
            if leave.status != "PENDING":
                return Response(
                    {
                        "success": False,
                        "message": f"Cannot update leave request that is {leave.status}",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Handle multipart/form-data
            if request.content_type and "multipart/form-data" in request.content_type:
                data = request.data.copy()
            else:
                data = request.data

            serializer = ParentLeaveRequestUpdateSerializer(
                leave, data=data, partial=True
            )

            if serializer.is_valid():
                # Update fields
                for attr, value in serializer.validated_data.items():
                    setattr(leave, attr, value)

                # Handle document update
                if "document" in request.FILES:
                    # Delete old document if exists
                    if leave.document:
                        leave.document.delete(save=False)
                    leave.document = request.FILES["document"]

                leave.save()

                logger.info(f"Leave request updated: {leave.id}")

                return Response(
                    {
                        "success": True,
                        "message": "Leave request updated successfully",
                        "data": ParentLeaveRequestDetailSerializer(leave).data,
                    }
                )

            return Response(
                {"success": False, "errors": serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

        except AttendanceLeave.DoesNotExist:
            return Response(
                {"success": False, "message": "Leave request not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveRequestDetailView PATCH: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def delete(self, request, leave_id):
        """Delete/cancel leave request (soft delete)"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            leave = AttendanceLeave.objects.get(id=leave_id, is_active=True)

            # Check access
            student_parent = parent.student_parents.filter(
                student=leave.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this leave request",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Check if can delete (only pending requests)
            if leave.status != "PENDING":
                return Response(
                    {
                        "success": False,
                        "message": f"Cannot cancel leave request that is {leave.status}",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Soft delete
            leave.is_active = False
            leave.save()

            logger.info(f"Leave request cancelled: {leave.id}")

            return Response(
                {"success": True, "message": "Leave request cancelled successfully"}
            )

        except AttendanceLeave.DoesNotExist:
            return Response(
                {"success": False, "message": "Leave request not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentLeaveRequestDetailView DELETE: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ParentLeaveTypesView(APIView):
    """
    GET /parent/attendance/leave-types/

    Get list of available leave types with descriptions
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        leave_types = [
            {
                "code": "MEDICAL",
                "name": "Medical Leave",
                "description": "For illness or medical appointments",
            },
            {
                "code": "FAMILY",
                "name": "Family Emergency",
                "description": "For family events or emergencies",
            },
            {
                "code": "EMERGENCY",
                "name": "Emergency",
                "description": "For urgent unforeseen situations",
            },
            {
                "code": "SPORTS",
                "name": "Sports/Event",
                "description": "For sports tournaments or school events",
            },
            {"code": "OTHER", "name": "Other", "description": "Any other valid reason"},
        ]

        return Response({"success": True, "data": leave_types})


class ParentCancelLeaveRequestView(APIView):
    """
    POST /parent/attendance/leave-requests/<leave_id>/cancel/

    Alternative endpoint to cancel a leave request
    """

    permission_classes = [IsAuthenticated]

    def post(self, request, leave_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            leave = AttendanceLeave.objects.get(id=leave_id, is_active=True)

            # Check access
            student_parent = parent.student_parents.filter(
                student=leave.student, is_active=True
            ).first()

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "You don't have access to this leave request",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Check if can cancel
            if leave.status != "PENDING":
                return Response(
                    {
                        "success": False,
                        "message": f"Cannot cancel leave request that is {leave.status}",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Get reason from request body
            cancel_reason = request.data.get("cancel_reason", "Cancelled by parent")

            # Soft delete
            leave.is_active = False
            leave.rejection_reason = cancel_reason
            leave.save()

            logger.info(f"Leave request cancelled: {leave.id} by parent {parent.id}")

            return Response(
                {
                    "success": True,
                    "message": "Leave request cancelled successfully",
                    "data": {
                        "leave_id": leave.id,
                        "cancelled_at": timezone.now(),
                        "reason": cancel_reason,
                    },
                }
            )

        except AttendanceLeave.DoesNotExist:
            return Response(
                {"success": False, "message": "Leave request not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentCancelLeaveRequestView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ParentChatRoomListView(APIView):
    """
    GET /parent/chat/rooms/?enrollment_id=<enrollment_id>

    Get all individual chat rooms for the parent with teachers
    Parent should only see their private conversations, not class/subject groups

    Includes student enrollment info (subject, class) and last message
    Shows ONLY the subjects that the teacher teaches for THAT SPECIFIC student

    Query Parameters:
    - enrollment_id: int (required)
    - page: int (default: 1)
    - page_size: int (default: 20)
    - search: string (optional) - search by teacher name or room name
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get parent from SCHOOL DB
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get query parameters
            enrollment_id = request.query_params.get("enrollment_id")
            page = int(request.query_params.get("page", 1))
            page_size = int(request.query_params.get("page_size", 20))
            search_query = request.query_params.get("search", "")

            if not enrollment_id:
                return Response(
                    {
                        "success": False,
                        "message": "enrollment_id parameter is required",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Calculate offset
            offset = (page - 1) * page_size

            # Get enrollment and verify parent has access
            try:
                enrollment = StudentEnrollment.objects.select_related(
                    "student",
                    "academic_class",
                    "academic_class__standard",
                    "academic_class__section",
                ).get(id=enrollment_id, is_active=True)

                # Verify parent has access to this student
                student_parent = parent.student_parents.filter(
                    student=enrollment.student, is_active=True
                ).first()

                if not student_parent:
                    return Response(
                        {
                            "success": False,
                            "message": "You don't have access to this student",
                        },
                        status=status.HTTP_403_FORBIDDEN,
                    )

            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get all individual chat rooms where parent is a participant
            rooms_qs = ChatRoom.objects.filter(
                room_type="INDIVIDUAL", parents=parent, is_active=True
            ).prefetch_related("teachers", "messages")

            # Apply search filter (search by teacher name or room name)
            if search_query:
                rooms_qs = rooms_qs.filter(
                    Q(name__icontains=search_query)
                    | Q(teachers__first_name__icontains=search_query)
                    | Q(teachers__last_name__icontains=search_query)
                ).distinct()

            # Order by last_message_at (most recent first)
            rooms_qs = rooms_qs.order_by("-last_message_at")

            # Get total count for pagination
            total_count = rooms_qs.count()

            # Apply pagination
            paginated_rooms = rooms_qs[offset : offset + page_size]

            # Get all teacher IDs from paginated rooms
            teacher_ids = []
            for room in paginated_rooms:
                teacher = room.teachers.first()
                if teacher:
                    teacher_ids.append(teacher.id)

            # Get subject teachers for this class (what subjects each teacher teaches)
            subject_teachers = SubjectTeacher.objects.filter(
                academic_class=enrollment.academic_class,
                teacher_id__in=teacher_ids,
                is_active=True,
            ).select_related("subject")

            # Build a map: teacher_id -> list of subjects they teach for THIS class
            teacher_subjects_map = {}
            for st in subject_teachers:
                if st.teacher_id not in teacher_subjects_map:
                    teacher_subjects_map[st.teacher_id] = []
                teacher_subjects_map[st.teacher_id].append(
                    {
                        "subject_id": st.subject.id,
                        "subject_name": st.subject.name,
                        "subject_code": st.subject.code,
                    }
                )

            # For higher secondary, filter subjects that the student has actually selected
            standard_type = enrollment.academic_class.standard.standard_type
            if standard_type == "higher_secondary":
                # Get subjects selected by this student
                selected_subject_ids = set(
                    StudentSubject.objects.filter(
                        enrollment=enrollment, is_active=True
                    ).values_list("subject_id", flat=True)
                )

                # Also get subjects from subject groups
                group_subject_ids = set(
                    StudentSubjectGroup.objects.filter(
                        enrollment=enrollment, is_active=True
                    ).values_list("subject_group__subjects__id", flat=True)
                )

                all_selected_subject_ids = selected_subject_ids | group_subject_ids

                # Filter teacher subjects to only those selected by student
                for teacher_id in teacher_subjects_map:
                    teacher_subjects_map[teacher_id] = [
                        sub
                        for sub in teacher_subjects_map[teacher_id]
                        if sub["subject_id"] in all_selected_subject_ids
                    ]

            # Build response
            rooms_data = []
            for room in paginated_rooms:
                teacher = room.teachers.first()

                if not teacher:
                    continue

                unread_count = self._get_unread_count_for_parent(room, parent)

                # Get subjects for this teacher (already filtered for this student)
                subjects = teacher_subjects_map.get(teacher.id, [])

                # Get last message
                last_message = (
                    room.messages.filter(is_deleted=False)
                    .order_by("-created_at")
                    .first()
                )

                last_message_data = None
                if last_message:
                    last_message_data = {
                        "id": str(last_message.id),
                        "content": (
                            last_message.content[:200] if last_message.content else None
                        ),
                        "message_type": last_message.message_type,
                        "sender_type": last_message.sender_type,
                        "sender_name": last_message.get_sender_name(),
                        "created_at": last_message.created_at,
                        "file_url": (
                            last_message.file.url if last_message.file else None
                        ),
                        "file_name": last_message.file_name,
                        "file_size": last_message.file_size,
                        "file_type": last_message.file_type,
                    }

                rooms_data.append(
                    {
                        "id": str(room.id),
                        "room_type": room.room_type,
                        "name": room.name,
                        "created_at": room.created_at,
                        "last_message_at": room.last_message_at,
                        "unread_count": unread_count,
                        "last_message": last_message_data,
                        "teacher": {
                            "id": teacher.id,
                            "name": teacher.full_name,
                            "employee_id": teacher.employee_id,
                            "profile_image": (
                                teacher.profile_image.url
                                if teacher.profile_image
                                else None
                            ),
                        },
                        "subjects": subjects,
                    }
                )

            # Build enrollment info for response
            enrollment_info = {
                "enrollment_id": enrollment.id,
                "student_id": enrollment.student.id,
                "student_name": enrollment.student.full_name,
                "roll_number": enrollment.roll_number,
                "class_id": enrollment.academic_class.id,
                "class_name": str(enrollment.academic_class),
                "standard": enrollment.academic_class.standard.name,
                "standard_type": standard_type,
                "section": enrollment.academic_class.section.code,
            }

            # Calculate pagination metadata
            total_pages = (
                (total_count + page_size - 1) // page_size if page_size > 0 else 0
            )
            has_next = offset + page_size < total_count
            has_previous = page > 1

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollment_info": enrollment_info,
                        "rooms": rooms_data,
                        "total_rooms": total_count,
                    },
                    "pagination": {
                        "current_page": page,
                        "page_size": page_size,
                        "total_pages": total_pages,
                        "total_items": total_count,
                        "has_next": has_next,
                        "has_previous": has_previous,
                        "next_page": page + 1 if has_next else None,
                        "previous_page": page - 1 if has_previous else None,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatRoomListView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_unread_count_for_parent(self, room, parent):
        """Get unread messages count for parent in this room"""
        try:
            messages = Message.objects.filter(room=room, is_deleted=False)
            read_messages = MessageReadReceipt.objects.filter(
                user_type="parent", user_id=str(parent.id), message__in=messages
            ).values_list("message_id", flat=True)

            unread = (
                messages.exclude(id__in=read_messages)
                .exclude(sender_type="parent", sender_id=str(parent.id))
                .count()
            )
            return unread
        except:
            return 0


class ParentChatRoomDetailView(APIView):
    """
    GET /parent/chat/rooms/<room_id>/

    Get details of a specific chat room
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, room_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get room and verify parent has access
            room = (
                ChatRoom.objects.filter(id=room_id, is_active=True)
                .filter(Q(parents=parent) | Q(students__student_parents__parent=parent))
                .distinct()
                .first()
            )

            if not room:
                return Response(
                    {
                        "success": False,
                        "message": "Chat room not found or access denied",
                    },
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Update parent's last active status
            self._update_parent_active_status(room, parent)

            serializer = ChatRoomSerializer(
                room,
                context={
                    "request": request,
                    "user_type": "parent",
                    "user_id": str(parent.id),
                },
            )

            data = serializer.data
            data["unread_count"] = self._get_unread_count(parent, room)
            # So the client can tell its own typing/message events apart
            # from the other participant's when both arrive over the socket.
            data["current_parent_id"] = str(parent.id)

            return Response({"success": True, "data": data})

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatRoomDetailView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_unread_count(self, parent, room):
        """Get unread messages count for parent"""
        messages = Message.objects.filter(room=room, is_deleted=False)
        read_messages = MessageReadReceipt.objects.filter(
            user_type="parent", user_id=str(parent.id), message__in=messages
        ).values_list("message_id", flat=True)

        return (
            messages.exclude(id__in=read_messages)
            .exclude(sender_type="parent", sender_id=str(parent.id))
            .count()
        )

    def _update_parent_active_status(self, room, parent):
        """Update parent's last active timestamp in room"""
        try:
            participant = room.chatparticipantparent_set.filter(
                parent=parent, is_active=True
            ).first()
            if participant:
                participant.last_active_at = timezone.now()
                participant.save(update_fields=["last_active_at"])
        except:
            pass


class ParentChatMessageListView(APIView):
    """
    GET /parent/chat/rooms/<room_id>/messages/
    POST /parent/chat/rooms/<room_id>/messages/

    Get messages for a chat room or send a new message
    When getting messages, automatically marks them as read
    Supports pagination with cursor-based loading (lazy loading)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, room_id):
        """Get messages for a chat room with lazy loading pagination"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Verify access
            if not self._has_access(room_id, parent):
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get pagination parameters
            page = int(request.query_params.get("page", 1))
            limit = int(request.query_params.get("limit", 20))
            before_id = request.query_params.get(
                "before_id"
            )  # For cursor-based pagination
            after_id = request.query_params.get(
                "after_id"
            )  # For loading newer messages

            # Base queryset
            messages_qs = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).select_related("room", "reply_to")

            # Cursor-based pagination (better for lazy loading)
            if before_id:
                # Load older messages (before this message ID)
                try:
                    before_message = Message.objects.get(id=before_id, room_id=room_id)
                    messages_qs = messages_qs.filter(
                        created_at__lt=before_message.created_at
                    )
                except Message.DoesNotExist:
                    pass

            elif after_id:
                # Load newer messages (after this message ID)
                try:
                    after_message = Message.objects.get(id=after_id, room_id=room_id)
                    messages_qs = messages_qs.filter(
                        created_at__gt=after_message.created_at
                    )
                except Message.DoesNotExist:
                    pass

            # Get messages ordered by created_at (oldest to newest for display)
            messages = messages_qs.order_by("-created_at")[:limit]

            # Reverse to get chronological order (oldest first for display)
            messages = list(reversed(messages))

            # Get total count for pagination info
            total_messages = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).count()

            # Get cursor info for next/previous pages
            first_message_id = messages[0].id if messages else None
            last_message_id = messages[-1].id if messages else None

            # Check if more messages exist
            has_older = False
            if messages:
                has_older = Message.objects.filter(
                    room_id=room_id,
                    is_deleted=False,
                    created_at__lt=messages[0].created_at,
                ).exists()

            has_newer = False
            if messages:
                has_newer = Message.objects.filter(
                    room_id=room_id,
                    is_deleted=False,
                    created_at__gt=messages[-1].created_at,
                ).exists()

            # Mark messages as delivered AND read for parent
            self._mark_as_delivered_and_read(room_id, parent, messages)

            # Mark all messages in room as read (auto-read functionality)
            self._mark_all_as_read(room_id, parent)

            serializer = MessageSerializer(
                messages,
                many=True,
                context={
                    "request": request,
                    "user_type": "parent",
                    "user_id": str(parent.id),
                },
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "messages": serializer.data,
                        "pagination": {
                            "total": total_messages,
                            "limit": limit,
                            "page": page,
                            "has_older": has_older,
                            "has_newer": has_newer,
                            "first_message_id": (
                                str(first_message_id) if first_message_id else None
                            ),
                            "last_message_id": (
                                str(last_message_id) if last_message_id else None
                            ),
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(
                f"Error in ParentChatMessageListView GET: {str(e)}", exc_info=True
            )
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def post(self, request, room_id):
        """Send a new message"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Verify access
            if not self._has_access(room_id, parent):
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            content = request.data.get("content")
            message_type = request.data.get("message_type", "TEXT")
            reply_to_id = request.data.get("reply_to_id")

            if not content:
                return Response(
                    {"success": False, "message": "Content is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Create message
            message = Message.objects.create(
                room_id=room_id,
                sender_type="parent",
                sender_id=str(parent.id),
                content=content,
                message_type=message_type,
                reply_to_id=reply_to_id,
            )
            broadcast_new_message(message)

            # Update room's last message time
            ChatRoom.objects.filter(id=room_id).update(last_message_at=timezone.now())

            # Process mentions
            self._process_mentions(message, content)

            # Create delivery records for all participants
            self._create_delivery_records(message)

            # Mark this message as read for sender
            MessageReadReceipt.objects.get_or_create(
                message=message, user_type="parent", user_id=str(parent.id)
            )

            serializer = MessageSerializer(
                message,
                context={
                    "request": request,
                    "user_type": "parent",
                    "user_id": str(parent.id),
                },
            )

            return Response(
                {
                    "success": True,
                    "data": serializer.data,
                    "message_id": str(message.id),
                },
                status=status.HTTP_201_CREATED,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(
                f"Error in ParentChatMessageListView POST: {str(e)}", exc_info=True
            )
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _has_access(self, room_id, parent):
        """Check if parent has access to this room"""
        try:
            room = ChatRoom.objects.get(id=room_id)
            if room.parents.filter(id=parent.id).exists():
                return True
            child_ids = parent.student_parents.filter(is_active=True).values_list(
                "student_id", flat=True
            )
            if room.students.filter(id__in=child_ids).exists():
                return True
            return False
        except:
            return False

    def _mark_as_delivered_and_read(self, room_id, parent, messages):
        """Mark messages as delivered and read"""
        for message in messages:
            if message.sender_type != "parent" or message.sender_id != str(parent.id):
                MessageDelivery.objects.update_or_create(
                    message=message,
                    user_type="parent",
                    user_id=str(parent.id),
                    defaults={"status": "DELIVERED", "delivered_at": timezone.now()},
                )
                MessageReadReceipt.objects.get_or_create(
                    message=message, user_type="parent", user_id=str(parent.id)
                )

    def _mark_all_as_read(self, room_id, parent):
        """Mark all messages in room as read for parent"""
        try:
            messages = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).exclude(sender_type="parent", sender_id=str(parent.id))

            for message in messages:
                MessageReadReceipt.objects.get_or_create(
                    message=message, user_type="parent", user_id=str(parent.id)
                )

            room = ChatRoom.objects.get(id=room_id)
            participant = room.chatparticipantparent_set.filter(
                parent=parent, is_active=True
            ).first()
            if participant:
                participant.last_read_at = timezone.now()
                participant.save(update_fields=["last_read_at"])

        except Exception as e:
            logger.error(f"Error in _mark_all_as_read: {str(e)}")

    def _create_delivery_records(self, message):
        """Create delivery records for all participants"""
        room = message.room

        parent_participants = room.parents.all().exclude(id=message.sender_id)
        for participant in parent_participants:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="parent",
                user_id=str(participant.id),
                defaults={"status": "SENT"},
            )

        teacher_participants = room.teachers.all()
        for participant in teacher_participants:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="teacher",
                user_id=str(participant.id),
                defaults={"status": "SENT"},
            )

        student_participants = room.students.all()
        for participant in student_participants:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="student",
                user_id=str(participant.id),
                defaults={"status": "SENT"},
            )

    def _process_mentions(self, message, content):
        """Process @mentions in message"""
        mention_pattern = r"@(teacher|parent|student)_(\d+)"
        mentions = re.findall(mention_pattern, content)
        for user_type, user_id in mentions:
            ChatMention.objects.create(
                message=message, user_type=user_type, user_id=user_id, is_notified=False
            )


class ParentChatMarkAsReadView(APIView):
    """
    POST /parent/chat/rooms/<room_id>/mark-read/

    Mark all messages in a room as read for parent
    """

    permission_classes = [IsAuthenticated]

    def post(self, request, room_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            if not self._has_access(room_id, parent):
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            messages = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).exclude(sender_type="parent", sender_id=str(parent.id))

            read_count = 0
            for message in messages:
                receipt, created = MessageReadReceipt.objects.get_or_create(
                    message=message, user_type="parent", user_id=str(parent.id)
                )
                if created:
                    read_count += 1

            try:
                room = ChatRoom.objects.get(id=room_id)
                participant = room.chatparticipantparent_set.filter(
                    parent=parent, is_active=True
                ).first()
                if participant:
                    participant.last_read_at = timezone.now()
                    participant.save(update_fields=["last_read_at"])
            except:
                pass

            return Response(
                {
                    "success": True,
                    "data": {
                        "message": f"Marked {read_count} messages as read",
                        "read_count": read_count,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatMarkAsReadView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _has_access(self, room_id, parent):
        try:
            room = ChatRoom.objects.get(id=room_id)
            return room.parents.filter(id=parent.id).exists()
        except:
            return False


class ParentChatRoomCreateView(APIView):
    """
    POST /parent/chat/rooms/create/

    Create a new chat room (individual or group)
    Body: {"room_type": "INDIVIDUAL", "participants": [{"type": "teacher", "id": 123}]}
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            room_type = request.data.get("room_type", "INDIVIDUAL")
            name = request.data.get("name")
            participant_ids = request.data.get("participants", [])

            if room_type == "INDIVIDUAL" and len(participant_ids) != 1:
                return Response(
                    {
                        "success": False,
                        "message": "Individual chat requires exactly one other participant",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Check if individual chat already exists
            if room_type == "INDIVIDUAL":
                other_participant = participant_ids[0]
                existing_room = (
                    ChatRoom.objects.filter(room_type="INDIVIDUAL", is_active=True)
                    .filter(parents=parent)
                    .filter(
                        **{f'{other_participant["type"]}s__id': other_participant["id"]}
                    )
                    .first()
                )

                if existing_room:
                    serializer = ChatRoomSerializer(
                        existing_room,
                        context={
                            "request": request,
                            "user_type": "parent",
                            "user_id": str(parent.id),
                        },
                    )
                    return Response(
                        {
                            "success": True,
                            "message": "Existing chat room found",
                            "data": serializer.data,
                        }
                    )

            # Create new room
            room = ChatRoom.objects.create(
                room_type=room_type,
                name=name,
                created_by_type="parent",
                created_by_id=str(parent.id),
                is_active=True,
            )

            # Add parent as participant
            room.parents.add(parent)
            ChatParticipantParent.objects.update_or_create(
                room=room,
                parent=parent,
                defaults={
                    "role": "ADMIN" if room_type == "GROUP" else "MEMBER",
                    "is_active": True,
                },
            )

            # Add other participants
            for participant in participant_ids:
                if participant["type"] == "teacher":
                    teacher = Teacher.objects.get(id=participant["id"], is_active=True)
                    room.teachers.add(teacher)
                    ChatParticipantTeacher.objects.update_or_create(
                        room=room,
                        teacher=teacher,
                        defaults={"role": "MEMBER", "is_active": True},
                    )

                elif participant["type"] == "parent":
                    other_parent = Parent.objects.get(
                        id=participant["id"], is_active=True
                    )
                    room.parents.add(other_parent)
                    ChatParticipantParent.objects.update_or_create(
                        room=room,
                        parent=other_parent,
                        defaults={"role": "MEMBER", "is_active": True},
                    )

                elif participant["type"] == "student":
                    student = Student.objects.get(id=participant["id"], is_active=True)
                    room.students.add(student)
                    ChatParticipantStudent.objects.update_or_create(
                        room=room,
                        student=student,
                        defaults={"role": "MEMBER", "is_active": True},
                    )

            serializer = ChatRoomSerializer(
                room,
                context={
                    "request": request,
                    "user_type": "parent",
                    "user_id": str(parent.id),
                },
            )

            return Response(
                {"success": True, "data": serializer.data},
                status=status.HTTP_201_CREATED,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatRoomCreateView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ParentChatRoomMuteView(APIView):
    """
    POST /parent/chat/rooms/<room_id>/mute/
    POST /parent/chat/rooms/<room_id>/unmute/
    """

    permission_classes = [IsAuthenticated]

    def post(self, request, room_id, action):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            room = ChatRoom.objects.get(id=room_id)
            participant = room.chatparticipantparent_set.filter(
                parent=parent, is_active=True
            ).first()

            if not participant:
                return Response(
                    {"success": False, "message": "Not a participant in this room"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            if action == "mute":
                duration = request.data.get("duration", "forever")
                participant.is_muted = True
                if duration and duration != "forever":
                    if duration.endswith("h"):
                        hours = int(duration[:-1])
                        participant.muted_until = timezone.now() + timedelta(
                            hours=hours
                        )
                    elif duration.endswith("d"):
                        days = int(duration[:-1])
                        participant.muted_until = timezone.now() + timedelta(days=days)
                participant.save()
                message = "Room muted successfully"

            elif action == "unmute":
                participant.is_muted = False
                participant.muted_until = None
                participant.save()
                message = "Room unmuted successfully"
            else:
                return Response(
                    {"success": False, "message": "Invalid action"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            return Response(
                {
                    "success": True,
                    "message": message,
                    "data": {
                        "is_muted": participant.is_muted,
                        "muted_until": participant.muted_until,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except ChatRoom.DoesNotExist:
            return Response(
                {"success": False, "message": "Room not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatRoomMuteView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class ParentChatUnreadCountView(APIView):
    """
    GET /parent/chat/unread-count/

    Get total unread messages count across all rooms
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            rooms = ChatRoom.objects.filter(parents=parent, is_active=True)
            total_unread = 0

            for room in rooms:
                messages = Message.objects.filter(room=room, is_deleted=False)
                read_messages = MessageReadReceipt.objects.filter(
                    user_type="parent", user_id=str(parent.id), message__in=messages
                ).values_list("message_id", flat=True)

                unread = (
                    messages.exclude(id__in=read_messages)
                    .exclude(sender_type="parent", sender_id=str(parent.id))
                    .count()
                )
                total_unread += unread

            return Response({"success": True, "data": {"total_unread": total_unread}})

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentChatUnreadCountView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# =====================================================
# NEW PARENT VIEWS FOR STUDENT-SPECIFIC CHATS
# =====================================================


class ParentAllEnrollmentsView(APIView):
    """
    GET /parent/chat/enrollments/

    Get all enrollments (students) for the parent
    Used to populate the student selector dropdown
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get all enrollments for parent's children
            enrollments = (
                StudentEnrollment.objects.filter(
                    student__student_parents__parent=parent,
                    student__student_parents__is_active=True,
                    is_active=True,
                )
                .select_related(
                    "student",
                    "academic_class",
                    "academic_class__standard",
                    "academic_class__section",
                    "academic_year",
                )
                .distinct()
            )

            enrollments_data = []
            for enrollment in enrollments:
                # Get unread count for this student across all rooms
                total_unread = self._get_total_unread_for_student(parent, enrollment)

                enrollments_data.append(
                    {
                        "enrollment_id": str(enrollment.id),
                        "student": {
                            "id": str(enrollment.student.id),
                            "name": enrollment.student.full_name,
                            "roll_number": enrollment.student.roll_number,
                            "student_id": enrollment.student.student_id,
                            "profile_image": (
                                enrollment.student.profile_image.url
                                if enrollment.student.profile_image
                                else None
                            ),
                        },
                        "class_info": {
                            "id": str(enrollment.academic_class.id),
                            "name": str(enrollment.academic_class),
                            "standard": (
                                enrollment.academic_class.standard.name
                                if enrollment.academic_class.standard
                                else None
                            ),
                            "section": (
                                enrollment.academic_class.section.name
                                if enrollment.academic_class.section
                                else None
                            ),
                        },
                        "academic_year": {
                            "id": str(enrollment.academic_year.id),
                            "name": enrollment.academic_year.name,
                        },
                        "total_unread": total_unread,
                    }
                )

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollments": enrollments_data,
                        "total_enrollments": len(enrollments_data),
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentAllEnrollmentsView: {str(e)}", exc_info=True)
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_total_unread_for_student(self, parent, enrollment):
        """Get total unread messages for a parent related to a specific student"""
        try:
            student = enrollment.student
            academic_class = enrollment.academic_class

            # Teachers relevant to this specific student, across all of their
            # enrollments (current + historical) — not just the current class.
            teacher_ids = _get_student_relevant_teacher_ids(student)

            # Get all relevant rooms
            class_rooms = ChatRoom.objects.filter(
                is_active=True, academic_class=academic_class
            ).filter(Q(parents=parent) | Q(students=student))

            individual_chats = ChatRoom.objects.filter(
                room_type="INDIVIDUAL",
                is_active=True,
                parents=parent,
                teachers__id__in=teacher_ids,
            )

            all_rooms = (class_rooms | individual_chats).distinct()

            total_unread = 0
            for room in all_rooms:
                messages = Message.objects.filter(room=room, is_deleted=False)
                read_messages = MessageReadReceipt.objects.filter(
                    user_type="parent", user_id=str(parent.id), message__in=messages
                ).values_list("message_id", flat=True)

                unread = (
                    messages.exclude(id__in=read_messages)
                    .exclude(sender_type="parent", sender_id=str(parent.id))
                    .count()
                )
                total_unread += unread

            return total_unread
        except:
            return 0


class ParentStudentTeachersListView(APIView):
    """
    GET /parent/chat/enrollment/<enrollment_id>/teachers/

    Get all teachers for a specific student enrollment (to start new chat)
    Returns class teacher and subject teachers for that student's class
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, enrollment_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get the enrollment and verify parent has access to this student
            academic_year_id = request.query_params.get("academic_year_id")
            try:
                enrollment_filters = {"id": enrollment_id, "is_active": True}
                if academic_year_id:
                    enrollment_filters["academic_class__academic_year_id"] = academic_year_id
                enrollment = (
                    StudentEnrollment.objects.filter(**enrollment_filters)
                    .select_related(
                        "student",
                        "academic_class",
                        "academic_class__standard",
                        "academic_class__section",
                        "academic_class__class_teacher",
                    )
                    .first()
                )

                if not enrollment:
                    return Response(
                        {"success": False, "message": "Enrollment not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

                # Verify parent has access to this student
                has_access = StudentParent.objects.filter(
                    parent=parent, student=enrollment.student, is_active=True
                ).exists()

                if not has_access:
                    return Response(
                        {
                            "success": False,
                            "message": "You don't have access to this student",
                        },
                        status=status.HTTP_403_FORBIDDEN,
                    )

            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            academic_class = enrollment.academic_class
            student = enrollment.student

            # Get all teachers for this class
            teachers_data = []
            teacher_ids_set = set()

            # 1. Class teacher
            if academic_class.class_teacher:
                class_teacher = academic_class.class_teacher
                teacher_ids_set.add(class_teacher.id)

                # Check if existing chat room exists
                existing_room = self._get_existing_chat_room(parent, class_teacher)

                teachers_data.append(
                    {
                        "id": class_teacher.id,
                        "name": class_teacher.full_name,
                        "email": class_teacher.email,
                        "employee_id": class_teacher.employee_id,
                        "profile_image": (
                            class_teacher.profile_image.url
                            if class_teacher.profile_image
                            else None
                        ),
                        "role": "Class Teacher",
                        "subject_name": None,
                        "subject_code": None,
                        "existing_room_id": existing_room.id if existing_room else None,
                        "has_chat": existing_room is not None,
                    }
                )

            # 2. Subject teachers
            subject_teachers = SubjectTeacher.objects.filter(
                academic_class=academic_class, is_active=True
            ).select_related("teacher", "subject")

            for subject_teacher in subject_teachers:
                teacher = subject_teacher.teacher

                if teacher.id not in teacher_ids_set:
                    teacher_ids_set.add(teacher.id)

                    existing_room = self._get_existing_chat_room(parent, teacher)

                    teachers_data.append(
                        {
                            "id": teacher.id,
                            "name": teacher.full_name,
                            "email": teacher.email,
                            "employee_id": teacher.employee_id,
                            "profile_image": (
                                teacher.profile_image.url
                                if teacher.profile_image
                                else None
                            ),
                            "role": "Subject Teacher",
                            "subject_name": subject_teacher.subject.name,
                            "subject_code": subject_teacher.subject.code,
                            "existing_room_id": (
                                existing_room.id if existing_room else None
                            ),
                            "has_chat": existing_room is not None,
                        }
                    )

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollment_id": enrollment.id,
                        "student": {
                            "id": student.id,
                            "name": student.full_name,
                            "roll_number": student.roll_number,
                            "student_id": student.student_id,
                        },
                        "class_info": {
                            "id": academic_class.id,
                            "name": str(academic_class),
                            "standard": (
                                academic_class.standard.name
                                if academic_class.standard
                                else None
                            ),
                            "section": (
                                academic_class.section.name
                                if academic_class.section
                                else None
                            ),
                        },
                        "teachers": teachers_data,
                        "total_teachers": len(teachers_data),
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(
                f"Error in ParentStudentTeachersListView: {str(e)}", exc_info=True
            )
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_existing_chat_room(self, parent, teacher):
        """Get existing individual chat room between parent and teacher"""
        try:
            room = ChatRoom.objects.filter(
                room_type="INDIVIDUAL", is_active=True, parents=parent, teachers=teacher
            ).first()
            return room
        except:
            return None


class ParentCreateTeacherChatView(APIView):
    """
    POST /parent/chat/create-teacher-chat/

    Create or get existing individual chat room with a teacher
    Body: {"teacher_id": 456}
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
            teacher_id = request.data.get("teacher_id")

            if not teacher_id:
                return Response(
                    {"success": False, "message": "teacher_id is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify teacher exists
            try:
                teacher = Teacher.objects.get(id=teacher_id, is_active=True)
            except Teacher.DoesNotExist:
                return Response(
                    {"success": False, "message": "Teacher not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # ✅ Check if chat room already exists - CORRECT WAY
            # Find room where parent is participant AND teacher is participant
            existing_room = (
                ChatRoom.objects.filter(room_type="INDIVIDUAL", is_active=True)
                .filter(parents=parent)  # Parent in this room
                .filter(teachers=teacher)  # Teacher in this room
                .first()
            )

            if existing_room:
                serializer = ChatRoomSerializer(
                    existing_room,
                    context={
                        "request": request,
                        "user_type": "parent",
                        "user_id": str(parent.id),
                    },
                )
                return Response(
                    {
                        "success": True,
                        "message": "Existing chat room found",
                        "is_new": False,
                        "data": serializer.data,
                    }
                )

            # Create new individual chat room
            room_name = f"Chat: {parent.full_name} & {teacher.full_name}"

            room = ChatRoom.objects.create(
                room_type="INDIVIDUAL",
                name=room_name,
                description=f"Individual chat between parent {parent.full_name} and teacher {teacher.full_name}",
                created_by_type="parent",
                created_by_id=str(parent.id),
                is_active=True,
                is_encrypted=True,
            )

            # Add parent as participant
            ChatParticipantParent.objects.create(
                room=room,
                parent=parent,
                role="MEMBER",
                is_active=True,
                notification_enabled=True,
            )

            # Add teacher as participant
            ChatParticipantTeacher.objects.create(
                room=room,
                teacher=teacher,
                role="MEMBER",
                is_active=True,
                notification_enabled=True,
            )

            logger.info(
                f"Individual chat created between parent {parent.id} and teacher {teacher.id}"
            )

            serializer = ChatRoomSerializer(
                room,
                context={
                    "request": request,
                    "user_type": "parent",
                    "user_id": str(parent.id),
                },
            )

            return Response(
                {
                    "success": True,
                    "message": "Chat room created successfully",
                    "is_new": True,
                    "data": serializer.data,
                },
                status=status.HTTP_201_CREATED,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(
                f"Error in ParentCreateTeacherChatView: {str(e)}", exc_info=True
            )
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


def _get_student_relevant_teacher_ids(student):
    """
    Teacher IDs relevant to a student's individual chats — across ALL of the
    student's enrollments (current + historical), not just the currently
    active one.

    Individual teacher<->parent chat rooms aren't linked to a specific
    student/enrollment (see TeacherCreateDirectChatView / ParentCreateTeacherChatView),
    so visibility is inferred from "does this teacher teach one of the
    student's classes". Scoping that to only the *current* academic year's
    class means a message from a teacher who taught the student last year
    (before class/section promotion) becomes permanently invisible the
    moment the year rolls over, even though the room and message still
    exist. Looking across all of the student's classes keeps that history
    visible while still scoping to this specific student (not siblings).
    """
    class_ids = list(
        StudentEnrollment.objects.filter(student=student, is_active=True).values_list(
            "academic_class_id", flat=True
        )
    )
    if not class_ids:
        return []

    teacher_ids = set(
        AcademicClass.objects.filter(
            id__in=class_ids, class_teacher__isnull=False
        ).values_list("class_teacher_id", flat=True)
    )
    teacher_ids.update(
        SubjectTeacher.objects.filter(
            academic_class_id__in=class_ids, is_active=True
        ).values_list("teacher_id", flat=True)
    )
    return list(teacher_ids)


class ParentChatRoomsByEnrollmentView(APIView):
    """
    GET /parent/chat/rooms/?enrollment_id=<enrollment_id>

    Get all chat rooms for a parent filtered by specific student enrollment
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
            enrollment_id = request.query_params.get("enrollment_id")

            if not enrollment_id:
                return Response(
                    {"success": False, "message": "enrollment_id is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify enrollment and parent access
            academic_year_id = request.query_params.get("academic_year_id")
            try:
                enrollment_filters = {"id": enrollment_id, "is_active": True}
                if academic_year_id:
                    enrollment_filters["academic_class__academic_year_id"] = academic_year_id
                enrollment = (
                    StudentEnrollment.objects.filter(**enrollment_filters)
                    .select_related("student", "academic_class")
                    .first()
                )

                if not enrollment:
                    return Response(
                        {"success": False, "message": "Enrollment not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

                has_access = StudentParent.objects.filter(
                    parent=parent, student=enrollment.student, is_active=True
                ).exists()

                if not has_access:
                    return Response(
                        {
                            "success": False,
                            "message": "You don't have access to this student",
                        },
                        status=status.HTTP_403_FORBIDDEN,
                    )

            except StudentEnrollment.DoesNotExist:
                return Response(
                    {"success": False, "message": "Enrollment not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            student = enrollment.student
            academic_class = enrollment.academic_class

            # Get all relevant rooms
            class_rooms = (
                ChatRoom.objects.filter(is_active=True, academic_class=academic_class)
                .filter(Q(parents=parent) | Q(students=student))
                .distinct()
            )

            # Teachers relevant to this specific student, across all of their
            # enrollments (current + historical) — not just the current class.
            teacher_ids = _get_student_relevant_teacher_ids(student)

            # Get individual chats with these teachers
            individual_chats = ChatRoom.objects.filter(
                room_type="INDIVIDUAL",
                is_active=True,
                parents=parent,
                teachers__id__in=teacher_ids,
            ).distinct()

            # Combine all rooms
            all_rooms = (class_rooms | individual_chats).distinct()
            all_rooms = (
                all_rooms.select_related("academic_class", "subject", "academic_year")
                .prefetch_related("teachers", "parents", "students")
                .order_by("-last_message_at")
            )

            # Add unread count for parent
            rooms_data = []
            for room in all_rooms:
                unread_count = self._get_unread_count_for_parent(room, parent)

                serializer = ChatRoomSerializer(
                    room,
                    context={
                        "request": request,
                        "user_type": "parent",
                        "user_id": str(parent.id),
                    },
                )
                room_data = serializer.data
                room_data["unread_count"] = unread_count
                rooms_data.append(room_data)

            return Response(
                {
                    "success": True,
                    "data": {
                        "enrollment_id": str(enrollment.id),
                        "student": {
                            "id": str(student.id),
                            "name": student.full_name,
                            "roll_number": student.roll_number,
                            "student_id": student.student_id,
                        },
                        "class_info": {
                            "id": str(academic_class.id),
                            "name": str(academic_class),
                        },
                        "rooms": rooms_data,
                        "total_rooms": len(rooms_data),
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(
                f"Error in ParentChatRoomsByEnrollmentView: {str(e)}", exc_info=True
            )
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_unread_count_for_parent(self, room, parent):
        """Get unread messages count for parent in this room"""
        try:
            messages = Message.objects.filter(room=room, is_deleted=False)
            read_messages = MessageReadReceipt.objects.filter(
                user_type="parent", user_id=str(parent.id), message__in=messages
            ).values_list("message_id", flat=True)

            return (
                messages.exclude(id__in=read_messages)
                .exclude(sender_type="parent", sender_id=str(parent.id))
                .count()
            )
        except:
            return 0


# =====================================================
# STUDENT CHAT VIEWS (for when students access chat)
# =====================================================
# =====================================================
# STUDENT CHAT VIEWS (accessed via parent login)
# =====================================================
class StudentClassmatesForChatView(APIView):
    """
    GET /student/chat/classmates/?student_id=<student_id>

    Get all classmates of a specific student that they can start individual chats with
    Returns students in the same class who are NOT already in an individual chat with this student
    (Parent accessing child's account)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get parent from SCHOOL DB
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            current_student = student_parent.student

            # Get current enrollment (most recent year first — classmates
            # must be resolved against the student's CURRENT class, not a
            # stale enrollment from a previous academic year)
            current_enrollment = (
                StudentEnrollment.objects.filter(
                    student=current_student, is_active=True
                )
                .select_related("academic_class")
                .order_by("-academic_class__academic_year__start_date")
                .first()
            )

            if not current_enrollment:
                return Response(
                    {"success": False, "message": "Student not enrolled in any class"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get all classmates (students in same class, excluding self)
            classmates = (
                StudentEnrollment.objects.filter(
                    academic_class=current_enrollment.academic_class, is_active=True
                )
                .exclude(student=current_student)
                .select_related("student")
                .order_by("roll_number")
            )

            # Get all existing individual chat rooms for this student
            existing_individual_rooms = ChatRoom.objects.filter(
                room_type="INDIVIDUAL", is_active=True, students=current_student
            ).prefetch_related("students")

            # Create a set of student IDs that already have individual chat with current student
            existing_chat_student_ids = set()
            for room in existing_individual_rooms:
                other_student = room.students.exclude(id=current_student.id).first()
                if other_student:
                    existing_chat_student_ids.add(other_student.id)

            classmates_data = []
            for enrollment in classmates:
                student = enrollment.student

                # Check if individual chat already exists
                existing_room = (
                    ChatRoom.objects.filter(
                        room_type="INDIVIDUAL", is_active=True, students=current_student
                    )
                    .filter(students=student)
                    .first()
                )

                classmates_data.append(
                    {
                        "student_id": student.id,
                        "name": student.full_name
                        or f"{student.first_name} {student.last_name}".strip(),
                        "roll_number": enrollment.roll_number,
                        "profile_image": (
                            student.profile_image.url if student.profile_image else None
                        ),
                        "existing_chat_room_id": (
                            str(existing_room.id) if existing_room else None
                        ),
                        "has_chat": existing_room is not None,
                    }
                )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": current_student.id,
                            "student_name": current_student.full_name,
                            "class_name": str(current_enrollment.academic_class),
                            "class_id": current_enrollment.academic_class.id,
                            "roll_number": current_enrollment.roll_number,
                        },
                        "total_classmates": len(classmates_data),
                        "classmates": classmates_data,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentClassmatesForChatView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class StudentChatRoomListView(APIView):
    """
    GET /student/chat/rooms/?student_id=<student_id>

    Get all chat rooms for a specific student (parent accessing child's chats)
    Returns rooms separated by type: individual_chats, class_groups, subject_groups
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            # Get parent from SCHOOL DB
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Get all chat rooms where student is participant
            rooms = (
                ChatRoom.objects.filter(students=student, is_active=True)
                .select_related("academic_class", "subject", "academic_year")
                .prefetch_related("teachers", "parents", "students")
                .order_by("-last_message_at")
            )

            # Separate rooms by type
            individual_chats = []
            class_groups = []
            subject_groups = []

            for room in rooms:
                # Get unread count for student
                unread_count = self._get_unread_count_for_student(room, student)

                # Get last message
                last_message = (
                    room.messages.filter(is_deleted=False)
                    .order_by("-created_at")
                    .first()
                )

                room_data = {
                    "room_id": str(room.id),
                    "room_type": room.room_type,
                    "name": room.name,
                    "created_at": room.created_at,
                    "last_message_at": room.last_message_at,
                    "unread_count": unread_count,
                    "last_message": (
                        {
                            "content": (
                                last_message.content[:100]
                                if last_message and last_message.content
                                else None
                            ),
                            "sender_name": (
                                last_message.get_sender_name() if last_message else None
                            ),
                            "created_at": (
                                last_message.created_at if last_message else None
                            ),
                        }
                        if last_message
                        else None
                    ),
                }

                if room.room_type == "INDIVIDUAL":
                    # For individual chats, get the other participant info
                    other_participant = None

                    # Check if other participant is a student (classmate)
                    other_student = room.students.exclude(id=student.id).first()
                    if other_student:
                        other_participant = {
                            "type": "student",
                            "id": other_student.id,
                            "name": other_student.full_name,
                            "roll_number": other_student.roll_number,
                            "profile_image": (
                                other_student.profile_image.url
                                if other_student.profile_image
                                else None
                            ),
                        }
                    else:
                        # Could be teacher or parent
                        other_teacher = room.teachers.first()
                        if other_teacher:
                            other_participant = {
                                "type": "teacher",
                                "id": other_teacher.id,
                                "name": other_teacher.full_name,
                                "profile_image": (
                                    other_teacher.profile_image.url
                                    if other_teacher.profile_image
                                    else None
                                ),
                            }
                        else:
                            other_parent = room.parents.first()
                            if other_parent:
                                other_participant = {
                                    "type": "parent",
                                    "id": other_parent.id,
                                    "name": other_parent.full_name,
                                }

                    room_data["other_participant"] = other_participant
                    individual_chats.append(room_data)

                elif room.room_type == "CLASS":
                    room_data["class_info"] = {
                        "class_id": (
                            room.academic_class.id if room.academic_class else None
                        ),
                        "class_name": (
                            str(room.academic_class) if room.academic_class else None
                        ),
                        "total_students": room.students.count(),
                    }
                    class_groups.append(room_data)

                elif room.room_type == "SUBJECT":
                    room_data["subject_info"] = {
                        "subject_id": room.subject.id if room.subject else None,
                        "subject_name": room.subject.name if room.subject else None,
                        "subject_code": room.subject.code if room.subject else None,
                        "class_name": (
                            str(room.academic_class) if room.academic_class else None
                        ),
                    }
                    subject_groups.append(room_data)
                else:
                    # Other room types (GROUP, PARENT_TEACHER, etc.)
                    individual_chats.append(room_data)

            # Get current enrollment for class name (most recent year first)
            current_enrollment = (
                StudentEnrollment.objects.filter(student=student, is_active=True)
                .select_related("academic_class")
                .order_by("-academic_class__academic_year__start_date")
                .first()
            )

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_info": {
                            "student_id": student.id,
                            "student_name": student.full_name,
                            "class_name": (
                                str(current_enrollment.academic_class)
                                if current_enrollment
                                else None
                            ),
                            "class_id": (
                                current_enrollment.academic_class.id
                                if current_enrollment
                                else None
                            ),
                            "roll_number": (
                                current_enrollment.roll_number
                                if current_enrollment
                                else None
                            ),
                        },
                        "individual_chats": individual_chats,
                        "class_groups": class_groups,
                        "subject_groups": subject_groups,
                        "total_rooms": len(individual_chats)
                        + len(class_groups)
                        + len(subject_groups),
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatRoomListView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_unread_count_for_student(self, room, student):
        """Get unread messages count for student in this room"""
        try:
            messages = Message.objects.filter(room=room, is_deleted=False)
            read_messages = MessageReadReceipt.objects.filter(
                user_type="student", user_id=str(student.id), message__in=messages
            ).values_list("message_id", flat=True)

            return (
                messages.exclude(id__in=read_messages)
                .exclude(sender_type="student", sender_id=str(student.id))
                .count()
            )
        except:
            return 0


class StudentChatRoomDetailView(APIView):
    """
    GET /student/chat/rooms/<room_id>/?student_id=<student_id>

    Get details of a specific chat room for a student
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, room_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Get room and verify student has access
            room = ChatRoom.objects.filter(
                id=room_id, students=student, is_active=True
            ).first()

            if not room:
                return Response(
                    {
                        "success": False,
                        "message": "Chat room not found or access denied",
                    },
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get unread count for student
            unread_count = self._get_unread_count(student, room)

            serializer = ChatRoomSerializer(
                room,
                context={
                    "request": request,
                    "user_type": "student",
                    "user_id": str(student.id),
                },
            )

            data = serializer.data
            data["unread_count"] = unread_count

            return Response({"success": True, "data": data})

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatRoomDetailView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _get_unread_count(self, student, room):
        """Get unread messages count for student"""
        messages = Message.objects.filter(room=room, is_deleted=False)
        read_messages = MessageReadReceipt.objects.filter(
            user_type="student", user_id=str(student.id), message__in=messages
        ).values_list("message_id", flat=True)

        return (
            messages.exclude(id__in=read_messages)
            .exclude(sender_type="student", sender_id=str(student.id))
            .count()
        )


class StudentChatMessageListView(APIView):
    """
    GET /student/chat/rooms/<room_id>/messages/?student_id=<student_id>
    POST /student/chat/rooms/<room_id>/messages/?student_id=<student_id>

    Get messages for a chat room or send a new message (as student)
    Supports lazy loading with pagination and AI content moderation
    """

    permission_classes = [IsAuthenticated]

    def get(self, request, room_id):
        """Get messages for a chat room with pagination"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Verify student has access to this room
            if not ChatRoom.objects.filter(
                id=room_id, students=student, is_active=True
            ).exists():
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            # Get pagination parameters
            page = int(request.query_params.get("page", 1))
            page_size = int(request.query_params.get("page_size", 20))
            limit = int(request.query_params.get("limit", page_size))
            offset = int(request.query_params.get("offset", (page - 1) * page_size))

            # Alternative: Use page and page_size
            if "page" in request.query_params and not request.query_params.get(
                "offset"
            ):
                offset = (page - 1) * page_size

            # Get total count for pagination info
            total_messages = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).count()

            # Get messages with pagination
            messages = (
                Message.objects.filter(room_id=room_id, is_deleted=False)
                .select_related("room", "reply_to")
                .prefetch_related("delivery_status", "read_receipts")
                .order_by("-created_at")[offset : offset + limit]
            )

            # Mark messages as delivered for student
            self._mark_as_delivered(room_id, student, messages)

            # Mark messages as read for the current student
            self._mark_as_read(student, messages)

            # Reverse messages to show oldest first (for chat display)
            messages_list = list(messages)
            messages_list.reverse()

            serializer = MessageSerializer(
                messages_list,
                many=True,
                context={
                    "request": request,
                    "user_type": "student",
                    "user_id": str(student.id),
                },
            )

            # Calculate pagination metadata
            has_next = (offset + limit) < total_messages
            has_previous = offset > 0
            next_offset = offset + limit if has_next else None
            previous_offset = max(0, offset - limit) if has_previous else None
            next_page = page + 1 if has_next else None
            previous_page = page - 1 if has_previous else None
            total_pages = (total_messages + limit - 1) // limit if limit > 0 else 0

            return Response(
                {
                    "success": True,
                    "data": {
                        "messages": serializer.data,
                        "pagination": {
                            "current_page": page,
                            "page_size": limit,
                            "total_messages": total_messages,
                            "total_pages": total_pages,
                            "has_next": has_next,
                            "has_previous": has_previous,
                            "next_offset": next_offset,
                            "previous_offset": previous_offset,
                            "next_page": next_page,
                            "previous_page": previous_page,
                        },
                        "room_info": {
                            "room_id": room_id,
                            "student_id": str(student.id),
                            "student_name": student.full_name,
                        },
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatMessageListView GET: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def post(self, request, room_id):
        """Send a new message as student with AI content moderation"""
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Verify student has access to this room
            if not ChatRoom.objects.filter(
                id=room_id, students=student, is_active=True
            ).exists():
                return Response(
                    {"success": False, "message": "Access denied"},
                    status=status.HTTP_403_FORBIDDEN,
                )

            content = request.data.get("content")
            message_type = request.data.get("message_type", "TEXT")
            reply_to_id = request.data.get("reply_to_id")

            if not content:
                return Response(
                    {"success": False, "message": "Content is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Check cache first for moderation result (to reduce API calls)
            cache_key = f"moderate_{hash(content)}_{student_id}"
            moderation_result = cache.get(cache_key)

            if not moderation_result:
                # AI Content Moderation
                moderation_result = moderate_chat_content(content, student.full_name)
                # Cache for 5 minutes (same message might be tried again)
                cache.set(cache_key, moderation_result, 300)

            # Check if message is academic appropriate
            if not moderation_result.get("is_academic", False):
                return Response(
                    {
                        "success": False,
                        "message": moderation_result.get(
                            "message", "Message rejected: Not academic appropriate"
                        ),
                        "reason": moderation_result.get("reason"),
                        "suggestions": moderation_result.get("suggestions"),
                        "category": moderation_result.get("category"),
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Create message as student (parent is sending on behalf of student)
            message = Message.objects.create(
                room_id=room_id,
                sender_type="student",
                sender_id=str(student.id),
                content=content,
                message_type=message_type,
                reply_to_id=reply_to_id,
                metadata={
                    "moderated": True,
                    "moderation_category": moderation_result.get("category"),
                    "moderation_confidence": moderation_result.get("confidence", 0),
                },
            )
            broadcast_new_message(message)

            # Update room's last message time
            ChatRoom.objects.filter(id=room_id).update(last_message_at=timezone.now())

            # Process mentions
            self._process_mentions(message, content)

            # Create delivery records for all participants
            self._create_delivery_records(message)

            # Get updated room info
            room = ChatRoom.objects.get(id=room_id)

            serializer = MessageSerializer(
                message,
                context={
                    "request": request,
                    "user_type": "student",
                    "user_id": str(student.id),
                },
            )

            return Response(
                {
                    "success": True,
                    "message": "Message sent successfully",
                    "moderation_info": {
                        "is_academic": True,
                        "category": moderation_result.get("category"),
                    },
                    "data": serializer.data,
                    "room_info": {
                        "room_id": room_id,
                        "last_message_at": room.last_message_at,
                    },
                },
                status=status.HTTP_201_CREATED,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatMessageListView POST: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def _mark_as_read(self, student, messages):
        """Mark messages as read for the current student"""
        from django.db.models import Q

        # Get IDs of messages that are not sent by this student
        message_ids = [msg.id for msg in messages if msg.sender_id != str(student.id)]

        if not message_ids:
            return

        # Update MessageDelivery status to READ
        updated_count = MessageDelivery.objects.filter(
            message_id__in=message_ids,
            user_type="student",
            user_id=str(student.id),
            status__in=["SENT", "DELIVERED"],
        ).update(status="READ", read_at=timezone.now())

        # Update Message.is_read flag for messages where ALL students have read it
        for message in messages:
            if message.sender_id != str(student.id):
                # Get total active participants in this room (excluding sender)
                total_participants = (
                    ChatParticipantStudent.objects.filter(
                        room=message.room, is_active=True
                    )
                    .exclude(student_id=message.sender_id)
                    .count()
                )

                # Count how many have read this message
                read_count = MessageDelivery.objects.filter(
                    message=message, user_type="student", status="READ"
                ).count()

                # If all participants have read it, mark message as read
                if read_count >= total_participants and total_participants > 0:
                    if not message.is_read:
                        message.is_read = True
                        message.read_at = timezone.now()
                        message.save(update_fields=["is_read", "read_at"])
                        logger.info(
                            f"Message {message.id} marked as read by all participants"
                        )

                # Create/update MessageReadReceipt for detailed tracking
                MessageReadReceipt.objects.update_or_create(
                    message=message,
                    user_type="student",
                    user_id=str(student.id),
                    defaults={"read_at": timezone.now()},
                )

    def _mark_as_delivered(self, room_id, student, messages):
        """Mark messages as delivered for student"""
        for message in messages:
            if message.sender_type != "student" or message.sender_id != str(student.id):
                MessageDelivery.objects.update_or_create(
                    message=message,
                    user_type="student",
                    user_id=str(student.id),
                    defaults={"status": "DELIVERED", "delivered_at": timezone.now()},
                )

    def _create_delivery_records(self, message):
        """Create delivery records for all participants"""
        room = message.room

        # Parent participants
        parent_participants = ChatParticipantParent.objects.filter(
            room=room, is_active=True
        )

        for participant in parent_participants:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="parent",
                user_id=str(participant.parent_id),
                defaults={"status": "SENT"},
            )

        # Teacher participants
        teacher_participants = ChatParticipantTeacher.objects.filter(
            room=room, is_active=True
        )

        for participant in teacher_participants:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="teacher",
                user_id=str(participant.teacher_id),
                defaults={"status": "SENT"},
            )

        # Other student participants
        other_students = ChatParticipantStudent.objects.filter(
            room=room, is_active=True
        ).exclude(student_id=message.sender_id)

        for participant in other_students:
            MessageDelivery.objects.get_or_create(
                message=message,
                user_type="student",
                user_id=str(participant.student_id),
                defaults={"status": "SENT"},
            )

    def _process_mentions(self, message, content):
        """Process @mentions in message"""
        mention_pattern = r"@(teacher|parent|student)_(\d+)"
        mentions = re.findall(mention_pattern, content)

        for user_type, user_id in mentions:
            ChatMention.objects.create(
                message=message, user_type=user_type, user_id=user_id, is_notified=False
            )


class StudentChatMarkAsReadView(APIView):
    """
    POST /student/chat/rooms/<room_id>/mark-read/?student_id=<student_id>

    Mark all messages in a room as read for student
    """

    permission_classes = [IsAuthenticated]

    def post(self, request, room_id):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Get participant through model
            participant = ChatParticipantStudent.objects.filter(
                room_id=room_id, student=student, is_active=True
            ).first()

            if not participant:
                return Response(
                    {"success": False, "message": "Not a participant in this room"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get all unread messages in room (excluding student's own messages)
            messages = Message.objects.filter(
                room_id=room_id, is_deleted=False
            ).exclude(sender_type="student", sender_id=str(student.id))

            # Create read receipts for all unread messages
            read_count = 0
            for message in messages:
                receipt, created = MessageReadReceipt.objects.get_or_create(
                    message=message, user_type="student", user_id=str(student.id)
                )
                if created:
                    read_count += 1

            # Update participant's last read time
            participant.last_read_at = timezone.now()
            participant.save(update_fields=["last_read_at"])

            return Response(
                {
                    "success": True,
                    "data": {
                        "message": f"Marked {read_count} messages as read for student",
                        "read_count": read_count,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatMarkAsReadView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class StudentChatUnreadCountView(APIView):
    """
    GET /student/chat/unread-count/?student_id=<student_id>

    Get total unread messages count across all rooms for student
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            student = student_parent.student

            # Get all rooms where student is participant
            rooms = ChatRoom.objects.filter(students=student, is_active=True)

            total_unread = 0

            for room in rooms:
                messages = Message.objects.filter(room=room, is_deleted=False)
                read_messages = MessageReadReceipt.objects.filter(
                    user_type="student", user_id=str(student.id), message__in=messages
                ).values_list("message_id", flat=True)

                unread = (
                    messages.exclude(id__in=read_messages)
                    .exclude(sender_type="student", sender_id=str(student.id))
                    .count()
                )

                total_unread += unread

            return Response(
                {
                    "success": True,
                    "data": {
                        "student_id": student.id,
                        "student_name": student.full_name,
                        "total_unread": total_unread,
                    },
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentChatUnreadCountView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class CreateStudentToStudentChatView(APIView):
    """
    POST /student/chat/create/

    Create individual chat between two students in the same class
    """

    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            # Get parent to verify access
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get student_id from query params (which child is using the app)
            student_id = request.query_params.get("student_id")
            if not student_id:
                return Response(
                    {"success": False, "message": "student_id parameter is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Verify this student belongs to the parent
            student_parent = (
                parent.student_parents.filter(student_id=student_id, is_active=True)
                .select_related("student")
                .first()
            )

            if not student_parent:
                return Response(
                    {
                        "success": False,
                        "message": "Student not found or you don't have access",
                    },
                    status=status.HTTP_403_FORBIDDEN,
                )

            current_student = student_parent.student

            # Get the friend's student ID from request
            friend_student_id = request.data.get("friend_student_id")

            if not friend_student_id:
                return Response(
                    {"success": False, "message": "friend_student_id is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Get friend student object
            try:
                friend_student = Student.objects.get(
                    id=friend_student_id, is_active=True
                )
            except Student.DoesNotExist:
                return Response(
                    {"success": False, "message": "Friend student not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

            # Get current student's enrollment (most recent year first)
            current_enrollment = (
                StudentEnrollment.objects.filter(
                    student=current_student, is_active=True
                )
                .select_related("academic_class")
                .order_by("-academic_class__academic_year__start_date")
                .first()
            )

            if not current_enrollment:
                return Response(
                    {"success": False, "message": "You are not enrolled in any class"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Get friend's enrollment (most recent year first)
            friend_enrollment = (
                StudentEnrollment.objects.filter(student=friend_student, is_active=True)
                .select_related("academic_class")
                .order_by("-academic_class__academic_year__start_date")
                .first()
            )

            if not friend_enrollment:
                return Response(
                    {
                        "success": False,
                        "message": "Friend is not enrolled in any class",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Check if they are in the same class
            if (
                current_enrollment.academic_class_id
                != friend_enrollment.academic_class_id
            ):
                return Response(
                    {
                        "success": False,
                        "message": "Students must be in the same class to chat",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Check if individual chat already exists between these two students
            existing_room = (
                ChatRoom.objects.filter(room_type="INDIVIDUAL", is_active=True)
                .filter(students=current_student)
                .filter(students=friend_student)
                .first()
            )

            if existing_room:
                # Get the other participant's info
                other_participant = existing_room.students.exclude(
                    id=current_student.id
                ).first()

                return Response(
                    {
                        "success": True,
                        "message": "Existing chat room found",
                        "data": {
                            "room_id": str(existing_room.id),
                            "room_type": existing_room.room_type,
                            "is_new": False,
                            "created_at": existing_room.created_at,
                            "other_participant": (
                                {
                                    "id": other_participant.id,
                                    "name": other_participant.full_name,
                                    "roll_number": other_participant.roll_number,
                                    "profile_image": (
                                        other_participant.profile_image.url
                                        if other_participant.profile_image
                                        else None
                                    ),
                                }
                                if other_participant
                                else None
                            ),
                        },
                    }
                )

            # Create new individual chat room
            room = ChatRoom.objects.create(
                room_type="INDIVIDUAL",
                name=f"Chat between {current_student.full_name} and {friend_student.full_name}",
                created_by_type="student",
                created_by_id=str(current_student.id),
                is_active=True,
            )

            # Add both students as participants using ManyToMany
            room.students.add(current_student)
            room.students.add(friend_student)

            # Also create participant records with through model for additional settings
            from chat.models import ChatParticipantStudent

            ChatParticipantStudent.objects.bulk_create(
                [
                    ChatParticipantStudent(
                        room=room,
                        student=current_student,
                        role="MEMBER",
                        is_active=True,
                        joined_at=timezone.now(),
                    ),
                    ChatParticipantStudent(
                        room=room,
                        student=friend_student,
                        role="MEMBER",
                        is_active=True,
                        joined_at=timezone.now(),
                    ),
                ],
                ignore_conflicts=True,
            )

            return Response(
                {
                    "success": True,
                    "message": "Individual chat created successfully",
                    "data": {
                        "room_id": str(room.id),
                        "room_type": room.room_type,
                        "is_new": True,
                        "created_at": room.created_at,
                        "other_participant": {
                            "id": friend_student.id,
                            "name": friend_student.full_name,
                            "roll_number": friend_student.roll_number,
                            "profile_image": (
                                friend_student.profile_image.url
                                if friend_student.profile_image
                                else None
                            ),
                        },
                    },
                },
                status=status.HTTP_201_CREATED,
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in CreateStudentToStudentChatView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class TransportTypeListView(APIView):
    """List all transport types (Bus, Van, Auto)"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        transport_types = TransportType.objects.all()
        serializer = TransportTypeSerializer(transport_types, many=True)
        return Response(
            {"success": True, "count": transport_types.count(), "data": serializer.data}
        )


# =====================================================
# TRANSPORT VEHICLE VIEWS
# =====================================================
class TransportVehicleListView(APIView):
    """List all transport vehicles with optional filters and pagination"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            transport_type_id = request.query_params.get("transport_type_id")
            search = request.query_params.get("search", "")

            # Get pagination parameters (similar to chat view)
            page = int(request.query_params.get("page", 1))
            page_size = int(request.query_params.get("page_size", 10))
            limit = int(request.query_params.get("limit", page_size))
            offset = int(request.query_params.get("offset", (page - 1) * page_size))

            # Alternative: Use page and page_size if offset not provided
            if "page" in request.query_params and not request.query_params.get(
                "offset"
            ):
                offset = (page - 1) * page_size

            # Start with base queryset
            queryset = TransportVehicle.objects.all()

            # Apply filters
            if transport_type_id:
                queryset = queryset.filter(transport_type_id=transport_type_id)

            # Apply search
            if search:
                queryset = queryset.filter(
                    Q(bus_number__icontains=search)
                    | Q(vehicle_number__icontains=search)
                    | Q(driver_name__icontains=search)
                    | Q(driver_phone__icontains=search)
                )

            # Order by bus number
            queryset = queryset.order_by("bus_number")

            # Get total count for pagination info
            total_count = queryset.count()

            # Get paginated results
            vehicles = queryset[offset : offset + limit]

            # Serialize the data
            serializer = TransportVehicleSerializer(vehicles, many=True)

            # Calculate pagination metadata (similar to chat view)
            has_next = (offset + limit) < total_count
            has_previous = offset > 0
            next_offset = offset + limit if has_next else None
            previous_offset = max(0, offset - limit) if has_previous else None
            next_page = page + 1 if has_next else None
            previous_page = page - 1 if has_previous else None
            total_pages = (total_count + limit - 1) // limit if limit > 0 else 0

            return Response(
                {
                    "success": True,
                    "data": serializer.data,
                    "pagination": {
                        "current_page": page,
                        "page_size": limit,
                        "total_count": total_count,
                        "total_pages": total_pages,
                        "has_next": has_next,
                        "has_previous": has_previous,
                        "next_offset": next_offset,
                        "previous_offset": previous_offset,
                        "next_page": next_page,
                        "previous_page": previous_page,
                    },
                }
            )

        except Exception as e:
            logger.error(f"Error in TransportVehicleListView GET: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class TransportVehicleDetailView(APIView):
    """Get details of a specific transport vehicle"""

    permission_classes = [IsAuthenticated]

    def get(self, request, vehicle_id):
        vehicle = get_object_or_404(TransportVehicle, id=vehicle_id)
        serializer = TransportVehicleDetailSerializer(vehicle)
        return Response({"success": True, "data": serializer.data})


# =====================================================
# ROUTE VIEWS
# =====================================================


class RouteListView(APIView):
    """List all routes with optional vehicle filter"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        vehicle_id = request.query_params.get("vehicle_id")

        queryset = Route.objects.all().prefetch_related("stops")

        if vehicle_id:
            queryset = queryset.filter(vehicle_id=vehicle_id)

        serializer = RouteSerializer(queryset, many=True)
        return Response(
            {"success": True, "count": queryset.count(), "data": serializer.data}
        )


class RouteDetailView(APIView):
    """Get details of a specific route including all stops"""

    permission_classes = [IsAuthenticated]

    def get(self, request, route_id):
        route = get_object_or_404(Route.objects.prefetch_related("stops"), id=route_id)
        serializer = RouteDetailSerializer(route)
        return Response({"success": True, "data": serializer.data})


class StopsByRouteView(APIView):
    """Get all stops for a specific route in order"""

    permission_classes = [IsAuthenticated]

    def get(self, request, route_id):
        route = get_object_or_404(Route, id=route_id)
        stops = route.stops.all().order_by("stop_order")
        serializer = StopSerializer(stops, many=True)
        return Response(
            {
                "success": True,
                "route_name": route.route_name,
                "route_id": route.id,
                "count": stops.count(),
                "data": serializer.data,
            }
        )


# =====================================================
# STUDENT TRANSPORT VIEWS
# =====================================================


class StudentTransportListView(APIView):
    """
    List all student transport assignments
    - Staff: see all
    - Parent: see only their children's transport
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        # Staff can see all
        if request.user.is_staff:
            queryset = StudentTransport.objects.filter(is_active=True).select_related(
                "student", "vehicle", "route", "stop", "enrollment"
            )
        else:
            # For parents, show only their children's transport
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
                child_ids = parent.student_parents.filter(is_active=True).values_list(
                    "student_id", flat=True
                )
                queryset = StudentTransport.objects.filter(
                    student_id__in=child_ids, is_active=True
                ).select_related("student", "vehicle", "route", "stop", "enrollment")
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

        serializer = StudentTransportSerializer(queryset, many=True)
        return Response(
            {"success": True, "count": queryset.count(), "data": serializer.data}
        )


class StudentTransportDetailView(APIView):
    """Get details of a specific student transport assignment"""

    permission_classes = [IsAuthenticated]

    def get(self, request, transport_id):
        transport = get_object_or_404(
            StudentTransport.objects.select_related(
                "student",
                "vehicle",
                "route",
                "stop",
                "enrollment",
                "enrollment__academic_class",
                "enrollment__academic_class__standard",
                "enrollment__academic_class__section",
            ),
            id=transport_id,
            is_active=True,
        )

        # Check access for non-staff users
        if not request.user.is_staff:
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
                if not parent.student_parents.filter(
                    student=transport.student, is_active=True
                ).exists():
                    return Response(
                        {"success": False, "message": "Access denied"},
                        status=status.HTTP_403_FORBIDDEN,
                    )
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

        serializer = StudentTransportDetailSerializer(transport)
        return Response({"success": True, "data": serializer.data})


class StudentCurrentTransportView(APIView):
    """
    Get current transport details for a student.
    Query params:
        - student_id: int (required)
        - enrollment_id: int (optional, defaults to current active enrollment)
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        student_id = request.query_params.get("student_id")
        enrollment_id = request.query_params.get("enrollment_id")

        if not student_id:
            return Response(
                {"success": False, "message": "student_id parameter is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            # Verify access for non-staff
            if not request.user.is_staff:
                try:
                    parent = Parent.objects.get(external_user_id=request.user.id)
                    if not parent.student_parents.filter(
                        student_id=student_id, is_active=True
                    ).exists():
                        return Response(
                            {"success": False, "message": "Access denied"},
                            status=status.HTTP_403_FORBIDDEN,
                        )
                except Parent.DoesNotExist:
                    return Response(
                        {"success": False, "message": "Parent profile not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

            student = get_object_or_404(Student, id=student_id, is_active=True)

            # Get current academic year
            today = timezone.now().date()
            current_academic_year_obj = AcademicYear.objects.filter(
                is_active=True, start_date__lte=today, end_date__gte=today
            ).first()
            current_academic_year = (
                current_academic_year_obj.name if current_academic_year_obj else None
            )

            # Build query for current transport
            transport_query = Q(student=student, is_active=True)

            if enrollment_id:
                transport_query &= Q(enrollment_id=enrollment_id)
            elif current_academic_year:
                transport_query &= Q(academic_year=current_academic_year)

            transport = (
                StudentTransport.objects.filter(transport_query)
                .select_related(
                    "vehicle",
                    "route",
                    "stop",
                    "enrollment",
                    "enrollment__academic_class",
                )
                .first()
            )

            if transport:
                serializer = StudentTransportSerializer(transport)
                return Response(
                    {"success": True, "has_transport": True, "data": serializer.data}
                )
            else:
                return Response(
                    {
                        "success": True,
                        "has_transport": False,
                        "message": "No transport assigned for current academic year",
                        "data": None,
                    }
                )

        except Student.DoesNotExist:
            return Response(
                {"success": False, "message": "Student not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in StudentCurrentTransportView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class StudentTransportByEnrollmentView(APIView):
    """Get transport details for a student by enrollment_id"""

    permission_classes = [IsAuthenticated]

    def get(self, request, enrollment_id):
        enrollment = get_object_or_404(
            StudentEnrollment, id=enrollment_id, is_active=True
        )

        # Verify access for non-staff
        if not request.user.is_staff:
            try:
                parent = Parent.objects.get(external_user_id=request.user.id)
                if not parent.student_parents.filter(
                    student=enrollment.student, is_active=True
                ).exists():
                    return Response(
                        {"success": False, "message": "Access denied"},
                        status=status.HTTP_403_FORBIDDEN,
                    )
            except Parent.DoesNotExist:
                return Response(
                    {"success": False, "message": "Parent profile not found"},
                    status=status.HTTP_404_NOT_FOUND,
                )

        transport = (
            StudentTransport.objects.filter(enrollment=enrollment, is_active=True)
            .select_related("vehicle", "route", "stop")
            .first()
        )

        if transport:
            serializer = StudentTransportSerializer(transport)
            return Response({"success": True, "data": serializer.data})
        else:
            return Response(
                {
                    "success": True,
                    "has_transport": False,
                    "message": "No transport assigned for this enrollment",
                    "data": None,
                }
            )


class ParentStudentsTransportView(APIView):
    """Get transport details for all children of a parent"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)

            # Get all children
            children = parent.student_parents.filter(is_active=True).select_related(
                "student"
            )

            # Get current academic year
            today = timezone.now().date()
            current_academic_year_obj = AcademicYear.objects.filter(
                is_active=True, start_date__lte=today, end_date__gte=today
            ).first()
            current_academic_year = (
                current_academic_year_obj.name if current_academic_year_obj else None
            )

            students_transport = []
            for sp in children:
                student = sp.student

                # Find transport for this student
                transport_query = Q(student=student, is_active=True)
                if current_academic_year:
                    transport_query &= Q(academic_year=current_academic_year)

                transport = (
                    StudentTransport.objects.filter(transport_query)
                    .select_related("vehicle", "route", "stop")
                    .first()
                )

                # Get current enrollment
                current_enrollment = (
                    StudentEnrollment.objects.filter(student=student, is_active=True)
                    .select_related("academic_class")
                    .first()
                )

                students_transport.append(
                    {
                        "student_id": student.id,
                        "student_name": student.full_name
                        or f"{student.first_name} {student.last_name}".strip(),
                        "student_profile_image": (
                            student.profile_image.url if student.profile_image else None
                        ),
                        "relationship": sp.relationship,
                        "is_primary_contact": sp.is_primary_contact,
                        "class_name": (
                            str(current_enrollment.academic_class)
                            if current_enrollment
                            else None
                        ),
                        "has_transport": transport is not None,
                        "transport_details": (
                            StudentTransportSerializer(transport).data
                            if transport
                            else None
                        ),
                    }
                )

            return Response(
                {
                    "success": True,
                    "count": len(students_transport),
                    "data": students_transport,
                }
            )

        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        except Exception as e:
            logger.error(f"Error in ParentStudentsTransportView: {str(e)}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


class TransportRoutesWithDetailsView(APIView):
    """Get all routes with stop count and assigned student count (Public info for parents)"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        routes = (
            Route.objects.all()
            .annotate(
                stop_count=Count("stops", distinct=True),
                student_count=Count(
                    "studenttransport",
                    filter=Q(studenttransport__is_active=True),
                    distinct=True,
                ),
            )
            .select_related("vehicle")
        )

        data = []
        for route in routes:
            data.append(
                {
                    "id": route.id,
                    "route_name": route.route_name,
                    "start_location": route.start_location,
                    "destination": route.destination,
                    "distance_km": route.distance_km,
                    "vehicle": {
                        "id": route.vehicle.id,
                        "bus_number": route.vehicle.bus_number,
                        "vehicle_number": route.vehicle.vehicle_number,
                        "driver_name": route.vehicle.driver_name,
                        "driver_phone": route.vehicle.driver_phone,
                        "capacity": route.vehicle.capacity,
                    },
                    "stop_count": route.stop_count,
                    "assigned_students": route.student_count,
                }
            )

        return Response({"success": True, "count": len(data), "data": data})


class RouteStopsWithTimingsView(APIView):
    """Get all stops with timings for a specific route"""

    permission_classes = [IsAuthenticated]

    def get(self, request, route_id):
        route = get_object_or_404(Route, id=route_id)

        stops = route.stops.all().order_by("stop_order")

        stops_data = []
        for stop in stops:
            stops_data.append(
                {
                    "id": stop.id,
                    "stop_name": stop.stop_name,
                    "stop_order": stop.stop_order,
                    "arrival_time": stop.arrival_time,
                    "arrival_time_formatted": (
                        stop.arrival_time.strftime("%I:%M %p")
                        if stop.arrival_time
                        else None
                    ),
                }
            )

        return Response(
            {
                "success": True,
                "route": {
                    "id": route.id,
                    "name": route.route_name,
                    "start_location": route.start_location,
                    "destination": route.destination,
                    "distance_km": route.distance_km,
                },
                "vehicle": {
                    "id": route.vehicle.id,
                    "bus_number": route.vehicle.bus_number,
                    "vehicle_number": route.vehicle.vehicle_number,
                    "driver_name": route.vehicle.driver_name,
                    "driver_phone": route.vehicle.driver_phone,
                },
                "stops": stops_data,
                "total_stops": len(stops_data),
            }
        )


# =====================================================
# AI PARENT ASSISTANT
# =====================================================

class AIParentAssistantView(APIView):
    """
    POST /parent/ai/assistant/

    Natural language Q&A for parents about their child's school data.

    Request Body:
    {
        "question": "How many days was my child absent this month?",
        "enrollment_id": 123,
        "academic_year_id": 1   // optional
    }

    Response:
    {
        "success": true,
        "data": {
            "question": "...",
            "answer": "...",
            "intent": "attendance",
            "student_name": "...",
            "data_found": true
        }
    }
    """

    permission_classes = [IsAuthenticated]

    HOMEWORK_PAGE_SIZE = 5

    def post(self, request):
        question = request.data.get("question", "").strip()
        enrollment_id = request.data.get("enrollment_id")
        academic_year_id = request.data.get("academic_year_id")
        homework_page = int(request.data.get("homework_page", 1))

        if not question:
            return Response(
                {"success": False, "message": "question is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if not enrollment_id:
            return Response(
                {"success": False, "message": "enrollment_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Verify the enrollment belongs to this parent's child
        child_student_ids = parent.student_parents.filter(
            is_active=True
        ).values_list("student_id", flat=True)

        enrollment_filters = {
            "id": enrollment_id,
            "student_id__in": child_student_ids,
            "is_active": True,
        }
        if academic_year_id:
            enrollment_filters["academic_class__academic_year_id"] = academic_year_id

        try:
            enrollment = StudentEnrollment.objects.select_related(
                "student",
                "academic_class",
                "academic_class__academic_year",
                "academic_class__standard",
                "academic_class__section",
            ).get(**enrollment_filters)
        except StudentEnrollment.DoesNotExist:
            return Response(
                {"success": False, "message": "Enrollment not found or access denied"},
                status=status.HTTP_404_NOT_FOUND,
            )

        try:
            student_data = self._gather_student_data(enrollment, homework_page)
            from .utils.ai import answer_parent_question
            ai_result = answer_parent_question(question, student_data)

            hw_pagination = student_data.get("completed_homework_pagination", {})

            return Response(
                {
                    "success": True,
                    "data": {
                        "question": question,
                        "answer": ai_result.get("answer", ""),
                        "intent": ai_result.get("intent", "unknown"),
                        "student_name": student_data.get("student_name", ""),
                        "data_found": ai_result.get("data_found", False),
                        "homework_pagination": hw_pagination,
                    },
                },
                status=status.HTTP_200_OK,
            )

        except Exception as e:
            logger.error(f"Error in AIParentAssistantView: {e}")
            return Response(
                {"success": False, "message": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    # ------------------------------------------------------------------
    # Data gathering helpers
    # ------------------------------------------------------------------

    def _gather_student_data(self, enrollment, homework_page=1):
        import calendar as cal_module

        today = timezone.now().date()
        student = enrollment.student
        academic_class = enrollment.academic_class
        academic_year = academic_class.academic_year

        student_name = " ".join(
            filter(None, [
                getattr(student, "first_name", ""),
                getattr(student, "last_name", ""),
            ])
        ) or str(student)

        class_name = str(academic_class)
        year_name = getattr(academic_year, "name", str(academic_year))

        completed_hw, hw_pagination = self._get_completed_homework(enrollment, homework_page)

        return {
            "student_name": student_name,
            "class_name": class_name,
            "academic_year": year_name,
            "attendance": self._get_attendance(enrollment, academic_class, today, cal_module, academic_year),
            "pending_homework": self._get_pending_homework(enrollment, academic_class, today),
            "completed_homework": completed_hw,
            "completed_homework_pagination": hw_pagination,
            "fee": self._get_fee_summary(enrollment, academic_class, academic_year),
            "exams": self._get_exams(enrollment, academic_class),
            "timetable": self._get_timetable(academic_class),
            "teachers": self._get_teachers(academic_class),
            "transport": self._get_transport(enrollment),
            "announcements": self._get_announcements(academic_class),
        }

    # ------------------------------------------------------------------
    # Attendance: all time periods so AI can pick the right one
    # ------------------------------------------------------------------

    def _get_attendance(self, enrollment, academic_class, today, cal_module, academic_year):
        base_qs = StudentAttendance.objects.filter(
            enrollment=enrollment,
            session__academic_class=academic_class,
            is_active=True,
        )

        def _count(qs):
            total = qs.count()
            present = qs.filter(status="PRESENT").count()
            absent = qs.filter(status="ABSENT").count()
            late = qs.filter(status="LATE").count()
            leave = qs.filter(status="LEAVE").count()
            pct = round(present / total * 100, 1) if total > 0 else 0.0
            return {
                "total_working_days": total,
                "present_days": present,
                "absent_days": absent,
                "late_days": late,
                "leave_days": leave,
                "attendance_percentage": pct,
            }

        def _from_summary(month, year):
            s = AttendanceSummary.objects.filter(
                enrollment=enrollment, month=month, year=year
            ).first()
            if s:
                return {
                    "total_working_days": s.total_working_days,
                    "present_days": s.present_days,
                    "absent_days": s.absent_days,
                    "late_days": s.late_days,
                    "leave_days": s.leave_days,
                    "attendance_percentage": float(s.attendance_percentage),
                }
            return None

        # --- Today ---
        today_stats = _count(base_qs.filter(session__date=today))

        # --- This week (Monday → today) ---
        week_start = today - timedelta(days=today.weekday())
        week_stats = _count(base_qs.filter(session__date__range=[week_start, today]))

        # --- This month ---
        month_stats = _from_summary(today.month, today.year) or _count(
            base_qs.filter(session__date__month=today.month, session__date__year=today.year)
        )

        # --- Last month ---
        if today.month == 1:
            lm, ly = 12, today.year - 1
        else:
            lm, ly = today.month - 1, today.year
        last_month_stats = _from_summary(lm, ly) or _count(
            base_qs.filter(session__date__month=lm, session__date__year=ly)
        )

        # --- Academic year up to today ---
        # Use ay_start..today: base_qs is already scoped to session__academic_class
        # so sessions from other years can't bleed in. Using min(today, ay_end)
        # caused 0 results when ay_end < today (e.g. school sessions run past the
        # recorded end date).
        ay_start = getattr(academic_year, "start_date", None)
        if ay_start:
            ay_stats = _count(base_qs.filter(session__date__range=[ay_start, today]))
        else:
            ay_stats = _count(base_qs)

        return {
            "today": {
                "label": today.strftime("%B %d, %Y"),
                **today_stats,
            },
            "this_week": {
                "label": f"{week_start.strftime('%b %d')} – {today.strftime('%b %d, %Y')}",
                **week_stats,
            },
            "this_month": {
                "label": f"{cal_module.month_name[today.month]} {today.year}",
                **month_stats,
            },
            "last_month": {
                "label": f"{cal_module.month_name[lm]} {ly}",
                **last_month_stats,
            },
            "academic_year": {
                "label": f"{getattr(academic_year, 'name', 'Academic Year')} (up to {today.strftime('%B %d, %Y')})",
                **ay_stats,
            },
        }

    def _get_pending_homework(self, enrollment, academic_class, today):
        tasks_qs = ClassTask.objects.filter(
            academic_class=academic_class,
            due_date__gte=today,
            is_published=True,
            is_active=True,
        ).select_related("subject").order_by("due_date")[:30]

        submitted_ids = set(
            TaskSubmission.objects.filter(
                enrollment=enrollment,
                task__in=tasks_qs,
                status__in=["SUBMITTED", "CHECKED", "LATE"],
            ).values_list("task_id", flat=True)
        )

        homework = []
        for task in tasks_qs:
            if task.id not in submitted_ids:
                homework.append({
                    "title": task.title,
                    "subject": task.subject.name if task.subject else "N/A",
                    "due_date": task.due_date.strftime("%B %d, %Y") if task.due_date else "N/A",
                    "status": "pending",
                })
        return homework

    def _get_completed_homework(self, enrollment, page=1):
        page_size = self.HOMEWORK_PAGE_SIZE
        offset = (page - 1) * page_size

        qs = TaskSubmission.objects.filter(
            enrollment=enrollment,
            status__in=["SUBMITTED", "CHECKED", "LATE"],
            is_active=True,
        ).select_related(
            "task", "task__subject", "task__task_type"
        ).order_by("-submitted_at")

        total_count = qs.count()
        total_pages = max(1, (total_count + page_size - 1) // page_size)
        submissions = qs[offset: offset + page_size]

        completed = []
        for sub in submissions:
            task = sub.task
            completed.append({
                "title": task.title,
                "subject": task.subject.name if task.subject else "N/A",
                "due_date": task.due_date.strftime("%B %d, %Y") if task.due_date else "N/A",
                "submitted_on": sub.submitted_at.strftime("%B %d, %Y") if sub.submitted_at else "N/A",
                "status": sub.get_status_display(),
                "marks_obtained": float(sub.marks_obtained) if sub.marks_obtained is not None else None,
                "total_marks": float(task.total_marks) if task.total_marks is not None else None,
                "remarks": sub.remarks or None,
                "is_late": sub.status == "LATE",
            })

        pagination = {
            "page": page,
            "page_size": page_size,
            "total_count": total_count,
            "total_pages": total_pages,
            "has_more": page < total_pages,
        }
        return completed, pagination

    # ------------------------------------------------------------------
    # Exams
    # ------------------------------------------------------------------

    def _get_exams(self, enrollment, academic_class):
        try:
            from exam.models import Exam, ExamResult, StudentMarks
            today = timezone.now().date()

            exams = Exam.objects.filter(
                academic_class=academic_class,
                is_active=True,
            ).select_related("exam_type").order_by("start_date")

            upcoming, ongoing, results = [], [], []

            for exam in exams:
                info = {
                    "name": exam.name,
                    "type": exam.exam_type.name if exam.exam_type else "N/A",
                    "start_date": exam.start_date.strftime("%B %d, %Y") if exam.start_date else "N/A",
                    "end_date": exam.end_date.strftime("%B %d, %Y") if exam.end_date else "N/A",
                    "status": exam.status,
                }

                if exam.status == "results_published":
                    result = ExamResult.objects.filter(
                        student_enrollment=enrollment, exam=exam
                    ).first()
                    subject_marks = StudentMarks.objects.filter(
                        student_enrollment=enrollment,
                        exam_subject__exam=exam,
                    ).select_related("exam_subject__subject")

                    info["result"] = {
                        "total_marks": float(result.total_marks) if result else None,
                        "max_marks": float(result.total_max_marks) if result else None,
                        "percentage": float(result.percentage) if result else None,
                        "grade": result.overall_grade if result else None,
                        "rank": result.rank if result else None,
                        "result_status": result.result_status if result else None,
                    }
                    info["subject_marks"] = [
                        {
                            "subject": m.exam_subject.subject.name,
                            "obtained": float(m.obtained_marks),
                            "max": float(m.exam_subject.max_marks),
                            "grade": m.grade,
                            "is_passed": m.is_passed,
                        }
                        for m in subject_marks
                    ]
                    results.append(info)
                elif exam.start_date and exam.start_date > today:
                    upcoming.append(info)
                elif exam.start_date and exam.end_date and exam.start_date <= today <= exam.end_date:
                    ongoing.append(info)

            return {"upcoming": upcoming, "ongoing": ongoing, "results": results}
        except Exception as e:
            logger.error(f"AI exams error: {e}")
            return {}

    # ------------------------------------------------------------------
    # Timetable
    # ------------------------------------------------------------------

    def _get_timetable(self, academic_class):
        try:
            timetables = TimeTable.objects.filter(
                academic_class=academic_class,
                is_active=True,
            ).select_related(
                "subject", "weekday", "subject_teacher__teacher"
            ).order_by("weekday", "period_number")

            schedule = {}
            for tt in timetables:
                day = tt.weekday.day_name if tt.weekday else "Unknown"
                if day not in schedule:
                    schedule[day] = []
                teacher_name = "N/A"
                if tt.subject_teacher and tt.subject_teacher.teacher:
                    teacher_name = tt.subject_teacher.teacher.full_name or "N/A"
                schedule[day].append({
                    "period": tt.period_number,
                    "subject": tt.subject.name if tt.subject else "N/A",
                    "start_time": tt.start_time.strftime("%I:%M %p") if tt.start_time else "N/A",
                    "end_time": tt.end_time.strftime("%I:%M %p") if tt.end_time else "N/A",
                    "teacher": teacher_name,
                })
            return schedule
        except Exception as e:
            logger.error(f"AI timetable error: {e}")
            return {}

    # ------------------------------------------------------------------
    # Teachers
    # ------------------------------------------------------------------

    def _get_teachers(self, academic_class):
        try:
            teachers = []
            seen_ids = set()

            if academic_class.class_teacher:
                ct = academic_class.class_teacher
                seen_ids.add(ct.id)
                teachers.append({
                    "name": ct.full_name,
                    "role": "Class Teacher",
                    "subject": None,
                    "email": ct.email,
                    "phone": ct.phone,
                })

            subject_teachers = SubjectTeacher.objects.filter(
                academic_class=academic_class,
                is_active=True,
            ).select_related("teacher", "subject")

            for st in subject_teachers:
                t = st.teacher
                if t.id in seen_ids:
                    continue
                seen_ids.add(t.id)
                teachers.append({
                    "name": t.full_name,
                    "role": "Subject Teacher",
                    "subject": st.subject.name if st.subject else "N/A",
                    "email": t.email,
                    "phone": t.phone,
                })

            return teachers
        except Exception as e:
            logger.error(f"AI teachers error: {e}")
            return []

    # ------------------------------------------------------------------
    # Transport
    # ------------------------------------------------------------------

    def _get_transport(self, enrollment):
        try:
            transport = StudentTransport.objects.filter(
                student=enrollment.student,
                is_active=True,
            ).select_related("vehicle", "route", "stop").first()

            if not transport:
                return None

            stop = transport.stop
            return {
                "route": transport.route.name if transport.route else "N/A",
                "bus_number": transport.vehicle.bus_number if transport.vehicle else "N/A",
                "vehicle_number": transport.vehicle.vehicle_number if transport.vehicle else "N/A",
                "driver_name": transport.vehicle.driver_name if transport.vehicle else "N/A",
                "driver_phone": transport.vehicle.driver_phone if transport.vehicle else "N/A",
                "stop_name": stop.name if stop else "N/A",
                "stop_arrival_time": stop.arrival_time.strftime("%I:%M %p") if stop and stop.arrival_time else "N/A",
            }
        except Exception as e:
            logger.error(f"AI transport error: {e}")
            return None

    # ------------------------------------------------------------------
    # Announcements
    # ------------------------------------------------------------------

    def _get_announcements(self, academic_class):
        try:
            today = timezone.now().date()
            active_q = Q(expire_on__isnull=True) | Q(expire_on__gte=today)

            school_wide = list(
                CommonAnnouncement.objects.filter(is_active=True).filter(active_q)
                .order_by("-created_at")
                .values("title", "message", "priority", "created_at")[:5]
            )
            for a in school_wide:
                a["date"] = a.pop("created_at").strftime("%B %d, %Y")
                a["message"] = a["message"][:200]

            standard_type = None
            if academic_class.standard:
                standard_type = academic_class.standard.standard_type

            class_q = Q(is_for_all_students=True) | Q(academic_class=academic_class)
            if standard_type:
                class_q = class_q | Q(section=standard_type)

            class_specific = list(
                StudentAnnouncement.objects.filter(is_active=True).filter(active_q)
                .filter(class_q)
                .order_by("-created_at")
                .values("title", "message", "priority", "created_at")[:5]
            )
            for a in class_specific:
                a["date"] = a.pop("created_at").strftime("%B %d, %Y")
                a["message"] = a["message"][:200]

            return {"school_wide": school_wide, "class_specific": class_specific}
        except Exception as e:
            logger.error(f"AI announcements error: {e}")
            return {}

    def _get_fee_summary(self, enrollment, academic_class, academic_year):
        try:
            today = timezone.now().date()
            terms = academic_year.terms.filter(is_active=True)

            total_fees = 0.0
            total_paid = 0.0
            components_detail = []   # per-component breakdown
            next_due = None

            for term in terms:
                class_fees = ClassFeeStructure.objects.filter(
                    academic_class=academic_class,
                    academic_term=term,
                    is_active=True,
                ).select_related("fee_component", "academic_term")

                for cfs in class_fees:
                    # Use student assignment (with discount) if exists
                    assignment = StudentFeeAssignment.objects.filter(
                        enrollment=enrollment,
                        class_fee_structure=cfs,
                        is_active=True,
                    ).first()

                    if assignment:
                        amount = float(assignment.calculate_final_amount())
                        due_date = assignment.due_date_override or cfs.due_date
                        paid_qs_amount = (
                            PaymentFeeComponent.objects.filter(
                                student_fee_assignment=assignment
                            ).aggregate(total=models.Sum("amount_paid"))["total"] or 0
                        )
                    else:
                        amount = float(cfs.amount)
                        due_date = cfs.due_date
                        paid_qs_amount = (
                            PaymentFeeComponent.objects.filter(
                                class_fee_structure=cfs,
                                payment__student_enrollment=enrollment,
                            ).aggregate(total=models.Sum("amount_paid"))["total"] or 0
                        )

                    paid = float(paid_qs_amount)
                    pending = max(0.0, round(amount - paid, 2))
                    is_overdue = due_date and due_date < today and pending > 0

                    if pending > 0 and due_date and (next_due is None or due_date < next_due):
                        next_due = due_date

                    total_fees += amount
                    total_paid += paid

                    status_label = (
                        "Paid" if pending <= 0
                        else ("Overdue" if is_overdue else ("Partial" if paid > 0 else "Pending"))
                    )

                    components_detail.append({
                        "component": cfs.fee_component.name if cfs.fee_component else "N/A",
                        "term": term.name,
                        "due_date": due_date.strftime("%B %d, %Y") if due_date else "N/A",
                        "total_amount": round(amount, 2),
                        "paid_amount": round(paid, 2),
                        "pending_amount": pending,
                        "status": status_label,
                        "is_overdue": is_overdue,
                    })

            # Payment history (completed payments only)
            payment_history = []
            payments_qs = FeePayment.objects.filter(
                student_enrollment=enrollment,
                status="completed",
            ).order_by("-payment_date").prefetch_related(
                "payment_components__fee_component",
                "payment_components__academic_term",
            )[:10]

            for p in payments_qs:
                components_paid = [
                    f"{pc.fee_component.name} (₹{pc.amount_paid})"
                    for pc in p.payment_components.select_related("fee_component").all()
                ]
                payment_history.append({
                    "receipt_number": p.receipt_number,
                    "amount_paid": float(p.amount_paid),
                    "payment_date": p.payment_date.strftime("%B %d, %Y"),
                    "payment_method": p.get_payment_method_display(),
                    "components_paid": components_paid,
                })

            total_pending = max(0.0, round(total_fees - total_paid, 2))
            overall_status = (
                "All Paid" if total_pending <= 0 and total_fees > 0
                else ("No Fees Assigned" if total_fees == 0 else "Has Pending")
            )

            return {
                "total_fees": round(total_fees, 2),
                "paid_amount": round(total_paid, 2),
                "pending_amount": total_pending,
                "overall_status": overall_status,
                "next_due_date": next_due.strftime("%B %d, %Y") if next_due else None,
                "components": components_detail,
                "payment_history": payment_history,
            }
        except Exception as e:
            logger.error(f"AI fee summary error: {e}")
            return {}


class StudentSubjectBooksView(APIView):
    """
    GET /api/parent/books/?student_id=<id>

    Returns subjects for the student's current active enrollment together with
    their book document info.

    Logic:
    - Mandatory subjects: all subjects assigned to the student's class.
    - Elective / language subjects: only those specifically assigned to the
      student via StudentSubject (i.e. their chosen subjects).
    - Each subject carries has_book, book_document_url (absolute URL to open
      the PDF directly), and book_document_name.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        student_id = request.query_params.get("student_id")
        if not student_id:
            return Response(
                {"success": False, "message": "student_id is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Verify parent identity
        try:
            parent = Parent.objects.get(external_user_id=request.user.id)
        except Parent.DoesNotExist:
            return Response(
                {"success": False, "message": "Parent profile not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Verify the student is linked to this parent
        student_parent = (
            parent.student_parents.filter(student_id=student_id, is_active=True)
            .select_related("student")
            .first()
        )
        if not student_parent:
            return Response(
                {
                    "success": False,
                    "message": "Student not found or you don't have access to this student",
                },
                status=status.HTTP_404_NOT_FOUND,
            )

        student = student_parent.student

        # Get the student's current active enrollment
        enrollment = (
            StudentEnrollment.objects.filter(student=student, is_active=True)
            .select_related(
                "academic_class",
                "academic_class__standard",
                "academic_class__section",
                "academic_class__academic_year",
            )
            .first()
        )
        if not enrollment:
            return Response(
                {"success": False, "message": "No active enrollment found for this student"},
                status=status.HTTP_404_NOT_FOUND,
            )

        academic_class = enrollment.academic_class

        # Collect elective/language subject IDs specifically assigned to this student
        student_specific_ids = set(
            StudentSubject.objects.filter(
                enrollment=enrollment, is_active=True
            ).values_list("subject_id", flat=True)
        )

        # Fetch all subjects assigned to the class
        class_subjects = ClassSubject.objects.filter(
            academic_class=academic_class
        ).select_related("subject", "subject__category", "subject__subject_group")

        subjects_data = []
        for cs in class_subjects:
            subj = cs.subject
            # Elective/language subjects are only shown if the student owns them
            if (
                subj.subject_type in ["elective", "language"]
                and subj.id not in student_specific_ids
            ):
                continue

            book_url = None
            book_name = None
            if subj.book_document:
                book_url = request.build_absolute_uri(subj.book_document.url)
                book_name = os.path.basename(subj.book_document.name)

            subjects_data.append(
                {
                    "subject_id": subj.id,
                    "subject_name": subj.name,
                    "subject_code": subj.code,
                    "subject_type": subj.subject_type,
                    "is_student_specific": subj.id in student_specific_ids,
                    "category": (
                        {"id": subj.category.id, "name": subj.category.name}
                        if subj.category
                        else None
                    ),
                    "subject_group": (
                        {"id": subj.subject_group.id, "name": subj.subject_group.name}
                        if subj.subject_group
                        else None
                    ),
                    "has_book": bool(subj.book_document),
                    "book_document_url": book_url,
                    "book_document_name": book_name,
                }
            )

        return Response(
            {
                "success": True,
                "student": {
                    "id": student.id,
                    "full_name": student.full_name
                    or f"{student.first_name or ''} {student.last_name or ''}".strip(),
                    "admission_number": student.admission_number or "",
                },
                "class": {
                    "id": academic_class.id,
                    "name": f"{academic_class.standard.name} - {academic_class.section.name if academic_class.section else 'All'}",
                    "standard": academic_class.standard.name,
                    "academic_year": (
                        academic_class.academic_year.name
                        if academic_class.academic_year
                        else None
                    ),
                },
                "enrollment_id": enrollment.id,
                "total_subjects": len(subjects_data),
                "subjects_with_books": sum(1 for s in subjects_data if s["has_book"]),
                "subjects": subjects_data,
            },
            status=status.HTTP_200_OK,
        )
