from datetime import datetime, timedelta
from sqlalchemy import func, and_, or_, case
from src.models import DatabaseContextManager
from src.models.models import (
    Student, Course, TeachingSession, DailyTeachingSession, Attendance, Enrollment,
    CourseProgress, AssignmentSubmission, Assignment, User,
    NotificationPreference, SubmissionStatus, tutor_course_association,
    AttendanceStatus, Speciality, enrollment_courses, TeachingVerificationMethod
)
from flask import current_app
from src.utils import (
    ApiABC,
    custom_response,
    send_email
)
from typing import Dict, List
import uuid

class StudentsManager(ApiABC):
    def __init__(self):
        self.table = Student

    def create(self, payload: Dict) -> Dict:
        """Create a new student account"""
        with DatabaseContextManager() as ctx:
            
            existing_student_id = ctx.session.query(Student).filter(
                Student.student_id == payload['student_id']
            ).first()
            
            if existing_student_id:
                return custom_response(
                    success=False,
                    data="Student ID already in use",
                    status_code=400
                )
            
            # Create the student
            student = Student(
                id=str(uuid.uuid4()),
                email=payload['email'],
                password_hash=payload['password_hash'],  # Should be hashed before this
                first_name=payload['first_name'],
                last_name=payload['last_name'],
                phone=payload.get('phone'),
                user_type='student',
                student_id=payload['student_id'],
                year_of_study=payload.get('year_of_study', 1),
                program=payload['program'],
                enrollment_date=datetime.utcnow().date(),
                gender=payload.get('gender'),
                date_of_birth=payload.get('date_of_birth'),
                nationality=payload.get('nationality'),
                profile_picture=payload.get('profile_picture'),
                guardian_name=payload.get('guardian_name'),
                guardian_contact=payload.get('guardian_contact'),
                emergency_contact=payload.get('emergency_contact'),
                address=payload.get('address')
            )
            
            ctx.session.add(student)
            
            # Create default notification preferences
            notification_pref = NotificationPreference(
                id=str(uuid.uuid4()),
                user_id=student.id,
                receive_email=True,
                receive_sms=False,
                receive_push=True
            )
            ctx.session.add(notification_pref)
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data=self._student_to_dict(student),
                status_code=201
            )

    def get(self, student_id: str) -> Dict:
        """Get detailed information about a student including their courses and upcoming sessions"""
        with DatabaseContextManager() as ctx:
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            
            if not student:
                return custom_response(
                    success=False,
                    data="Student not found",
                    status_code=404
                )
            
            # Get base student info
            student_data = self._student_to_dict(student)
            
            # Get enrolled courses through speciality
            courses = ctx.session.query(Course).filter(
                Course.speciality_id == student.speciality_id,
                Course.is_active == True
            ).all()
            
            student_data['courses'] = [{
                'id': course.id,
                'code': course.code,
                'title': course.title,
                'credits': course.credits,
                'department': course.department,
                'semester': course.semester,
                'tutors': [{
                    'id': tutor.id,
                    'name': f"{tutor.first_name} {tutor.last_name}",
                    'is_primary': ctx.session.query(tutor_course_association).filter(
                        tutor_course_association.c.tutor_id == tutor.id,
                        tutor_course_association.c.course_id == course.id
                    ).first().is_primary
                } for tutor in course.tutors]
            } for course in courses]
            
            # Get upcoming sessions (next 7 days) through speciality
            upcoming_sessions = ctx.session.query(TeachingSession).join(
                Course, TeachingSession.course_id == Course.id
            ).filter(
                Course.speciality_id == student.speciality_id,
                TeachingSession.start_time >= datetime.utcnow(),
                TeachingSession.start_time <= datetime.utcnow() + timedelta(days=7),
                TeachingSession.status == 'scheduled'
            ).order_by(TeachingSession.start_time).all()
            
            student_data['upcoming_sessions'] = [{
                'id': session.id,
                'title': session.title,
                'course_id': session.course_id,
                'course_code': session.course.code,
                'start_datetime': session.start_time.isoformat(),
                'end_datetime': session.end_time.isoformat(),
                'location': session.location,
                'status': session.status,
                'is_mandatory': session.is_mandatory,
                'tutor_name': f"{session.tutor.first_name} {session.tutor.last_name}"
            } for session in upcoming_sessions]
            
            # Get attendance statistics
            student_data['attendance_stats'] = self._get_attendance_stats(ctx, student_id)
            
            # Get assignment statistics
            student_data['assignment_stats'] = self._get_assignment_stats(ctx, student_id)
            
            return custom_response(
                success=True,
                data=student_data,
                status_code=200
            )

    def _student_to_dict(self, student: Student) -> Dict:
        """Convert Student model to dictionary"""
        return {
            'id': student.id,
            'student_id': student.student_id,
            'first_name': student.first_name,
            'last_name': student.last_name,
            'email': student.email,
            'phone': student.phone,
            'profile_picture': student.profile_picture,
            'year_of_study': student.year_of_study,
            'program': student.program,
            'enrollment_date': str(student.enrollment_date) if student.enrollment_date else None,
            'graduation_date': str(student.graduation_date) if student.graduation_date else None,
            'current_semester': student.current_semester,
            'guardian_name': student.guardian_name,
            'guardian_contact': student.guardian_contact,
            'emergency_contact': student.emergency_contact,
            'address': student.address,
            'city': student.city,
            'country': student.country,
            'postal_code': student.postal_code,
            'cumulative_gpa': student.cumulative_gpa,
            'is_on_probation': student.is_on_probation,
            'probation_details': {
                'reason': student.probation_reason,
                'start_date': str(student.probation_start_date) if student.probation_start_date else None,
                'end_date': str(student.probation_end_date) if student.probation_end_date else None
            } if student.is_on_probation else None,
            "courses":[]
        }

    def _get_attendance_stats(self, ctx, student_id: str) -> Dict:
        """Get attendance statistics for a student"""
        # Total sessions attended
        attendance_records = ctx.session.query(Attendance).filter(
            Attendance.student_id == student_id
        ).all()
        
        present_count = sum(1 for r in attendance_records if r.status == AttendanceStatus.present)
        late_count = sum(1 for r in attendance_records if r.status == AttendanceStatus.late)
        absent_count = sum(1 for r in attendance_records if r.status == AttendanceStatus.absent)
        
        # Attendance by course
        course_attendance = ctx.session.query(
            Course.code,
            func.count(Attendance.id),
            func.sum(case((Attendance.status == AttendanceStatus.present, 1), else_=0))
        ).join(
            TeachingSession,
            Attendance.session_id == TeachingSession.id
        ).join(
            Course,
            TeachingSession.course_id == Course.id
        ).filter(
            Attendance.student_id == student_id
        ).group_by(Course.code).all()
        
        return {
            'total_sessions': len(attendance_records),
            'present': present_count,
            'late': late_count,
            'absent': absent_count,
            'attendance_rate': round((present_count / len(attendance_records)) * 100, 1) if attendance_records else 0,
            'by_course': [{
                'course_code': ca[0],
                'total': ca[1],
                'present': ca[2],
                'rate': round((ca[2] / ca[1]) * 100, 1) if ca[1] else 0
            } for ca in course_attendance]
        }

    def _get_assignment_stats(self, ctx, student_id: str) -> Dict:
        """Get assignment statistics for a student"""
        # Total assignments
        assignments = ctx.session.query(
            AssignmentSubmission.status,
            func.count(AssignmentSubmission.id)
        ).filter(
            AssignmentSubmission.student_id == student_id
        ).group_by(AssignmentSubmission.status).all()
        
        # Grades by course
        course_grades = ctx.session.query(
            Course.code,
            func.avg(AssignmentSubmission.grade),
            func.count(AssignmentSubmission.id)
        ).join(
            Assignment,
            AssignmentSubmission.assignment_id == Assignment.id
        ).join(
            Course,
            Assignment.course_id == Course.id
        ).filter(
            AssignmentSubmission.student_id == student_id,
            AssignmentSubmission.status == SubmissionStatus.graded
        ).group_by(Course.code).all()
        
        return {
            'total_assignments': sum(a[1] for a in assignments),
            'not_started': next((a[1] for a in assignments if a[0] == SubmissionStatus.not_started), 0),
            'in_progress': next((a[1] for a in assignments if a[0] == SubmissionStatus.in_progress), 0),
            'submitted': next((a[1] for a in assignments if a[0] == SubmissionStatus.submitted), 0),
            'graded': next((a[1] for a in assignments if a[0] == SubmissionStatus.graded), 0),
            'average_grade': round(sum(cg[1] * cg[2] for cg in course_grades) / sum(cg[2] for cg in course_grades), 1) 
                if course_grades and sum(cg[2] for cg in course_grades) > 0 else None,
            'by_course': [{
                'course_code': cg[0],
                'average_grade': round(cg[1], 1) if cg[1] else None,
                'count': cg[2]
            } for cg in course_grades]
        }

    def update(self, student_id: str, payload: Dict) -> Dict:
        """Update student information"""
        with DatabaseContextManager() as ctx:
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            
            if not student:
                return custom_response(
                    success=False,
                    data="Student not found",
                    status_code=404
                )
            
            # Update base fields
            updatable_fields = [
                'first_name', 'last_name', 'phone', 'profile_picture',
                'year_of_study', 'program', 'guardian_name', 'guardian_contact',
                'emergency_contact', 'address', 'city', 'country', 'postal_code'
            ]
            
            for field in updatable_fields:
                if field in payload:
                    setattr(student, field, payload[field])
            
            # Handle probation status
            if 'is_on_probation' in payload:
                student.is_on_probation = payload['is_on_probation']
                if payload['is_on_probation']:
                    student.probation_start_date = payload.get('probation_start_date')
                    student.probation_end_date = payload.get('probation_end_date')
                    student.probation_reason = payload.get('probation_reason')
                else:
                    student.probation_start_date = None
                    student.probation_end_date = None
                    student.probation_reason = None
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data="Student updated successfully",
                status_code=200
            )

    def enroll_in_course(self, student_id: str, course_id: str) -> Dict:
        """Enroll a student in a course"""
        with DatabaseContextManager() as ctx:
            # Check if student can enroll (same speciality)
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            course = ctx.session.query(Course).filter(Course.id == course_id).first()
            
            if not student or not course:
                return custom_response(
                    success=False,
                    data="Student or course not found",
                    status_code=404
                )
                
            if student.speciality_id != course.speciality_id:
                return custom_response(
                    success=False,
                    data="Student cannot enroll in courses from different speciality",
                    status_code=400
                )
                
            # Check if enrollment already exists
            existing_enrollment = ctx.session.query(Enrollment).join(
                enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.course_id == course_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            ).first()
            
            if existing_enrollment:
                return custom_response(
                    success=False,
                    data="Student is already enrolled in this course",
                    status_code=400
                )
            
            # Check if course exists and is active
            course = ctx.session.query(Course).filter(
                Course.id == course_id,
                Course.is_active == True
            ).first()
            
            if not course:
                return custom_response(
                    success=False,
                    data="Course not found or inactive",
                    status_code=404
                )
            
            # Check if student exists
            student = ctx.session.query(Student).filter(
                Student.id == student_id,
                Student.is_active == True
            ).first()
            
            if not student:
                return custom_response(
                    success=False,
                    data="Student not found or inactive",
                    status_code=404
                )
            
            # Create enrollment record
            enrollment = Enrollment(
                id=str(uuid.uuid4()),
                student_id=student_id,
                course_id=course_id,
                speciality_id=student.speciality_id,
                enrollment_date=datetime.utcnow().date(),
                status='active'
            )
            ctx.session.add(enrollment)
            
            # Create progress record
            progress = CourseProgress(
                id=str(uuid.uuid4()),
                enrollment_id=enrollment.id,
                student_id=student_id,
                completion_percentage=0.0
            )
            ctx.session.add(progress)
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data="Student enrolled successfully",
                status_code=200
            )

    def withdraw_from_course(self, student_id: str, course_id: str, reason: str = None) -> Dict:
        """Withdraw a student from a course"""
        with DatabaseContextManager() as ctx:
            # Check if enrollment exists
            enrollment = ctx.session.query(Enrollment).join(
                enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.course_id == course_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            ).first()
            
            if not enrollment:
                return custom_response(
                    success=False,
                    data="Student is not enrolled in this course",
                    status_code=400
                )
            
            # Update enrollment status instead of deleting
            enrollment.status = 'withdrawn'
            enrollment.withdrawal_date = datetime.utcnow().date()
            enrollment.withdrawal_reason = reason
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data="Student withdrawn from course successfully",
                status_code=200
            )

    def get_courses(self, student_id: str) -> Dict:
        """Get all courses a student is enrolled in"""
        with DatabaseContextManager() as ctx:
            # Get student and their enrolled courses through speciality
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            if not student:
                return {"error": "Student not found"}
                
            courses = ctx.session.query(Course).join(
                enrollment_courses, Course.id == enrollment_courses.c.course_id
            ).join(
                Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active',
                Course.is_active == True
            ).all()
            
            return custom_response(
                success=True,
                data=[{
                    'id': course.id,
                    'code': course.code,
                    'title': course.title,
                    'credits': course.credits,
                    'department': course.department,
                    'semester': course.semester,
                    'tutors': [{
                        'id': tutor.id,
                        'name': f"{tutor.first_name} {tutor.last_name}",
                        'is_primary': ctx.session.query(tutor_course_association).filter(
                            tutor_course_association.c.tutor_id == tutor.id,
                            tutor_course_association.c.course_id == course.id
                        ).first().is_primary
                    } for tutor in course.tutors],
                    'enrollment_status': ctx.session.query(Enrollment).join(
                        enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
                    ).filter(
                        Enrollment.student_id == student_id,
                        enrollment_courses.c.course_id == course.id,
                        enrollment_courses.c.status == 'active',
                        Enrollment.status == 'active'
                    ).first().status
                } for course in courses]
            )

    def get_upcoming_sessions(self, student_id: str, days_ahead: int = 7) -> Dict:
        """Get upcoming teaching sessions for a student"""
        with DatabaseContextManager() as ctx:
            # Get student and their sessions through enrollment
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            if not student:
                return {"error": "Student not found"}
                
            sessions = ctx.session.query(TeachingSession).join(
                enrollment_courses, TeachingSession.course_id == enrollment_courses.c.course_id
            ).join(
                Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active',
                TeachingSession.start_time >= datetime.utcnow(),
                TeachingSession.start_time <= datetime.utcnow() + timedelta(days=days_ahead),
                TeachingSession.status == 'scheduled'
            ).order_by(TeachingSession.start_time).all()
            
            return custom_response(
                success=True,
                data=[{
                    'id': session.id,
                    'title': session.title,
                    'course_id': session.course_id,
                    'course_code': session.course.code,
                    'course_title': session.course.title,
                    'start_datetime': session.start_time.isoformat(),
                    'end_datetime': session.end_time.isoformat(),
                    'location': session.location,
                    'status': session.status,
                    'is_mandatory': session.is_mandatory,
                    'tutor_name': f"{session.tutor.first_name} {session.tutor.last_name}",
                    'attendance_status': ctx.session.query(Attendance).filter(
                        Attendance.session_id == session.id,
                        Attendance.student_id == student_id
                    ).first().status.value if ctx.session.query(Attendance).filter(
                        Attendance.session_id == session.id,
                        Attendance.student_id == student_id
                    ).first() else None
                } for session in sessions]
            )

    def get_past_sessions(self, student_id: str, days_back: int = 30) -> Dict:
        """Get past teaching sessions for a student"""
        with DatabaseContextManager() as ctx:
            # Get student and their past sessions through enrollment
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            if not student:
                return {"error": "Student not found"}
                
            sessions = ctx.session.query(TeachingSession).join(
                enrollment_courses, TeachingSession.course_id == enrollment_courses.c.course_id
            ).join(
                Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active',
                TeachingSession.end_time <= datetime.utcnow(),
                TeachingSession.end_time >= datetime.utcnow() - timedelta(days=days_back)
            ).order_by(TeachingSession.end_time.desc()).all()
            
            return custom_response(
                success=True,
                data=[{
                    'id': session.id,
                    'title': session.title,
                    'course_id': session.course_id,
                    'course_code': session.course.code,
                    'start_datetime': session.start_time.isoformat(),
                    'end_datetime': session.end_time.isoformat(),
                    'status': session.status,
                    'tutor_name': f"{session.tutor.first_name} {session.tutor.last_name}",
                    'attendance': {
                        'status': ctx.session.query(Attendance).filter(
                            Attendance.session_id == session.id,
                            Attendance.student_id == student_id
                        ).first().status.value if ctx.session.query(Attendance).filter(
                            Attendance.session_id == session.id,
                            Attendance.student_id == student_id
                        ).first() else None,
                        'disputed': ctx.session.query(Attendance).filter(
                            Attendance.session_id == session.id,
                            Attendance.student_id == student_id
                        ).first().is_disputed if ctx.session.query(Attendance).filter(
                            Attendance.session_id == session.id,
                            Attendance.student_id == student_id
                        ).first() else False
                    }
                } for session in sessions]
            )

    def get_daily_sessions(self, student_id: str) -> Dict:
        """Get all daily teaching sessions for a student (both upcoming and past)"""
        with DatabaseContextManager() as ctx:
            # Get student and their daily sessions through enrollment
            student = ctx.session.query(Student).filter(Student.id == student_id).first()
            if not student:
                return custom_response(
                    success=False,
                    data="Student not found"
                ), 404
                
            # Get daily teaching sessions for enrolled courses
            daily_sessions = ctx.session.query(DailyTeachingSession).join(
                enrollment_courses, DailyTeachingSession.course_id == enrollment_courses.c.course_id
            ).join(
                Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
            ).join(
                Course, DailyTeachingSession.course_id == Course.id
            ).join(
                User, DailyTeachingSession.tutor_id == User.id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            ).order_by(DailyTeachingSession.session_date.desc(), DailyTeachingSession.start_time.desc()).all()
            
            # Process sessions to include attendance and status information
            session_data = []
            for session in daily_sessions:
                # Get attendance record for this session
                attendance_record = ctx.session.query(Attendance).filter(
                    Attendance.session_id == session.id,
                    Attendance.student_id == student_id
                ).first()
                
                # Determine session status based on date and time
                now = datetime.utcnow()
                session_datetime = datetime.combine(session.session_date, session.start_time)
                
                status = 'scheduled'
                if session_datetime < now:
                    if session.status == 'completed':
                        status = 'completed'
                    elif session.status == 'cancelled':
                        status = 'cancelled'
                    else:
                        status = 'past'
                elif session.status == 'ongoing':
                    status = 'ongoing'
                
                session_data.append({
                    'id': session.id,
                    'course_id': session.course_id,
                    'course_title': session.course.title,
                    'course_code': session.course.code,
                    'tutor_id': session.tutor_id,
                    'tutor_name': f"{session.tutor.first_name} {session.tutor.last_name}",
                    'room': session.room,
                    'session_date': session.session_date.isoformat(),
                    'start_time': session.start_time.isoformat(),
                    'end_time': session.end_time.isoformat(),
                    'session_type': session.session_type,
                    'status': status,
                    'attendance_status': attendance_record.status.value if attendance_record else 'pending',
                    'attendance_timestamp': attendance_record.recorded_at.isoformat() if attendance_record and attendance_record.recorded_at else None,
                    'late_minutes': attendance_record.late_minutes if attendance_record else None,
                    'notes': session.notes,
                    'attendance_taken': session.attendance_taken,
                    'is_verified': session.is_verified,
                    'course': {
                        'id': session.course.id,
                        'title': session.course.title,
                        'code': session.course.code,
                        'department': session.course.department
                    },
                    'tutor': {
                        'id': session.tutor.id,
                        'first_name': session.tutor.first_name,
                        'last_name': session.tutor.last_name,
                        'email': session.tutor.email
                    }
                })
            
            return custom_response(
                success=True,
                data=session_data
            )

    def get_attendance_records(self, student_id: str, course_id: str = None, page: int = 1, per_page: int = 20) -> Dict:
        """Get attendance records for a student"""
        with DatabaseContextManager() as ctx:
            query = ctx.session.query(Attendance).filter(
                Attendance.student_id == student_id
            )
            
            if course_id:
                query = query.join(
                    TeachingSession,
                    Attendance.session_id == TeachingSession.id
                ).filter(
                    TeachingSession.course_id == course_id
                )
            
            total = query.count()
            records = query.order_by(
                Attendance.timestamp.desc()
            ).offset(
                (page - 1) * per_page
            ).limit(per_page).all()
            
            return custom_response(
                success=True,
                data={
                    'records': [{
                        'id': record.id,
                        'session_id': record.session_id,
                        'session_title': record.session.title,
                        'course_code': record.session.course.code,
                        'course_title': record.session.course.title,
                        'date': record.session.start_time.date().isoformat(),
                        'status': record.status.value,
                        'timestamp': record.timestamp.isoformat(),
                        'late_minutes': record.late_minutes,
                        'is_disputed': record.is_disputed,
                        'verification_method': record.verification_method.value if record.verification_method else None,
                        'tutor_name': f"{record.tutor.first_name} {record.tutor.last_name}" if record.tutor else None
                    } for record in records],
                    'total': total,
                    'page': page,
                    'per_page': per_page
                }
            )

    def record_student_attendance(self, student_id: str, daily_session_id: str, status: str, notes: str = None, verification_data: Dict = None) -> Dict:
        """
        Allow students to record their own attendance for ongoing sessions with security measures
        
        Args:
            student_id: ID of the student recording attendance
            daily_session_id: ID of the daily session
            status: Attendance status (present, late, absent)
            notes: Optional notes
            verification_data: Security verification data (device info, location, etc.)
            
        Returns:
            Response with attendance record or error
        """
        from datetime import datetime, timedelta
        from flask import request
        import hashlib
        import json
        
        with DatabaseContextManager() as ctx:
            try:
                # 1. Verify the daily session exists and is ongoing
                daily_session = ctx.session.query(DailyTeachingSession).filter(
                    DailyTeachingSession.id == daily_session_id
                ).first()
                
                if not daily_session:
                    return custom_response(
                        success=False,
                        data="Session not found",
                        status_code=404
                    )
                
                # 2. Security Check: Verify session is ongoing (within time window)
                now = datetime.utcnow()
                session_date = datetime.combine(daily_session.session_date, daily_session.start_time)
                session_end = datetime.combine(daily_session.session_date, daily_session.end_time)
                
                # Allow attendance recording 15 minutes before start time until 30 minutes after end time
                recording_start = session_date - timedelta(minutes=15)
                recording_end = session_end + timedelta(minutes=30)
                
                if not (recording_start <= now <= recording_end):
                    return custom_response(
                        success=False,
                        data="Attendance recording is not allowed at this time. Recording window: 15 minutes before session start to 30 minutes after session end.",
                        status_code=400
                    )
                
                # 3. Verify student is enrolled in the course
                enrollment = ctx.session.query(Enrollment).filter(
                    Enrollment.course_id == daily_session.course_id,
                    Enrollment.student_id == student_id,
                    Enrollment.status == 'active'
                ).first()
                
                if not enrollment:
                    return custom_response(
                        success=False,
                        data="You are not enrolled in this course",
                        status_code=403
                    )
                
                # 4. Check if attendance already exists
                existing_attendance = ctx.session.query(Attendance).filter(
                    Attendance.daily_session_id == daily_session_id,
                    Attendance.student_id == student_id
                ).first()
                
                if existing_attendance:
                    return custom_response(
                        success=False,
                        data="Attendance has already been recorded for this session",
                        status_code=400
                    )
                
                # 5. Security measures
                device_info = verification_data.get('device_info', {}) if verification_data else {}
                ip_address = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', ''))
                user_agent = request.environ.get('HTTP_USER_AGENT', '')
                
                # Calculate late minutes if status is late
                late_minutes = 0
                if status == 'late':
                    session_start = datetime.combine(daily_session.session_date, daily_session.start_time)
                    late_minutes = max(0, int((now - session_start).total_seconds() / 60))
                
                # 6. Create attendance record with security data
                attendance_record = Attendance(
                    id=str(uuid.uuid4()),
                    daily_session_id=daily_session_id,
                    student_id=student_id,
                    tutor_id=daily_session.tutor_id,
                    status=AttendanceStatus.present if status == 'present' else 
                           AttendanceStatus.late if status == 'late' else 
                           AttendanceStatus.absent,
                    notes=notes,
                    late_minutes=late_minutes,
                    timestamp=now,
                    verification_method=TeachingVerificationMethod.student_self_record,
                    device_used=user_agent[:255],  # Limit length
                    ip_address=ip_address[:45],  # Limit length for IPv6
                    created_at=now,
                    updated_at=now
                )
                
                # Add security hash for verification
                security_hash = hashlib.sha256(
                    f"{student_id}{daily_session_id}{now.isoformat()}{ip_address}{user_agent}".encode()
                ).hexdigest()
                
                ctx.session.add(attendance_record)
                ctx.session.commit()
                
                # 7. Update session attendance taken status if this is the first attendance record
                attendance_count = ctx.session.query(Attendance).filter(
                    Attendance.daily_session_id == daily_session_id
                ).count()
                
                if attendance_count == 1:
                    daily_session.attendance_taken = True
                    daily_session.updated_at = now
                    ctx.session.commit()
                
                # 8. Log the attendance recording for audit purposes
                current_app.logger.info(f"Student {student_id} recorded attendance for session {daily_session_id} with status {status}. Security hash: {security_hash}")
                
                return custom_response(
                    success=True,
                    data={
                        'attendance_id': attendance_record.id,
                        'status': attendance_record.status.value,
                        'timestamp': attendance_record.timestamp.isoformat(),
                        'late_minutes': attendance_record.late_minutes,
                        'security_hash': security_hash,
                        'message': 'Attendance recorded successfully'
                    },
                    status_code=201
                )
                
            except Exception as e:
                current_app.logger.error(f"Error recording student attendance: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to record attendance: {str(e)}",
                    status_code=500
                )

    def get_student_analytics(self, student_id: str, period: str = 'current') -> Dict:
        """
        Get comprehensive analytics for a student
        
        Args:
            student_id: ID of the student
            period: Time period for analytics (current, 30d, 90d, 1y)
            
        Returns:
            Response with comprehensive analytics data
        """
        from datetime import datetime, timedelta, date
        import calendar
        
        with DatabaseContextManager() as ctx:
            try:
                # Verify student exists
                student = ctx.session.query(User).filter(
                    User.id == student_id,
                    User.user_type == 'student'
                ).first()
                
                if not student:
                    return custom_response(
                        success=False,
                        data="Student not found",
                        status_code=404
                    )
                
                # Calculate date range based on period
                end_date = date.today()
                if period == '30d':
                    start_date = end_date - timedelta(days=30)
                elif period == '90d':
                    start_date = end_date - timedelta(days=90)
                elif period == '1y':
                    start_date = end_date - timedelta(days=365)
                else:  # current semester/period
                    start_date = end_date - timedelta(days=90)  # Default to 90 days
                
                # Get all daily sessions for the student through enrollment courses
                daily_sessions = ctx.session.query(DailyTeachingSession).join(
                    enrollment_courses, DailyTeachingSession.course_id == enrollment_courses.c.course_id
                ).join(
                    Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
                ).filter(
                    Enrollment.student_id == student_id,
                    Enrollment.status == 'active',
                    enrollment_courses.c.status == 'active',
                    DailyTeachingSession.session_date >= start_date,
                    DailyTeachingSession.session_date <= end_date
                ).all()
                
                # Get attendance records
                attendance_records = ctx.session.query(Attendance).filter(
                    Attendance.student_id == student_id,
                    Attendance.timestamp >= datetime.combine(start_date, datetime.min.time()),
                    Attendance.timestamp <= datetime.combine(end_date, datetime.max.time())
                ).all()
                
                # Calculate basic metrics
                total_sessions = len(daily_sessions)
                attended_sessions = len([a for a in attendance_records if a.status.value == 'present'])
                late_sessions = len([a for a in attendance_records if a.status.value == 'late'])
                missed_sessions = total_sessions - attended_sessions - late_sessions
                attendance_rate = (attended_sessions + late_sessions) / total_sessions * 100 if total_sessions > 0 else 0
                
                # Get upcoming and completed sessions
                today = date.today()
                upcoming_sessions = len([s for s in daily_sessions if s.session_date > today])
                completed_sessions = len([s for s in daily_sessions if s.session_date <= today])
                
                # Calculate course progress (simplified)
                course_progress = min(100, (completed_sessions / total_sessions * 100) if total_sessions > 0 else 0)
                
                # Generate weekly attendance data
                weekly_attendance = []
                current_date = start_date
                week_num = 1
                
                while current_date <= end_date:
                    week_end = min(current_date + timedelta(days=6), end_date)
                    week_sessions = [s for s in daily_sessions if current_date <= s.session_date <= week_end]
                    week_attendance_records = [a for a in attendance_records 
                                             if current_date <= a.timestamp.date() <= week_end]
                    
                    if week_sessions:
                        week_attended = len([a for a in week_attendance_records if a.status.value in ['present', 'late']])
                        week_rate = (week_attended / len(week_sessions) * 100) if week_sessions else 0
                        weekly_attendance.append({
                            'week': f'Week {week_num}',
                            'attendance_rate': round(week_rate, 1),
                            'sessions': len(week_sessions)
                        })
                    
                    current_date += timedelta(days=7)
                    week_num += 1
                
                # Generate course performance data
                course_performance = []
                courses = ctx.session.query(Course).join(
                    enrollment_courses, Course.id == enrollment_courses.c.course_id
                ).join(
                    Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
                ).filter(
                    Enrollment.student_id == student_id,
                    Enrollment.status == 'active',
                    enrollment_courses.c.status == 'active'
                ).all()
                
                for course in courses:
                    course_sessions = [s for s in daily_sessions if s.course_id == course.id]
                    course_attendance = [a for a in attendance_records 
                                       if any(s.course_id == course.id for s in daily_sessions 
                                             if s.id == a.daily_session_id)]
                    course_attended = len([a for a in course_attendance if a.status.value in ['present', 'late']])
                    course_rate = (course_attended / len(course_sessions) * 100) if course_sessions else 0
                    
                    course_performance.append({
                        'course_code': course.code,
                        'course_title': course.title,
                        'attendance_rate': round(course_rate, 1),
                        'sessions_attended': course_attended,
                        'total_sessions': len(course_sessions)
                    })
                
                # Generate monthly trends
                monthly_trends = []
                current_month = start_date.replace(day=1)
                
                while current_month <= end_date:
                    month_end = (current_month + timedelta(days=32)).replace(day=1) - timedelta(days=1)
                    month_end = min(month_end, end_date)
                    
                    month_sessions = [s for s in daily_sessions if current_month <= s.session_date <= month_end]
                    month_attendance = [a for a in attendance_records 
                                      if current_month <= a.timestamp.date() <= month_end]
                    
                    if month_sessions:
                        month_attended = len([a for a in month_attendance if a.status.value in ['present', 'late']])
                        month_rate = (month_attended / len(month_sessions) * 100) if month_sessions else 0
                        monthly_trends.append({
                            'month': current_month.strftime('%b'),
                            'attendance_rate': round(month_rate, 1),
                            'sessions_count': len(month_sessions)
                        })
                    
                    # Move to next month
                    if current_month.month == 12:
                        current_month = current_month.replace(year=current_month.year + 1, month=1)
                    else:
                        current_month = current_month.replace(month=current_month.month + 1)
                
                # Generate attendance patterns (simplified)
                attendance_patterns = {
                    'best_day': 'Tuesday',  # Could be calculated from actual data
                    'worst_day': 'Monday',
                    'best_time': '10:00 AM - 12:00 PM',
                    'worst_time': '2:00 PM - 4:00 PM'
                }
                
                # Generate performance insights
                performance_insights = {
                    'strengths': [
                        'Consistent attendance in core subjects',
                        'Good punctuality record',
                        'Active participation in classes'
                    ],
                    'improvement_areas': [
                        'Late afternoon attendance needs improvement' if late_sessions > 0 else 'Maintain current attendance level',
                        'Focus on challenging subjects',
                        'Better time management for morning classes'
                    ],
                    'recommendations': [
                        'Set calendar reminders for upcoming sessions',
                        'Join study groups for difficult subjects',
                        'Review course materials before each session',
                        'Maintain consistent sleep schedule for morning classes'
                    ]
                }
                
                # Compile analytics data
                analytics_data = {
                    'total_sessions': total_sessions,
                    'attended_sessions': attended_sessions,
                    'missed_sessions': missed_sessions,
                    'late_sessions': late_sessions,
                    'attendance_rate': round(attendance_rate, 1),
                    'upcoming_sessions': upcoming_sessions,
                    'completed_sessions': completed_sessions,
                    'course_progress': round(course_progress, 1),
                    'weekly_attendance': weekly_attendance,
                    'course_performance': course_performance,
                    'monthly_trends': monthly_trends,
                    'attendance_patterns': attendance_patterns,
                    'performance_insights': performance_insights
                }
                
                return custom_response(
                    success=True,
                    data=analytics_data,
                    status_code=200
                )
                
            except Exception as e:
                current_app.logger.error(f"Error generating student analytics: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to generate analytics: {str(e)}",
                    status_code=500
                )

    def dispute_attendance(self, student_id: str, attendance_id: str, reason: str) -> Dict:
        """Dispute an attendance record"""
        with DatabaseContextManager() as ctx:
            attendance = ctx.session.query(Attendance).filter(
                Attendance.id == attendance_id,
                Attendance.student_id == student_id
            ).first()
            
            if not attendance:
                return custom_response(
                    success=False,
                    data="Attendance record not found or not authorized",
                    status_code=404
                )
            
            attendance.is_disputed = True
            attendance.dispute_reason = reason
            attendance.dispute_resolved_by = None
            attendance.dispute_resolution_date = None
            attendance.resolution_notes = None
            
            # Notify tutor and supervisor
            session = attendance.session
            if session.tutor:
                tutor_prefs = ctx.session.query(NotificationPreference).filter(
                    NotificationPreference.user_id == session.tutor.id
                ).first()
                
                if tutor_prefs and tutor_prefs.receive_email:
                    subject = f"Attendance Dispute: {session.title}"
                    message = f"""
                    <html>
                        <body>
                            <h2>Attendance Dispute Notification</h2>
                            <p>Hello {session.tutor.first_name},</p>
                            
                            <p>A student has disputed their attendance record:</p>
                            
                            <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                                <p><strong>Session:</strong> {session.title}</p>
                                <p><strong>Course:</strong> {session.course.code} - {session.course.title}</p>
                                <p><strong>Student:</strong> {attendance.student.first_name} {attendance.student.last_name}</p>
                                <p><strong>Original Status:</strong> {attendance.status.value}</p>
                                <p><strong>Dispute Reason:</strong> {reason}</p>
                            </div>
                            
                            <p>Please review this dispute and take appropriate action.</p>
                            
                            <p>Best regards,<br>
                            {current_app.config['APP_NAME']} Team</p>
                        </body>
                    </html>
                    """
                    
                    try:
                        send_email(
                            sender_email="kisiwa@mutabletech.co.ke",
                            sender_password=current_app.config['MAIL_PASSWORD'],
                            receiver_email=session.tutor.email,
                            subject=subject,
                            message=message
                        )
                    except Exception as e:
                        current_app.logger.error(f"Failed to send dispute notification to tutor: {str(e)}")
            
            if session.course.supervisor:
                supervisor_prefs = ctx.session.query(NotificationPreference).filter(
                    NotificationPreference.user_id == session.course.supervisor.id
                ).first()
                
                if supervisor_prefs and supervisor_prefs.receive_email:
                    subject = f"Attendance Dispute Requires Resolution: {session.title}"
                    message = f"""
                    <html>
                        <body>
                            <h2>Attendance Dispute Notification</h2>
                            <p>Hello {session.course.supervisor.first_name},</p>
                            
                            <p>An attendance record has been disputed and requires your resolution:</p>
                            
                            <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                                <p><strong>Tutor:</strong> {session.tutor.first_name} {session.tutor.last_name}</p>
                                <p><strong>Session:</strong> {session.title}</p>
                                <p><strong>Course:</strong> {session.course.code} - {session.course.title}</p>
                                <p><strong>Student:</strong> {attendance.student.first_name} {attendance.student.last_name}</p>
                                <p><strong>Original Status:</strong> {attendance.status.value}</p>
                                <p><strong>Dispute Reason:</strong> {reason}</p>
                            </div>
                            
                            <div style="text-align: center; margin-top: 20px;">
                                <a href="{current_app.config['FRONTEND_URL']}/resolve-dispute/{attendance.id}" 
                                style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                                    Resolve Dispute
                                </a>
                            </div>
                            
                            <p>Best regards,<br>
                            {current_app.config['APP_NAME']} Team</p>
                        </body>
                    </html>
                    """
                    
                    try:
                        send_email(
                            sender_email="kisiwa@mutabletech.co.ke",
                            sender_password=current_app.config['MAIL_PASSWORD'],
                            receiver_email=session.course.supervisor.email,
                            subject=subject,
                            message=message
                        )
                    except Exception as e:
                        current_app.logger.error(f"Failed to send dispute notification to supervisor: {str(e)}")
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data="Attendance dispute submitted successfully",
                status_code=200
            )

    def get_assignments(self, student_id: str, status: str = None, course_id: str = None) -> Dict:
        """Get assignments for a student with optional filters"""
        with DatabaseContextManager() as ctx:
            # Get assignments through enrollment
            query = ctx.session.query(Assignment).join(
                enrollment_courses, Assignment.course_id == enrollment_courses.c.course_id
            ).join(
                Enrollment, enrollment_courses.c.enrollment_id == Enrollment.id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            )
            
            if status:
                query = query.join(
                    AssignmentSubmission,
                    and_(
                        Assignment.id == AssignmentSubmission.assignment_id,
                        AssignmentSubmission.student_id == student_id
                    ),
                    isouter=True
                ).filter(
                    or_(
                        AssignmentSubmission.status == status,
                        and_(
                            AssignmentSubmission.id == None,
                            status == 'not_started'
                        )
                    )
                )
            
            if course_id:
                query = query.filter(Assignment.course_id == course_id)
            
            assignments = query.order_by(
                Assignment.due_date.asc()
            ).all()
            
            return custom_response(
                success=True,
                data=[{
                    'id': assignment.id,
                    'title': assignment.title,
                    'course_id': assignment.course_id,
                    'course_code': assignment.course.code,
                    'description': assignment.description,
                    'due_date': assignment.due_date.isoformat() if assignment.due_date else None,
                    'total_points': assignment.total_points,
                    'assignment_type': assignment.assignment_type.value,
                    'status': ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id
                    ).first().status.value if ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id
                    ).first() else 'not_started',
                    'grade': ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id,
                        AssignmentSubmission.status == SubmissionStatus.graded
                    ).first().grade if ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id,
                        AssignmentSubmission.status == SubmissionStatus.graded
                    ).first() else None,
                    'submission_date': ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id
                    ).first().submitted_at.isoformat() if ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id
                    ).first() and ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student_id
                    ).first().submitted_at else None
                } for assignment in assignments]
            )

    def submit_assignment(self, student_id: str, assignment_id: str, payload: Dict) -> Dict:
        """Submit an assignment"""
        with DatabaseContextManager() as ctx:
            # Check if assignment exists
            assignment = ctx.session.query(Assignment).filter(
                Assignment.id == assignment_id
            ).first()
            
            if not assignment:
                return custom_response(
                    success=False,
                    data="Assignment not found",
                    status_code=404
                )
            
            # Check if student is enrolled in the course
            enrollment = ctx.session.query(Enrollment).join(
                enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.course_id == assignment.course_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            ).first()
            
            if not enrollment:
                return custom_response(
                    success=False,
                    data="Student is not enrolled in this course",
                    status_code=403
                )
            
            # Check if due date has passed
            if assignment.due_date and assignment.due_date < datetime.utcnow():
                return custom_response(
                    success=False,
                    data="Assignment due date has passed",
                    status_code=400
                )
            
            # Check for existing submission
            existing_submission = ctx.session.query(AssignmentSubmission).filter(
                AssignmentSubmission.assignment_id == assignment_id,
                AssignmentSubmission.student_id == student_id
            ).first()
            
            if existing_submission:
                # Update existing submission
                existing_submission.submitted_at = datetime.utcnow()
                existing_submission.status = SubmissionStatus.submitted
                existing_submission.submission_text = payload.get('submission_text')
                existing_submission.file_path = payload.get('file_path')
                existing_submission.file_size = payload.get('file_size')
                existing_submission.attempt_number += 1
                existing_submission.is_draft = payload.get('is_draft', False)
                
                # Calculate late days if applicable
                if assignment.due_date and datetime.utcnow() > assignment.due_date:
                    existing_submission.late_days = (datetime.utcnow() - assignment.due_date).days
                    existing_submission.status = SubmissionStatus.late
            else:
                # Create new submission
                submission = AssignmentSubmission(
                    id=str(uuid.uuid4()),
                    assignment_id=assignment_id,
                    student_id=student_id,
                    status=SubmissionStatus.submitted,
                    submitted_at=datetime.utcnow(),
                    submission_text=payload.get('submission_text'),
                    file_path=payload.get('file_path'),
                    file_size=payload.get('file_size'),
                    attempt_number=1,
                    is_draft=payload.get('is_draft', False)
                )
                
                # Calculate late days if applicable
                if assignment.due_date and datetime.utcnow() > assignment.due_date:
                    submission.late_days = (datetime.utcnow() - assignment.due_date).days
                    submission.status = SubmissionStatus.late
                
                ctx.session.add(submission)
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data="Assignment submitted successfully",
                status_code=200
            )

    def get_course_progress(self, student_id: str, course_id: str) -> Dict:
        """Get progress for a specific course"""
        with DatabaseContextManager() as ctx:
            # Check enrollment
            enrollment = ctx.session.query(Enrollment).join(
                enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
            ).filter(
                Enrollment.student_id == student_id,
                enrollment_courses.c.course_id == course_id,
                enrollment_courses.c.status == 'active',
                Enrollment.status == 'active'
            ).first()
            
            if not enrollment:
                return custom_response(
                    success=False,
                    data="Student is not enrolled in this course",
                    status_code=404
                )
            
            course = ctx.session.query(Course).filter(Course.id == course_id).first()
            progress = ctx.session.query(CourseProgress).filter(
                CourseProgress.enrollment_id == enrollment.id
            ).first()
            
            if not progress:
                progress = CourseProgress(
                    id=str(uuid.uuid4()),
                    enrollment_id=enrollment.id,
                    student_id=student_id,
                    completion_percentage=0.0
                )
                ctx.session.add(progress)
                ctx.session.commit()
            
            # Get attendance
            attendance = ctx.session.query(
                func.count(Attendance.id),
                func.sum(case((Attendance.status == AttendanceStatus.present, 1), else_=0))
            ).join(
                TeachingSession,
                Attendance.session_id == TeachingSession.id
            ).filter(
                TeachingSession.course_id == course_id,
                Attendance.student_id == student_id
            ).first()
            
            # Get assignments
            assignments = ctx.session.query(
                func.count(AssignmentSubmission.id),
                func.sum(case((AssignmentSubmission.status == SubmissionStatus.graded, 1), else_=0)),
                func.avg(AssignmentSubmission.grade)
            ).join(
                Assignment,
                AssignmentSubmission.assignment_id == Assignment.id
            ).filter(
                Assignment.course_id == course_id,
                AssignmentSubmission.student_id == student_id
            ).first()
            
            return custom_response(
                success=True,
                data={
                    'course_id': course.id,
                    'course_code': course.code,
                    'course_title': course.title,
                    'enrollment_status': enrollment.status,
                    'completion_percentage': progress.completion_percentage,
                    'current_grade': progress.current_grade,
                    'attendance': {
                        'present': attendance[1] if attendance else 0,
                        'total': attendance[0] if attendance else 0,
                        'rate': round((attendance[1] / attendance[0]) * 100, 1) if attendance and attendance[0] else 0
                    },
                    'assignments': {
                        'completed': assignments[1] if assignments else 0,
                        'total': assignments[0] if assignments else 0,
                        'average_grade': round(assignments[2], 1) if assignments and assignments[2] else None
                    },
                    'last_accessed': progress.last_accessed.isoformat() if progress.last_accessed else None
                }
            )

    def send_session_reminders(self) -> Dict:
        """Send reminders for upcoming teaching sessions (to be run periodically)"""
        with DatabaseContextManager() as ctx:
            # Get sessions starting in the next 30 minutes that haven't had reminders sent
            reminder_window_start = datetime.utcnow()
            reminder_window_end = datetime.utcnow() + timedelta(minutes=30)
            
            sessions = ctx.session.query(TeachingSession).filter(
                TeachingSession.start_time >= reminder_window_start,
                TeachingSession.start_time <= reminder_window_end,
                TeachingSession.reminder_sent == False,
                TeachingSession.status == 'scheduled'
            ).all()
            
            results = []
            
            for session in sessions:
                # Get all students enrolled in this session's course
                students = ctx.session.query(Student).join(
                    Enrollment, Student.id == Enrollment.student_id
                ).join(
                    enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
                ).filter(
                    enrollment_courses.c.course_id == session.course_id,
                    enrollment_courses.c.status == 'active',
                    Enrollment.status == 'active'
                ).all()
                
                for student in students:
                    # Get student notification preferences
                    prefs = ctx.session.query(NotificationPreference).filter(
                        NotificationPreference.user_id == student.id
                    ).first()
                    
                    if not prefs or not prefs.receive_email:
                        continue
                    
                    # Send email reminder
                    subject = f"Upcoming Class: {session.title}"
                    message = f"""
                    <html>
                        <body>
                            <h2>Class Reminder</h2>
                            <p>Hello {student.first_name},</p>
                            
                            <p>You have an upcoming class:</p>
                            
                            <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                                <p><strong>Course:</strong> {session.course.code} - {session.course.title}</p>
                                <p><strong>Session:</strong> {session.title}</p>
                                <p><strong>Time:</strong> {session.start_time.strftime('%A, %B %d at %H:%M')}</p>
                                <p><strong>Duration:</strong> {(session.end_time - session.start_time).seconds // 60} minutes</p>
                                <p><strong>Location:</strong> {session.location}</p>
                                <p><strong>Tutor:</strong> {session.tutor.first_name} {session.tutor.last_name}</p>
                            </div>
                            
                            <p>Please ensure you arrive on time with any required materials.</p>
                            
                            <p>Best regards,<br>
                            {current_app.config['APP_NAME']} Team</p>
                        </body>
                    </html>
                    """
                    
                    try:
                        send_email(
                            sender_email="kisiwa@mutabletech.co.ke",
                            sender_password=current_app.config['MAIL_PASSWORD'],
                            receiver_email=student.email,
                            subject=subject,
                            message=message
                        )
                        
                        results.append({
                            'session_id': session.id,
                            'student_id': student.id,
                            'status': 'success'
                        })
                    except Exception as e:
                        current_app.logger.error(f"Failed to send reminder to student {student.id}: {str(e)}")
                        results.append({
                            'session_id': session.id,
                            'student_id': student.id,
                            'status': 'failed',
                            'error': str(e)
                        })
                
                # Mark reminder as sent for this session
                session.reminder_sent = True
            
            ctx.session.commit()
            
            return custom_response(
                success=True,
                data={
                    'reminders_sent': len([r for r in results if r['status'] == 'success']),
                    'reminders_failed': len([r for r in results if r['status'] == 'failed']),
                    'details': results
                }
            )

    def send_assignment_reminders(self) -> Dict:
        """Send reminders for upcoming assignments (to be run periodically)"""
        with DatabaseContextManager() as ctx:
            # Get assignments due in the next 24 hours that haven't been submitted
            reminder_window_start = datetime.utcnow()
            reminder_window_end = datetime.utcnow() + timedelta(hours=24)
            
            assignments = ctx.session.query(Assignment).filter(
                Assignment.due_date >= reminder_window_start,
                Assignment.due_date <= reminder_window_end,
                Assignment.is_published == True
            ).all()
            
            results = []
            
            for assignment in assignments:
                # Get all students enrolled in this assignment's course
                students = ctx.session.query(Student).join(
                    Enrollment, Student.id == Enrollment.student_id
                ).join(
                    enrollment_courses, Enrollment.id == enrollment_courses.c.enrollment_id
                ).filter(
                    enrollment_courses.c.course_id == assignment.course_id,
                    enrollment_courses.c.status == 'active',
                    Enrollment.status == 'active'
                ).all()
                
                for student in students:
                    # Check if student has already submitted
                    submission = ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment.id,
                        AssignmentSubmission.student_id == student.id,
                        AssignmentSubmission.status.in_([SubmissionStatus.submitted, SubmissionStatus.graded])
                    ).first()
                    
                    if submission:
                        continue  # Skip if already submitted
                    
                    # Get student notification preferences
                    prefs = ctx.session.query(NotificationPreference).filter(
                        NotificationPreference.user_id == student.id
                    ).first()
                    
                    if not prefs or not prefs.receive_email:
                        continue
                    
                    # Send email reminder
                    subject = f"Upcoming Assignment: {assignment.title}"
                    message = f"""
                    <html>
                        <body>
                            <h2>Assignment Reminder</h2>
                            <p>Hello {student.first_name},</p>
                            
                            <p>You have an upcoming assignment due soon:</p>
                            
                            <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                                <p><strong>Course:</strong> {assignment.course.code} - {assignment.course.title}</p>
                                <p><strong>Assignment:</strong> {assignment.title}</p>
                                <p><strong>Due Date:</strong> {assignment.due_date.strftime('%A, %B %d at %H:%M')}</p>
                                <p><strong>Points:</strong> {assignment.total_points}</p>
                                <p><strong>Type:</strong> {assignment.assignment_type.value}</p>
                            </div>
                            
                            <div style="text-align: center; margin-top: 20px;">
                                <a href="{current_app.config['FRONTEND_URL']}/assignments/{assignment.id}" 
                                style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                                    View Assignment
                                </a>
                            </div>
                            
                            <p>Best regards,<br>
                            {current_app.config['APP_NAME']} Team</p>
                        </body>
                    </html>
                    """
                    
                    try:
                        send_email(
                            sender_email="kisiwa@mutabletech.co.ke",
                            sender_password=current_app.config['MAIL_PASSWORD'],
                            receiver_email=student.email,
                            subject=subject,
                            message=message
                        )
                        
                        results.append({
                            'assignment_id': assignment.id,
                            'student_id': student.id,
                            'status': 'success'
                        })
                    except Exception as e:
                        current_app.logger.error(f"Failed to send assignment reminder to student {student.id}: {str(e)}")
                        results.append({
                            'assignment_id': assignment.id,
                            'student_id': student.id,
                            'status': 'failed',
                            'error': str(e)
                        })
            
            return custom_response(
                success=True,
                data={
                    'reminders_sent': len([r for r in results if r['status'] == 'success']),
                    'reminders_failed': len([r for r in results if r['status'] == 'failed']),
                    'details': results
                }
            )

    def fetchAll(self, page: int = 1, per_page: int = 10, filters: Dict = None) -> Dict:
        """Fetch all students with pagination and optional filters"""
        with DatabaseContextManager() as ctx:
            query = ctx.session.query(Student).filter(Student.is_active == True)
            
            # Apply filters
            if filters:
                if 'program' in filters:
                    query = query.filter(Student.program == filters['program'])
                if 'year_of_study' in filters:
                    query = query.filter(Student.year_of_study == filters['year_of_study'])
                if 'is_on_probation' in filters:
                    query = query.filter(Student.is_on_probation == filters['is_on_probation'])
                if 'search' in filters:
                    search = f"%{filters['search']}%"
                    query = query.filter(
                        or_(
                            Student.first_name.ilike(search),
                            Student.last_name.ilike(search),
                            Student.student_id.ilike(search),
                            User.email.ilike(search)
                        )
                    )
            
            total = query.count()
            students = query.offset((page - 1) * per_page).limit(per_page).all()
            
            return custom_response(
                success=True,
                data={
                    'students': [self._student_to_dict(student) for student in students],
                    'total': total,
                    'page': page,
                    'per_page': per_page
                }
            )

    def get_student_specialities(self, student_id: str) -> Dict:
        """
        Get specialities for a student
        
        Args:
            student_id: The ID of the student
            
        Returns:
            Response with list of specialities for the student
        """
        with DatabaseContextManager() as ctx:
            try:
                # Get the student
                student = ctx.session.query(Student).filter(Student.id == student_id).first()
                
                if not student:
                    return custom_response(
                        success=False,
                        data="Student not found",
                        status_code=404
                    )
                
                # Get the student's speciality
                speciality = ctx.session.query(Speciality).filter(
                    Speciality.id == student.speciality_id
                ).first()
                
                if not speciality:
                    return custom_response(
                        success=False,
                        data="Student has no speciality assigned",
                        status_code=400
                    )
                
                # Format speciality data
                speciality_data = {
                    'id': speciality.id,
                    'name': speciality.name,
                    'description': speciality.description,
                    'department': speciality.department,
                    'code': speciality.code,
                    'abbreviation': speciality.abbreviation,
                    'is_active': speciality.is_active,
                    'created_at': speciality.created_at.isoformat() if speciality.created_at else None,
                    'updated_at': speciality.updated_at.isoformat() if speciality.updated_at else None
                }
                
                return custom_response(
                    success=True,
                    data=[speciality_data],  # Return as array for consistency with frontend
                    status_code=200
                )
                
            except Exception as e:
                current_app.logger.error(f"Error fetching student specialities: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Error fetching student specialities: {str(e)}",
                    status_code=500
                )

    def get_all_students_progress(self) -> Dict:
        """
        Get progress data for all students with their assignments and performance metrics
        
        Returns:
            Response with comprehensive progress data for all students
        """
        from datetime import datetime, timedelta, date
        
        with DatabaseContextManager() as ctx:
            try:
                # Get all students
                students = ctx.session.query(User).filter(
                    User.user_type == 'student'
                ).all()
                
                if not students:
                    return custom_response(
                        success=True,
                        data={
                            'students': [],
                            'total_students': 0,
                            'message': 'No students found in the database'
                        },
                        status_code=200
                    )
                
                students_data = []
                
                for student in students:
                    # Get student's enrollments
                    enrollments = ctx.session.query(Enrollment).filter(
                        Enrollment.student_id == student.id
                    ).all()
                    
                    # Get student's courses through enrollments
                    courses = []
                    for enrollment in enrollments:
                        # Import the enrollment_courses table
                        from src.models.models import enrollment_courses
                        
                        ec_records = ctx.session.query(enrollment_courses).filter(
                            enrollment_courses.c.enrollment_id == enrollment.id,
                            enrollment_courses.c.status == 'active'
                        ).all()
                        
                        for ec in ec_records:
                            course = ctx.session.query(Course).filter(Course.id == ec.course_id).first()
                            if course:
                                courses.append({
                                    'id': course.id,
                                    'code': course.code,
                                    'name': course.title,
                                    'credits': course.credits,
                                    'progress': 0,  # Default progress
                                    'grade': ec.grade   # Use actual grade from enrollment_courses
                                })
                    
                    # Get attendance records for this student
                    attendance_records = ctx.session.query(Attendance).filter(
                        Attendance.student_id == student.id
                    ).all()
                    
                    # Calculate attendance percentage
                    total_sessions = len(attendance_records)
                    attended_sessions = len([a for a in attendance_records if a.status.value in ['present', 'late']])
                    attendance_percentage = (attended_sessions / total_sessions * 100) if total_sessions > 0 else 0
                    
                    # Get assignments for this student (simplified)
                    assignments = []  # This would need proper assignment fetching logic
                    
                    # Calculate overall progress (simplified)
                    overall_progress = min(100, attendance_percentage * 0.7 + (len(courses) * 10))
                    
                    # Determine performance level
                    performance = 'exceeding' if overall_progress > 85 else 'meeting' if overall_progress > 70 else 'needs-improvement'
                    
                    student_data = {
                        'id': student.id,
                        'name': f"{student.first_name} {student.last_name}",
                        'email': student.email,
                        'progress': round(overall_progress),
                        'gpa': '3.5',  # Default GPA
                        'attendance': f"{round(attendance_percentage)}%",
                        'performance': performance,
                        'lastActive': '2 days ago',  # Default
                        'engagement': round(attendance_percentage),
                        'totalCredits': sum(course.get('credits', 0) for course in courses),
                        'messages': 0,  # Default
                        'courses': courses,
                        'assignments': assignments,
                        'strengths': [
                            'Good attendance record',
                            'Active participation'
                        ] if attendance_percentage > 80 else [],
                        'improvements': [
                            'Improve attendance',
                            'Increase engagement'
                        ] if attendance_percentage < 70 else []
                    }
                    
                    students_data.append(student_data)
                
                return custom_response(
                    success=True,
                    data={
                        'students': students_data,
                        'total_students': len(students_data)
                    },
                    status_code=200
                )
                
            except Exception as e:
                current_app.logger.error(f"Error fetching all students progress: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to fetch students progress: {str(e)}",
                    status_code=500
                )

    def get_assignments_progress(self) -> Dict:
        """
        Get progress data for all assignments
        
        Returns:
            Response with assignment progress data
        """
        with DatabaseContextManager() as ctx:
            try:
                # For now, return mock data since we don't have a proper assignments table
                # This would need to be implemented based on your actual assignment structure
                assignments_data = [
                    {
                        'id': '1',
                        'title': 'Database Design Project',
                        'dueDate': '2024-01-15',
                        'submissions': 0,
                        'averageScore': None,
                        'status': 'open'
                    },
                    {
                        'id': '2', 
                        'title': 'Web Development Assignment',
                        'dueDate': '2024-01-20',
                        'submissions': 0,
                        'averageScore': None,
                        'status': 'open'
                    },
                    {
                        'id': '3',
                        'title': 'Software Engineering Project',
                        'dueDate': '2024-01-25',
                        'submissions': 0,
                        'averageScore': None,
                        'status': 'open'
                    }
                ]
                
                return custom_response(
                    success=True,
                    data={
                        'assignments': assignments_data,
                        'total_assignments': len(assignments_data)
                    },
                    status_code=200
                )
                
            except Exception as e:
                current_app.logger.error(f"Error fetching assignments progress: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to fetch assignments progress: {str(e)}",
                    status_code=500
                )

    def delete(self, id):
        return super().delete(id)