from datetime import datetime, timedelta
from sqlalchemy import func, and_, or_, case
from src.models import DatabaseContextManager
from src.models.models import (
    Course, Assignment, AssignmentSubmission, Student, Tutor,
    CourseModule, AssignmentResource, FeedbackResponse,
    SubmissionStatus, NotificationPreference, 
    tutor_course_association, Enrollment
)
from flask import current_app
from src.utils import (
    ApiABC,
    custom_response,
    send_email
)
from typing import Dict, List, Optional, Any
import uuid
import json

class AssignmentManager(ApiABC):
    def __init__(self):
        self.table = Assignment

    def _assignment_to_dict(self, assignment: Assignment, include_details: bool = False) -> Dict:
        """Convert Assignment model to dictionary with optional detailed information"""
        base_data = {
            'id': assignment.id,
            'course_id': assignment.course_id,
            'module_id': assignment.module_id,
            'title': assignment.title,
            'description': assignment.description,
            'assignment_type': assignment.assignment_type.value,
            'total_points': assignment.total_points,
            'due_date': assignment.due_date.isoformat() if assignment.due_date else None,
            'created_at': assignment.created_at.isoformat(),
            'is_published': assignment.is_published,
            'time_limit': assignment.time_limit,
            'max_attempts': assignment.max_attempts,
            'late_submission_penalty': assignment.late_submission_penalty,
            'submission_count': len(assignment.submissions),
            'average_grade': self._calculate_average_grade(assignment.submissions),
            'resources_count': len(assignment.resources)
        }

        if include_details:
            base_data.update({
                'submission_instructions': assignment.submission_instructions,
                'allowed_formats': assignment.allowed_formats,
                'is_group_assignment': assignment.is_group_assignment,
                'plagiarism_check_enabled': assignment.plagiarism_check_enabled,
                'feedback_type': assignment.feedback_type,
                'resources': [self._assignment_resource_to_dict(res) for res in assignment.resources],
                'module_title': assignment.module.title if assignment.module else None,
                'course_title': assignment.course.title,
                'course_code': assignment.course.code,
                'creator_name': f"{assignment.creator.first_name} {assignment.creator.last_name}" if assignment.creator else None
            })

        return base_data

    def _assignment_resource_to_dict(self, resource: AssignmentResource) -> Dict:
        """Convert AssignmentResource model to dictionary"""
        return {
            'id': resource.id,
            'title': resource.title,
            'description': resource.description,
            'resource_type': resource.resource_type.value,
            'url': resource.url,
            'file_path': resource.file_path,
            'is_required': resource.is_required,
            'sample_solution': resource.sample_solution,
            'grading_criteria': resource.grading_criteria
        }

    def _submission_to_dict(self, submission: AssignmentSubmission, include_feedback: bool = False) -> Dict:
        """Convert AssignmentSubmission model to dictionary"""
        base_data = {
            'id': submission.id,
            'assignment_id': submission.assignment_id,
            'student_id': submission.student_id,
            'student_name': f"{submission.student.first_name} {submission.student.last_name}",
            'student_id_number': submission.student.student_id,
            'status': submission.status.value,
            'submitted_at': submission.submitted_at.isoformat() if submission.submitted_at else None,
            'grade': submission.grade,
            'attempt_number': submission.attempt_number,
            'late_days': submission.late_days,
            'similarity_score': submission.similarity_score,
            'is_draft': submission.is_draft,
            'assignment_title': submission.assignment.title,
            'total_points': submission.assignment.total_points,
            'grade_percentage': round((submission.grade / submission.assignment.total_points) * 100, 1) 
                              if submission.grade and submission.assignment.total_points else None
        }

        if include_feedback:
            base_data.update({
                'feedback': submission.feedback,
                'graded_at': submission.graded_at.isoformat() if submission.graded_at else None,
                'grader_name': f"{submission.grader.first_name} {submission.grader.last_name}" if submission.grader else None,
                'submission_text': submission.submission_text,
                'file_path': submission.file_path,
                'feedback_responses': [
                    self._feedback_response_to_dict(resp) 
                    for resp in submission.feedback_responses
                ] if submission.feedback_responses else []
            })

        return base_data

    def _feedback_response_to_dict(self, response: FeedbackResponse) -> Dict:
        """Convert FeedbackResponse model to dictionary"""
        return {
            'id': response.id,
            'response_text': response.response_text,
            'created_at': response.created_at.isoformat(),
            'is_read': response.is_read,
            'read_at': response.read_at.isoformat() if response.read_at else None,
            'tutor_response': response.tutor_response,
            'tutor_response_at': response.tutor_response_at.isoformat() if response.tutor_response_at else None,
            'is_closed': response.is_closed
        }

    def _calculate_average_grade(self, submissions: List[AssignmentSubmission]) -> Optional[float]:
        """Calculate average grade from submissions"""
        graded_submissions = [s for s in submissions if s.status == SubmissionStatus.graded and s.grade is not None]
        if not graded_submissions:
            return None
        return round(sum(s.grade for s in graded_submissions) / len(graded_submissions), 1)

    def create_assignment(self, payload: Dict) -> Dict:
        """Create a new assignment"""
        with DatabaseContextManager() as ctx:
            try:
                # Validate course exists
                course = ctx.session.query(Course).filter(
                    Course.id == payload['course_id'],
                    Course.is_active == True
                ).first()
                
                if not course:
                    return custom_response(
                        success=False,
                        data="Course not found or inactive",
                        status_code=404
                    )

                # Validate module if provided
                if payload.get('module_id'):
                    module = ctx.session.query(CourseModule).filter(
                        CourseModule.id == payload['module_id'],
                        CourseModule.course_id == payload['course_id']
                    ).first()
                    
                    if not module:
                        return custom_response(
                            success=False,
                            data="Module not found or doesn't belong to this course",
                            status_code=404
                        )

                # Create assignment
                assignment = Assignment(
                    id=str(uuid.uuid4()),
                    course_id=payload['course_id'],
                    module_id=payload.get('module_id'),
                    title=payload['title'],
                    description=payload.get('description', ''),
                    assignment_type=payload['assignment_type'],
                    total_points=payload['total_points'],
                    due_date=datetime.fromisoformat(payload['due_date']) if payload.get('due_date') else None,
                    created_by=payload['creator_id'],
                    submission_instructions=payload.get('submission_instructions'),
                    allowed_formats=payload.get('allowed_formats'),
                    max_attempts=payload.get('max_attempts', 1),
                    time_limit=payload.get('time_limit'),
                    is_group_assignment=payload.get('is_group_assignment', False),
                    plagiarism_check_enabled=payload.get('plagiarism_check_enabled', True),
                    late_submission_penalty=payload.get('late_submission_penalty', 0.0),
                    feedback_type=payload.get('feedback_type', 'comments'),
                    is_published=payload.get('is_published', False)
                )
                
                ctx.session.add(assignment)
                
                # Add resources if provided
                if payload.get('resources'):
                    for resource_data in payload['resources']:
                        resource = AssignmentResource(
                            id=str(uuid.uuid4()),
                            assignment_id=assignment.id,
                            title=resource_data['title'],
                            description=resource_data.get('description'),
                            resource_type=resource_data['resource_type'],
                            url=resource_data.get('url'),
                            file_path=resource_data.get('file_path'),
                            is_required=resource_data.get('is_required', True),
                            sample_solution=resource_data.get('sample_solution', False),
                            grading_criteria=resource_data.get('grading_criteria')
                        )
                        ctx.session.add(resource)
                
                ctx.session.commit()
                
                # Notify students if published
                if assignment.is_published:
                    self._notify_students_new_assignment(ctx, assignment)
                
                return custom_response(
                    success=True,
                    data=self._assignment_to_dict(assignment, include_details=True),
                    status_code=201
                )
                
            except Exception as e:
                ctx.session.rollback()
                current_app.logger.error(f"Failed to create assignment: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to create assignment: {str(e)}",
                    status_code=500
                )

    def _notify_students_new_assignment(self, ctx, assignment: Assignment) -> None:
        """Notify students about a new assignment"""
        # Get students through speciality relationship
        course = ctx.session.query(Course).filter(Course.id == assignment.course_id).first()
        if not course:
            return []
            
        students = ctx.session.query(Student).filter(
            Student.speciality_id == course.speciality_id,
            Student.is_active == True
        ).all()
        
        for student in students:
            prefs = ctx.session.query(NotificationPreference).filter(
                NotificationPreference.user_id == student.id
            ).first()
            
            if prefs and prefs.receive_email:
                subject = f"New Assignment: {assignment.title}"
                message = f"""
                <html>
                    <body>
                        <h2>New Assignment Available</h2>
                        <p>Hello {student.first_name},</p>
                        
                        <p>A new assignment has been posted for {assignment.course.code}:</p>
                        
                        <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                            <p><strong>Title:</strong> {assignment.title}</p>
                            <p><strong>Course:</strong> {assignment.course.code} - {assignment.course.title}</p>
                            <p><strong>Due Date:</strong> {assignment.due_date.strftime('%A, %B %d, %Y at %H:%M') if assignment.due_date else 'No due date'}</p>
                            <p><strong>Points:</strong> {assignment.total_points}</p>
                            <p><strong>Type:</strong> {assignment.assignment_type.value.replace('_', ' ').title()}</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
                    )
                except Exception as e:
                    current_app.logger.error(f"Failed to send assignment notification to student {student.id}: {str(e)}")

    def update_assignment(self, assignment_id: str, payload: Dict) -> Dict:
        """Update an existing assignment"""
        with DatabaseContextManager() as ctx:
            try:
                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
                    )
                
                was_published = assignment.is_published
                
                # Update basic fields
                if 'title' in payload:
                    assignment.title = payload['title']
                if 'description' in payload:
                    assignment.description = payload['description']
                if 'assignment_type' in payload:
                    assignment.assignment_type = payload['assignment_type']
                if 'total_points' in payload:
                    assignment.total_points = payload['total_points']
                if 'due_date' in payload:
                    assignment.due_date = datetime.fromisoformat(payload['due_date']) if payload['due_date'] else None
                if 'submission_instructions' in payload:
                    assignment.submission_instructions = payload['submission_instructions']
                if 'allowed_formats' in payload:
                    assignment.allowed_formats = payload['allowed_formats']
                if 'max_attempts' in payload:
                    assignment.max_attempts = payload['max_attempts']
                if 'time_limit' in payload:
                    assignment.time_limit = payload['time_limit']
                if 'is_group_assignment' in payload:
                    assignment.is_group_assignment = payload['is_group_assignment']
                if 'plagiarism_check_enabled' in payload:
                    assignment.plagiarism_check_enabled = payload['plagiarism_check_enabled']
                if 'late_submission_penalty' in payload:
                    assignment.late_submission_penalty = payload['late_submission_penalty']
                if 'feedback_type' in payload:
                    assignment.feedback_type = payload['feedback_type']
                if 'is_published' in payload:
                    assignment.is_published = payload['is_published']
                
                # Handle resources
                if 'resources' in payload:
                    # Delete existing resources not in the new list
                    existing_resource_ids = {res.id for res in assignment.resources}
                    new_resource_ids = {res['id'] for res in payload['resources'] if 'id' in res}
                    
                    for resource_id in existing_resource_ids - new_resource_ids:
                        ctx.session.query(AssignmentResource).filter(
                            AssignmentResource.id == resource_id
                        ).delete()
                    
                    # Add/update resources
                    for resource_data in payload['resources']:
                        if 'id' in resource_data:
                            # Update existing resource
                            resource = ctx.session.query(AssignmentResource).filter(
                                AssignmentResource.id == resource_data['id']
                            ).first()
                            
                            if resource:
                                if 'title' in resource_data:
                                    resource.title = resource_data['title']
                                if 'description' in resource_data:
                                    resource.description = resource_data['description']
                                if 'resource_type' in resource_data:
                                    resource.resource_type = resource_data['resource_type']
                                if 'url' in resource_data:
                                    resource.url = resource_data['url']
                                if 'file_path' in resource_data:
                                    resource.file_path = resource_data['file_path']
                                if 'is_required' in resource_data:
                                    resource.is_required = resource_data['is_required']
                                if 'sample_solution' in resource_data:
                                    resource.sample_solution = resource_data['sample_solution']
                                if 'grading_criteria' in resource_data:
                                    resource.grading_criteria = resource_data['grading_criteria']
                        else:
                            # Add new resource
                            resource = AssignmentResource(
                                id=str(uuid.uuid4()),
                                assignment_id=assignment.id,
                                title=resource_data['title'],
                                description=resource_data.get('description'),
                                resource_type=resource_data['resource_type'],
                                url=resource_data.get('url'),
                                file_path=resource_data.get('file_path'),
                                is_required=resource_data.get('is_required', True),
                                sample_solution=resource_data.get('sample_solution', False),
                                grading_criteria=resource_data.get('grading_criteria')
                            )
                            ctx.session.add(resource)
                
                ctx.session.commit()
                
                # Notify students if assignment was just published
                if not was_published and assignment.is_published:
                    self._notify_students_new_assignment(ctx, assignment)
                
                return custom_response(
                    success=True,
                    data=self._assignment_to_dict(assignment, include_details=True),
                    status_code=200
                )
                
            except Exception as e:
                ctx.session.rollback()
                current_app.logger.error(f"Failed to update assignment: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to update assignment: {str(e)}",
                    status_code=500
                )

    def get_assignment_details(self, assignment_id: str, include_submissions: bool = False) -> Dict:
        """Get detailed information about an assignment"""
        with DatabaseContextManager() as ctx:
            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
                )
            
            response_data = self._assignment_to_dict(assignment, include_details=True)
            
            if include_submissions:
                response_data['submissions'] = [
                    self._submission_to_dict(sub, include_feedback=True)
                    for sub in assignment.submissions
                ]
            
            return custom_response(
                success=True,
                data=response_data,
                status_code=200
            )

    def get_course_assignments(self, course_id: str = None, include_stats: bool = False, page: int = 1, per_page: int = 10) -> Dict:
        """Get all assignments for a course with pagination"""
        with DatabaseContextManager() as ctx:
            query = ctx.session.query(Assignment)
            
            # Filter by course if specified
            if course_id:
                query = query.filter(Assignment.course_id == course_id)
            
            # Get total count for pagination
            total_count = query.count()
            
            # Apply pagination
            offset = (page - 1) * per_page
            assignments = query.order_by(
                Assignment.due_date.asc()
            ).offset(offset).limit(per_page).all()
            
            response_data = [self._assignment_to_dict(a) for a in assignments]
            
            if include_stats:
                # Add statistics for each assignment
                for assignment in response_data:
                    submissions = ctx.session.query(AssignmentSubmission).filter(
                        AssignmentSubmission.assignment_id == assignment['id']
                    ).all()
                    
                    assignment['submitted_count'] = len([s for s in submissions if s.status != SubmissionStatus.not_started])
                    assignment['graded_count'] = len([s for s in submissions if s.status == SubmissionStatus.graded])
                    assignment['average_grade'] = round(
                        sum(s.grade for s in submissions if s.grade is not None) / len(submissions), 1
                    ) if submissions and any(s.grade for s in submissions) else None
            
            # Calculate pagination info
            total_pages = (total_count + per_page - 1) // per_page
            has_next = page < total_pages
            has_prev = page > 1
            
            return custom_response(
                success=True,
                data={
                    'assignments': response_data,
                    'pagination': {
                        'page': page,
                        'per_page': per_page,
                        'total_count': total_count,
                        'total_pages': total_pages,
                        'has_next': has_next,
                        'has_prev': has_prev
                    }
                },
                status_code=200
            )

    def submit_assignment(self, student_id: str, assignment_id: str, payload: Dict) -> Dict:
        """Submit an assignment (or save as draft)"""
        with DatabaseContextManager() as ctx:
            try:
                # Validate assignment exists and is active
                assignment = ctx.session.query(Assignment).filter(
                    Assignment.id == assignment_id,
                    Assignment.is_published == True
                ).first()
                
                if not assignment:
                    return custom_response(
                        success=False,
                        data="Assignment not found or not published",
                        status_code=404
                    )
                
                # Check if student is enrolled in the course
                enrollment = ctx.session.query(Enrollment).filter(
                    Enrollment.student_id == student_id,
                    Enrollment.course_id == assignment.course_id,
                    Enrollment.status == 'active'
                ).first()
                
                if not enrollment:
                    return custom_response(
                        success=False,
                        data="You are not enrolled in this course",
                        status_code=403
                    )
                
                # Check attempt limit
                current_attempts = ctx.session.query(AssignmentSubmission).filter(
                    AssignmentSubmission.student_id == student_id,
                    AssignmentSubmission.assignment_id == assignment_id
                ).count()
                
                if current_attempts >= assignment.max_attempts:
                    return custom_response(
                        success=False,
                        data=f"Maximum attempts ({assignment.max_attempts}) reached for this assignment",
                        status_code=400
                    )
                
                # Check if due date has passed
                late_days = 0
                if assignment.due_date and datetime.utcnow() > assignment.due_date:
                    late_days = (datetime.utcnow() - assignment.due_date).days
                
                # Create or update submission
                existing_submission = ctx.session.query(AssignmentSubmission).filter(
                    AssignmentSubmission.student_id == student_id,
                    AssignmentSubmission.assignment_id == assignment_id,
                    AssignmentSubmission.attempt_number == current_attempts + 1
                ).first()
                
                if existing_submission:
                    # Update existing draft
                    if 'submission_text' in payload:
                        existing_submission.submission_text = payload['submission_text']
                    if 'file_path' in payload:
                        existing_submission.file_path = payload['file_path']
                    if 'file_size' in payload:
                        existing_submission.file_size = payload['file_size']
                    if 'is_draft' in payload:
                        existing_submission.is_draft = payload['is_draft']
                    
                    if not payload.get('is_draft', True):
                        existing_submission.status = SubmissionStatus.submitted
                        existing_submission.submitted_at = datetime.utcnow()
                        existing_submission.late_days = late_days
                else:
                    # Create new submission
                    submission = AssignmentSubmission(
                        id=str(uuid.uuid4()),
                        assignment_id=assignment_id,
                        student_id=student_id,
                        status=SubmissionStatus.submitted if not payload.get('is_draft', True) else SubmissionStatus.in_progress,
                        submitted_at=datetime.utcnow() if not payload.get('is_draft', True) else None,
                        attempt_number=current_attempts + 1,
                        late_days=late_days if not payload.get('is_draft', True) else 0,
                        submission_text=payload.get('submission_text'),
                        file_path=payload.get('file_path'),
                        file_size=payload.get('file_size'),
                        ip_address=payload.get('ip_address'),
                        is_draft=payload.get('is_draft', True)
                    )
                    ctx.session.add(submission)
                
                ctx.session.commit()
                
                # Notify tutor if not a draft
                if not payload.get('is_draft', True):
                    self._notify_tutor_new_submission(ctx, assignment, student_id)
                
                return custom_response(
                    success=True,
                    data={
                        'message': 'Draft saved successfully' if payload.get('is_draft', True) else 'Assignment submitted successfully',
                        'attempt_number': current_attempts + 1,
                        'late_days': late_days if not payload.get('is_draft', True) else 0
                    },
                    status_code=200
                )
                
            except Exception as e:
                ctx.session.rollback()
                current_app.logger.error(f"Failed to submit assignment: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to submit assignment: {str(e)}",
                    status_code=500
                )

    def _notify_tutor_new_submission(self, ctx, assignment: Assignment, student_id: str) -> None:
        """Notify tutor about a new assignment submission"""
        # Get primary tutor for the course
        tutor = ctx.session.query(Tutor).join(
            tutor_course_association,
            Tutor.id == tutor_course_association.c.tutor_id
        ).filter(
            tutor_course_association.c.course_id == assignment.course_id,
            tutor_course_association.c.is_primary == True
        ).first()
        
        if not tutor:
            return
        
        # Get student info
        student = ctx.session.query(Student).filter(
            Student.id == student_id
        ).first()
        
        if not student:
            return
        
        # Get tutor preferences
        prefs = ctx.session.query(NotificationPreference).filter(
            NotificationPreference.user_id == tutor.id
        ).first()
        
        if prefs and prefs.receive_email:
            subject = f"New Submission: {assignment.title}"
            message = f"""
            <html>
                <body>
                    <h2>New Assignment Submission</h2>
                    <p>Hello {tutor.first_name},</p>
                    
                    <p>A student has submitted an assignment:</p>
                    
                    <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                        <p><strong>Assignment:</strong> {assignment.title}</p>
                        <p><strong>Course:</strong> {assignment.course.code} - {assignment.course.title}</p>
                        <p><strong>Student:</strong> {student.first_name} {student.last_name} ({student.student_id})</p>
                        <p><strong>Submitted At:</strong> {datetime.utcnow().strftime('%A, %B %d, %Y at %H:%M UTC')}</p>
                    </div>
                    
                    <div style="text-align: center; margin-top: 20px;">
                        <a href="{current_app.config['FRONTEND_URL']}/grade-submission/{assignment.id}" 
                        style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                            Grade Submission
                        </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=tutor.email,
                    subject=subject,
                    message=message
                )
            except Exception as e:
                current_app.logger.error(f"Failed to send submission notification to tutor {tutor.id}: {str(e)}")

    def grade_submission(self, submission_id: str, payload: Dict) -> Dict:
        """Grade an assignment submission"""
        with DatabaseContextManager() as ctx:
            try:
                submission = ctx.session.query(AssignmentSubmission).filter(
                    AssignmentSubmission.id == submission_id
                ).first()
                
                if not submission:
                    return custom_response(
                        success=False,
                        data="Submission not found",
                        status_code=404
                    )
                
                # Update submission
                if 'grade' in payload:
                    submission.grade = payload['grade']
                if 'feedback' in payload:
                    submission.feedback = payload['feedback']
                
                submission.status = SubmissionStatus.graded
                submission.graded_at = datetime.utcnow()
                submission.graded_by = payload['grader_id']
                
                ctx.session.commit()
                
                # Notify student
                self._notify_student_graded_submission(ctx, submission)
                
                return custom_response(
                    success=True,
                    data=self._submission_to_dict(submission, include_feedback=True),
                    status_code=200
                )
                
            except Exception as e:
                ctx.session.rollback()
                current_app.logger.error(f"Failed to grade submission: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to grade submission: {str(e)}",
                    status_code=500
                )

    def _notify_student_graded_submission(self, ctx, submission: AssignmentSubmission) -> None:
        """Notify student about graded submission"""
        prefs = ctx.session.query(NotificationPreference).filter(
            NotificationPreference.user_id == submission.student_id
        ).first()
        
        if prefs and prefs.receive_email:
            subject = f"Grade Posted: {submission.assignment.title}"
            message = f"""
            <html>
                <body>
                    <h2>Assignment Graded</h2>
                    <p>Hello {submission.student.first_name},</p>
                    
                    <p>Your assignment has been graded:</p>
                    
                    <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                        <p><strong>Assignment:</strong> {submission.assignment.title}</p>
                        <p><strong>Course:</strong> {submission.assignment.course.code} - {submission.assignment.course.title}</p>
                        <p><strong>Grade:</strong> {submission.grade}/{submission.assignment.total_points} ({round((submission.grade / submission.assignment.total_points) * 100, 1)}%)</p>
                        <p><strong>Feedback:</strong> {submission.feedback or 'No feedback provided'}</p>
                    </div>
                    
                    <div style="text-align: center; margin-top: 20px;">
                        <a href="{current_app.config['FRONTEND_URL']}/submissions/{submission.id}" 
                        style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                            View Submission
                        </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=submission.student.email,
                    subject=subject,
                    message=message
                )
            except Exception as e:
                current_app.logger.error(f"Failed to send grade notification to student {submission.student_id}: {str(e)}")

    def get_student_submissions(self, student_id: str, course_id: Optional[str] = None) -> Dict:
        """Get all submissions for a student, optionally filtered by course"""
        with DatabaseContextManager() as ctx:
            query = ctx.session.query(AssignmentSubmission).filter(
                AssignmentSubmission.student_id == student_id
            ).join(
                Assignment,
                AssignmentSubmission.assignment_id == Assignment.id
            ).order_by(
                Assignment.due_date.desc()
            )
            
            if course_id:
                query = query.filter(Assignment.course_id == course_id)
            
            submissions = query.all()
            
            return custom_response(
                success=True,
                data=[self._submission_to_dict(sub) for sub in submissions],
                status_code=200
            )

    def get_course_submissions(self, course_id: str, graded_only: bool = False) -> Dict:
        """Get all submissions for a course, optionally filtered by graded status"""
        with DatabaseContextManager() as ctx:
            query = ctx.session.query(AssignmentSubmission).join(
                Assignment,
                AssignmentSubmission.assignment_id == Assignment.id
            ).filter(
                Assignment.course_id == course_id
            ).order_by(
                Assignment.due_date.asc(),
                Student.last_name.asc(),
                Student.first_name.asc()
            )
            
            if graded_only:
                query = query.filter(
                    AssignmentSubmission.status == SubmissionStatus.graded
                )
            
            submissions = query.all()
            
            return custom_response(
                success=True,
                data=[self._submission_to_dict(sub, include_feedback=True) for sub in submissions],
                status_code=200
            )

    def add_feedback_response(self, submission_id: str, payload: Dict) -> Dict:
        """Add a response to assignment feedback (from student or tutor)"""
        with DatabaseContextManager() as ctx:
            try:
                submission = ctx.session.query(AssignmentSubmission).filter(
                    AssignmentSubmission.id == submission_id
                ).first()
                
                if not submission:
                    return custom_response(
                        success=False,
                        data="Submission not found",
                        status_code=404
                    )
                
                # Determine if this is a student response or tutor follow-up
                is_tutor_response = payload.get('is_tutor_response', False)
                
                # Create new response
                response = FeedbackResponse(
                    id=str(uuid.uuid4()),
                    submission_id=submission_id,
                    response_text=payload['response_text'],
                    is_read=False if is_tutor_response else True,
                    tutor_response=payload['response_text'] if is_tutor_response else None,
                    tutor_response_at=datetime.utcnow() if is_tutor_response else None,
                    is_closed=payload.get('is_closed', False)
                )
                
                ctx.session.add(response)
                ctx.session.commit()
                
                # Notify the other party
                if is_tutor_response:
                    self._notify_student_feedback_response(ctx, submission, response)
                else:
                    self._notify_tutor_feedback_response(ctx, submission, response)
                
                return custom_response(
                    success=True,
                    data=self._feedback_response_to_dict(response),
                    status_code=201
                )
                
            except Exception as e:
                ctx.session.rollback()
                current_app.logger.error(f"Failed to add feedback response: {str(e)}")
                return custom_response(
                    success=False,
                    data=f"Failed to add feedback response: {str(e)}",
                    status_code=500
                )

    def _notify_student_feedback_response(self, ctx, submission: AssignmentSubmission, response: FeedbackResponse) -> None:
        """Notify student about tutor's feedback response"""
        prefs = ctx.session.query(NotificationPreference).filter(
            NotificationPreference.user_id == submission.student_id
        ).first()
        
        if prefs and prefs.receive_email:
            subject = f"Feedback Update: {submission.assignment.title}"
            message = f"""
            <html>
                <body>
                    <h2>Feedback Response</h2>
                    <p>Hello {submission.student.first_name},</p>
                    
                    <p>Your tutor has responded to your feedback on the assignment:</p>
                    
                    <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                        <p><strong>Assignment:</strong> {submission.assignment.title}</p>
                        <p><strong>Tutor:</strong> {response.submission.grader.first_name} {response.submission.grader.last_name}</p>
                        <p><strong>Response:</strong> {response.tutor_response}</p>
                    </div>
                    
                    <div style="text-align: center; margin-top: 20px;">
                        <a href="{current_app.config['FRONTEND_URL']}/submissions/{submission.id}" 
                        style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                            View Conversation
                        </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=submission.student.email,
                    subject=subject,
                    message=message
                )
            except Exception as e:
                current_app.logger.error(f"Failed to send feedback response notification to student {submission.student_id}: {str(e)}")

    def _notify_tutor_feedback_response(self, ctx, submission: AssignmentSubmission, response: FeedbackResponse) -> None:
        """Notify tutor about student's feedback response"""
        prefs = ctx.session.query(NotificationPreference).filter(
            NotificationPreference.user_id == submission.graded_by
        ).first()
        
        if prefs and prefs.receive_email:
            subject = f"Feedback Received: {submission.assignment.title}"
            message = f"""
            <html>
                <body>
                    <h2>Student Feedback Response</h2>
                    <p>Hello {submission.grader.first_name},</p>
                    
                    <p>A student has responded to your feedback on their assignment:</p>
                    
                    <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                        <p><strong>Assignment:</strong> {submission.assignment.title}</p>
                        <p><strong>Student:</strong> {submission.student.first_name} {submission.student.last_name}</p>
                        <p><strong>Response:</strong> {response.response_text}</p>
                    </div>
                    
                    <div style="text-align: center; margin-top: 20px;">
                        <a href="{current_app.config['FRONTEND_URL']}/grade-submission/{submission.id}" 
                        style="background-color: #3182ce; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
                            Respond to Feedback
                        </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=submission.grader.email,
                    subject=subject,
                    message=message
                )
            except Exception as e:
                current_app.logger.error(f"Failed to send feedback response notification to tutor {submission.graded_by}: {str(e)}")

    def get_upcoming_assignments(self, student_id: str, days_ahead: int = 7) -> Dict:
        """Get upcoming assignments for a student within the specified timeframe"""
        with DatabaseContextManager() as ctx:
            now = datetime.utcnow()
            end_date = now + timedelta(days=days_ahead)
            
            # Get all active enrollments
            enrollments = ctx.session.query(Enrollment).filter(
                Enrollment.student_id == student_id,
                Enrollment.status == 'active'
            ).all()
            
            if not enrollments:
                return custom_response(
                    success=True,
                    data=[],
                    status_code=200
                )
            
            # Get assignments for enrolled courses with due dates in the timeframe
            assignments = ctx.session.query(Assignment).filter(
                Assignment.course_id.in_([e.course_id for e in enrollments]),
                Assignment.is_published == True,
                Assignment.due_date >= now,
                Assignment.due_date <= end_date
            ).order_by(
                Assignment.due_date.asc()
            ).all()
            
            # Check submission status for each assignment
            result = []
            for assignment in assignments:
                submission = ctx.session.query(AssignmentSubmission).filter(
                    AssignmentSubmission.assignment_id == assignment.id,
                    AssignmentSubmission.student_id == student_id
                ).order_by(
                    AssignmentSubmission.attempt_number.desc()
                ).first()
                
                result.append({
                    'assignment': self._assignment_to_dict(assignment),
                    'submission_status': submission.status.value if submission else 'not_started',
                    'submission_grade': submission.grade if submission and submission.grade else None,
                    'days_remaining': (assignment.due_date - now).days if assignment.due_date else None
                })
            
            return custom_response(
                success=True,
                data=result,
                status_code=200
            )

    def get_general_assignment_stats(self, course_id: str = None) -> Dict:
        """Get general assignment statistics for a course or all courses"""
        with DatabaseContextManager() as ctx:
            # Build query for assignments
            query = ctx.session.query(Assignment)
            if course_id:
                query = query.filter(Assignment.course_id == course_id)
            
            assignments = query.all()
            
            if not assignments:
                return custom_response(
                    success=True,
                    data={
                        'total_assignments': 0,
                        'pending_assignments': 0,
                        'submitted_assignments': 0,
                        'graded_assignments': 0,
                        'overdue_assignments': 0,
                        'average_grade': 0
                    },
                    status_code=200
                )
            
            # Calculate general stats
            total_assignments = len(assignments)
            pending_assignments = len([a for a in assignments if not a.is_published])
            published_assignments = len([a for a in assignments if a.is_published])
            
            # Get all submissions for these assignments
            assignment_ids = [a.id for a in assignments]
            submissions = ctx.session.query(AssignmentSubmission).filter(
                AssignmentSubmission.assignment_id.in_(assignment_ids)
            ).all()
            
            submitted_assignments = len(set(s.assignment_id for s in submissions))
            graded_assignments = len([s for s in submissions if s.status == SubmissionStatus.graded])
            
            # Calculate overdue assignments
            now = datetime.utcnow()
            overdue_assignments = len([a for a in assignments if a.due_date and a.due_date < now])
            
            # Calculate average grade
            grades = [s.grade for s in submissions if s.grade is not None]
            average_grade = round(sum(grades) / len(grades), 1) if grades else 0
            
            return custom_response(
                success=True,
                data={
                    'total_assignments': total_assignments,
                    'pending_assignments': pending_assignments,
                    'submitted_assignments': submitted_assignments,
                    'graded_assignments': graded_assignments,
                    'overdue_assignments': overdue_assignments,
                    'average_grade': average_grade
                },
                status_code=200
            )

    def get_assignment_stats(self, assignment_id: str) -> Dict:
        """Get statistics for an assignment"""
        with DatabaseContextManager() as ctx:
            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
                )
            
            # Get all submissions for this assignment
            submissions = ctx.session.query(AssignmentSubmission).filter(
                AssignmentSubmission.assignment_id == assignment_id
            ).all()
            
            # Calculate basic stats
            total_students = ctx.session.query(Enrollment).filter(
                Enrollment.course_id == assignment.course_id,
                Enrollment.status == 'active'
            ).count()
            
            submitted_count = len([s for s in submissions if s.status != SubmissionStatus.not_started])
            graded_count = len([s for s in submissions if s.status == SubmissionStatus.graded])
            
            # Grade distribution
            grades = [s.grade for s in submissions if s.grade is not None]
            average_grade = round(sum(grades) / len(grades), 1) if grades else None
            max_grade = max(grades) if grades else None
            min_grade = min(grades) if grades else None
            
            # Late submissions
            late_count = len([s for s in submissions if s.late_days > 0])
            
            # Similarity scores (for plagiarism detection)
            similarity_scores = [s.similarity_score for s in submissions if s.similarity_score is not None]
            avg_similarity = round(sum(similarity_scores) / len(similarity_scores), 1) if similarity_scores else None
            
            return custom_response(
                success=True,
                data={
                    'assignment_id': assignment_id,
                    'assignment_title': assignment.title,
                    'total_students': total_students,
                    'submitted_count': submitted_count,
                    'submission_rate': round((submitted_count / total_students) * 100, 1) if total_students else 0,
                    'graded_count': graded_count,
                    'graded_rate': round((graded_count / submitted_count) * 100, 1) if submitted_count else 0,
                    'average_grade': average_grade,
                    'max_grade': max_grade,
                    'min_grade': min_grade,
                    'late_count': late_count,
                    'late_rate': round((late_count / submitted_count) * 100, 1) if submitted_count else 0,
                    'average_similarity': avg_similarity,
                    'grade_distribution': self._calculate_grade_distribution(grades, assignment.total_points) if grades else None
                },
                status_code=200
            )

    def _calculate_grade_distribution(self, grades: List[float], total_points: float) -> Dict:
        """Calculate grade distribution for statistics"""
        if not grades or not total_points:
            return None
            
        # Convert to percentages
        percentages = [round((grade / total_points) * 100) for grade in grades]
        
        # Define grade brackets
        brackets = {
            'A (90-100%)': 0,
            'B (80-89%)': 0,
            'C (70-79%)': 0,
            'D (60-69%)': 0,
            'F (<60%)': 0
        }
        
        for p in percentages:
            if p >= 90:
                brackets['A (90-100%)'] += 1
            elif p >= 80:
                brackets['B (80-89%)'] += 1
            elif p >= 70:
                brackets['C (70-79%)'] += 1
            elif p >= 60:
                brackets['D (60-69%)'] += 1
            else:
                brackets['F (<60%)'] += 1
        
        # Convert counts to percentages
        total = len(percentages)
        return {k: round((v / total) * 100, 1) for k, v in brackets.items()}

    def get_student_progress(self, student_id: str, course_id: str) -> Dict:
        """Get assignment progress for a student in a course"""
        with DatabaseContextManager() as ctx:
            # Verify enrollment
            enrollment = ctx.session.query(Enrollment).filter(
                Enrollment.student_id == student_id,
                Enrollment.course_id == course_id,
                Enrollment.status == 'active'
            ).first()
            
            if not enrollment:
                return custom_response(
                    success=False,
                    data="Student is not enrolled in this course",
                    status_code=403
                )
            
            # Get all assignments for the course
            assignments = ctx.session.query(Assignment).filter(
                Assignment.course_id == course_id,
                Assignment.is_published == True
            ).order_by(
                Assignment.due_date.asc()
            ).all()
            
            # Get all submissions for the student in this course
            submissions = ctx.session.query(AssignmentSubmission).filter(
                AssignmentSubmission.student_id == student_id,
                AssignmentSubmission.assignment_id.in_([a.id for a in assignments])
            ).all()
            
            # Create a mapping of assignment ID to submission
            submission_map = {s.assignment_id: s for s in submissions}
            
            # Prepare response data
            result = []
            for assignment in assignments:
                submission = submission_map.get(assignment.id)
                
                result.append({
                    'assignment': self._assignment_to_dict(assignment),
                    'submission_status': submission.status.value if submission else 'not_started',
                    'submission_grade': submission.grade if submission and submission.grade else None,
                    'grade_percentage': round((submission.grade / assignment.total_points) * 100, 1) 
                                      if submission and submission.grade and assignment.total_points else None,
                    'is_late': submission.late_days > 0 if submission else False,
                    'feedback_available': submission.feedback is not None if submission else False,
                    'days_remaining': (assignment.due_date - datetime.utcnow()).days if assignment.due_date else None,
                    'is_past_due': assignment.due_date < datetime.utcnow() if assignment.due_date else False
                })
            
            # Calculate overall progress
            completed = len([a for a in result if a['submission_status'] == 'graded'])
            total = len(result)
            progress = round((completed / total) * 100, 1) if total else 0
            
            # Calculate average grade
            graded_assignments = [a for a in result if a['submission_grade'] is not None]
            average_grade = round(sum(a['grade_percentage'] for a in graded_assignments) / len(graded_assignments), 1) if graded_assignments else None
            
            return custom_response(
                success=True,
                data={
                    'assignments': result,
                    'progress': progress,
                    'completed_count': completed,
                    'total_count': total,
                    'average_grade': average_grade,
                    'student_name': f"{enrollment.student.first_name} {enrollment.student.last_name}",
                    'course_title': enrollment.course.title,
                    'course_code': enrollment.course.code
                },
                status_code=200
            )

    def send_assignment_reminders(self, assignment_id: str) -> Dict:
        """Send reminders to students who haven't submitted an assignment (to be run periodically)"""
        with DatabaseContextManager() as ctx:
            assignment = ctx.session.query(Assignment).filter(
                Assignment.id == assignment_id,
                Assignment.is_published == True
            ).first()
            
            if not assignment:
                return custom_response(
                    success=False,
                    data="Assignment not found or not published",
                    status_code=404
                )
            
            # Get all enrolled students who haven't submitted
            submitted_student_ids = {s.student_id for s in assignment.submissions}
            
            # Get students through speciality relationship
            course = ctx.session.query(Course).filter(Course.id == assignment.course_id).first()
            if not course:
                return []
                
            students = ctx.session.query(Student).filter(
                Student.speciality_id == course.speciality_id,
                Student.id.notin_(submitted_student_ids),
                Student.is_active == True
            ).all()
            
            results = []
            for student in students:
                prefs = ctx.session.query(NotificationPreference).filter(
                    NotificationPreference.user_id == student.id
                ).first()
                
                if not prefs or not prefs.receive_email:
                    continue
                
                # Send reminder email
                subject = f"Reminder: {assignment.title} Due Soon"
                message = f"""
                <html>
                    <body>
                        <h2>Assignment Reminder</h2>
                        <p>Hello {student.first_name},</p>
                        
                        <p>This is a reminder that you haven't submitted the following assignment:</p>
                        
                        <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
                            <p><strong>Assignment:</strong> {assignment.title}</p>
                            <p><strong>Course:</strong> {assignment.course.code} - {assignment.course.title}</p>
                            <p><strong>Due Date:</strong> {assignment.due_date.strftime('%A, %B %d, %Y at %H:%M') if assignment.due_date else 'No due date'}</p>
                            <p><strong>Points:</strong> {assignment.total_points}</p>
                            <p><strong>Status:</strong> Not submitted</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({
                        '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({
                        'student_id': student.id,
                        'status': 'failed',
                        'error': str(e)
                    })
            
            return custom_response(
                success=True,
                data={
                    'assignment_id': assignment_id,
                    '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
                }
            )

    # Implementation of required abstract methods from ApiABC
    def get(self, id: str) -> Dict[str, Any]:
        """Get a single assignment by ID"""
        return self.get_assignment_details(id)

    def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Create a new assignment"""
        return self.create_assignment(payload)

    def update(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Update an assignment"""
        # Extract assignment_id from payload or use a default
        assignment_id = payload.get('id') or payload.get('assignment_id')
        if not assignment_id:
            return custom_response(
                success=False,
                data="Assignment ID is required for update",
                status_code=400
            )
        return self.update_assignment(assignment_id, payload)

    def fetchAll(self) -> Dict[str, Any]:
        """Get all assignments"""
        return self.get_course_assignments(page=1, per_page=1000)  # Get all with high limit

    def delete(self, id: str) -> Dict[str, Any]:
        """Delete an assignment"""
        return self.delete_assignment(id)