from django.db import models
from django.apps import apps
from typing import Any, cast
import re
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from .cbe import is_junior_subject_name, is_primary_subject_name


# 🏫 SCHOOL MODEL
class School(models.Model):
    SCHOOL_TYPES = (
        ('CAMBRIDGE', 'Cambridge'),
        ('CBE', 'CBE'),
    )

    SCHOOL_CATEGORY_CHOICES = (
        ('PRIMARY', 'Primary (Grades 1–6)'),
        ('JUNIOR', 'Junior (Grades 7–9)'),
        ('SENIOR', 'Senior (Grades 10–12)'),
        ('COMPREHENSIVE', 'Comprehensive (Primary + Junior)'),
        ('ALL_THROUGH', 'All Through (Pre School to Advanced)'),
    )

    name = models.CharField(max_length=255)
    motto = models.CharField(max_length=255, blank=True, default='')
    system_type = models.CharField(
        max_length=10,
        choices=[('844', '8-4-4'), ('CBE', 'Competency Based EDUCATION')],
        default='844'
    )
    school_category = models.CharField(
        max_length=20,
        choices=SCHOOL_CATEGORY_CHOICES,
        default='PRIMARY'
    )
    school_type = models.CharField(max_length=20, choices=SCHOOL_TYPES)
    CAMBRIDGE_GRADING_CHOICES = (
        ('CAMB_9_1', 'Cambridge 9-1'),
        ('CAMB_A_G', 'Cambridge A-G'),
    )
    cambridge_grading_system = models.CharField(
        max_length=20,
        choices=CAMBRIDGE_GRADING_CHOICES,
        default='CAMB_9_1',
    )
    cambridge_show_ranking = models.BooleanField(default=False)
    address = models.TextField(blank=True)
    phone = models.CharField(max_length=50, blank=True)
    email = models.EmailField(blank=True)
    student_limit = models.PositiveIntegerField(default=0)
    logo = models.ImageField(upload_to='school_logos/', blank=True, null=True)
    stamp = models.ImageField(upload_to='school_stamps/', blank=True, null=True)
    head_signature = models.ImageField(upload_to='head_signatures/', blank=True, null=True)

    def __str__(self):
        return f"{self.name} ({self.school_type})"

    def allows_level(self, level_name: str) -> bool:
        if self.system_type != 'CBE' or not level_name:
            return True
        category = self.school_category
        if category == 'PRIMARY':
            return level_name in ('Pre School', 'Lower Primary', 'Upper Primary')
        if category == 'JUNIOR':
            return level_name == 'Junior'
        if category == 'SENIOR':
            return level_name == 'Senior'
        if category == 'COMPREHENSIVE':
            return level_name in ('Pre School', 'Lower Primary', 'Upper Primary', 'Junior')
        if category == 'ALL_THROUGH':
            return level_name in ('Pre School', 'Kindergarten', 'Lower Primary', 'Upper Primary', 'Junior', 'Senior')
        return True

    def resolve_cbe_level(self, class_level_name=None):
        if self.system_type != 'CBE':
            return class_level_name
        category = self.school_category
        if category == 'PRIMARY':
            return class_level_name if class_level_name in ('Pre School', 'Lower Primary', 'Upper Primary') else 'Lower Primary'
        if category == 'JUNIOR':
            return 'Junior'
        if category == 'SENIOR':
            return 'Senior'
        if category == 'COMPREHENSIVE':
            return class_level_name or 'Lower Primary'
        if category == 'ALL_THROUGH':
            return class_level_name or 'Lower Primary'
        return class_level_name

    def allows_pathways(self) -> bool:
        return self.system_type == 'CBE' and self.school_category in ('SENIOR', 'ALL_THROUGH')

    def is_cambridge(self) -> bool:
        return self.school_type == 'CAMBRIDGE'


# 📘 SUBJECTS PER SCHOOL
class Subject(models.Model):
    SUBJECT_CATEGORY_STEM = 'STEM'
    SUBJECT_CATEGORY_SOCIAL_SCIENCES = 'SOCIAL_SCIENCES'
    SUBJECT_CATEGORY_ARTS_SPORTS_SCIENCE = 'ARTS_SPORTS_SCIENCE'
    SUBJECT_CATEGORY_CHOICES = (
        (SUBJECT_CATEGORY_STEM, 'STEM'),
        (SUBJECT_CATEGORY_SOCIAL_SCIENCES, 'Social Sciences'),
        (SUBJECT_CATEGORY_ARTS_SPORTS_SCIENCE, 'Arts & Sports Science'),
    )

    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name="subjects")
    code = models.CharField(max_length=20)
    name = models.CharField(max_length=100)
    short_name = models.CharField(max_length=30, null=True, blank=True)
    subject_category = models.CharField(max_length=30, choices=SUBJECT_CATEGORY_CHOICES, blank=True, default='')
    pathway = models.ForeignKey('Pathway', null=True, blank=True, on_delete=models.SET_NULL, related_name='school_subjects')
    education_level = models.ForeignKey('EducationLevel', on_delete=models.CASCADE, null=True, blank=True, related_name='school_subjects')

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['school', 'code', 'education_level'], name='unique_subject_code_per_school'),
            models.UniqueConstraint(fields=['school', 'name', 'education_level'], name='unique_subject_name_per_school'),
        ]

    def __str__(self):
        if self.short_name:
            return f"{self.code} - {self.name} ({self.short_name}) - {self.school.name}"
        return f"{self.code} - {self.name} - {self.school.name}"


class EducationLevel(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name


class Pathway(models.Model):
    name = models.CharField(max_length=100)
    code = models.CharField(max_length=10)

    class Meta:
        ordering = ['code']

    def __str__(self):
        return f"{self.code} - {self.name}"


class SchoolTypePricing(models.Model):
    school_type = models.CharField(max_length=20, choices=School.SCHOOL_TYPES, unique=True)
    price_per_student = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    def __str__(self):
        return f"{self.get_school_type_display()} @ {self.price_per_student}"


# 👨‍🏫 TEACHER MODEL
class Teacher(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    is_class_teacher = models.BooleanField(default=False)

    def __str__(self):
        return self.user.get_full_name() or self.user.username


# Audit log for promotions/demotions
class PromotionLog(models.Model):
    student = models.ForeignKey('Student', on_delete=models.CASCADE)
    from_class = models.ForeignKey('ClassRoom', on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
    to_class = models.ForeignKey('ClassRoom', on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
    performed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    timestamp = models.DateTimeField(auto_now_add=True)
    note = models.TextField(blank=True)

    def __str__(self):
        return f"{self.student} {self.from_class} -> {self.to_class} by {self.performed_by} at {self.timestamp}"


# 👨‍💼 HEADTEACHER (SCHOOL ADMIN)
class HeadTeacher(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='headteacher')
    school = models.ForeignKey(School, on_delete=models.CASCADE)
    full_name = models.CharField(max_length=200, blank=True, default='')
    phone = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return f"{self.full_name} - {self.school.name}"


class SchoolUserAccess(models.Model):
    ROLE_DEAN = 'DEAN'
    ROLE_SECRETARY = 'SECRETARY'
    ROLE_ACCOUNTS = 'ACCOUNTS'
    ROLE_DEPUTY = 'DEPUTY'
    ROLE_CHOICES = (
        (ROLE_DEAN, 'Dean'),
        (ROLE_SECRETARY, 'Secretary'),
        (ROLE_ACCOUNTS, 'Accounts (Bursar)'),
        (ROLE_DEPUTY, 'Deputy'),
    )

    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='user_access_roles')
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='school_access_role')
    role = models.CharField(max_length=20, choices=ROLE_CHOICES)
    is_active = models.BooleanField(default=True)
    granted_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='granted_school_roles')
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'School User Access Role'
        verbose_name_plural = 'School User Access Roles'

    def __str__(self):
        return f"{self.user.username} - {self.role} ({self.school.name})"


# 🏫 CLASSROOM MODEL
class ClassRoom(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='classes')
    name = models.CharField(max_length=100)  # e.g., Grade 1, Year 5
    section = models.CharField(max_length=50, blank=True)
    level = models.ForeignKey('EducationLevel', on_delete=models.CASCADE, null=True, blank=True)
    # Optional ordering index to allow sensible "previous/next" class navigation
    order = models.IntegerField(default=0)
    class_teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_classes')

    class Meta:
        ordering = ['order', 'name']

    def __str__(self):
        if self.section:
            return f"{self.name} {self.section} - {self.school.name}"
        return f"{self.name} - {self.school.name}"


# 🌊 STREAM MODEL (e.g., Stream A, B, C within a classroom)
class Stream(models.Model):
    classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE, related_name='streams')
    name = models.CharField(max_length=50)  # e.g., "A", "B", "Science", "Arts"
    code = models.CharField(max_length=10)  # e.g., "STR_A", "STR_SCI"

    class Meta:
        unique_together = ('classroom', 'name')
        ordering = ['name']

    def __str__(self):
        return f"{self.classroom.name} - Stream {self.name}"


# 👩‍🎓 STUDENT MODEL
class Student(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='students')
    classroom = models.ForeignKey(ClassRoom, on_delete=models.SET_NULL, null=True, blank=True)
    stream = models.ForeignKey(Stream, on_delete=models.SET_NULL, null=True, blank=True)
    parent_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)

    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    gender = models.CharField(max_length=10, choices=(('Male', 'Male'), ('Female', 'Female')))

    admission_number = models.CharField(max_length=50)
    admission_date = models.DateField()

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['school', 'admission_number'], name='unique_admission_per_school')
        ]

    parent_name = models.CharField(max_length=200, blank=True)
    parent_phone = models.CharField(max_length=50, blank=True)
    photo = models.ImageField(upload_to='student_photos/', blank=True, null=True)
    is_alumni = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.first_name} {self.last_name} ({self.school.name})"

    def total_fees_due(self, term, year):
        if not self.classroom_id:
            return 0

        FeeStructure = apps.get_model('finance', 'FeeStructure')
        structures = (
            FeeStructure.objects
            .filter(school=self.school, year=year, applicable_classes=self.classroom)
            .distinct()
        )

        total = 0
        for item in structures:
            if item.billing_mode == FeeStructure.BILLING_MODE_ONCE_YEAR:
                if item.due_term == term:
                    total += item.amount
            elif item.billing_mode == FeeStructure.BILLING_MODE_SELECTED_TERMS:
                if term in (item.applied_terms or []):
                    total += item.amount

        return total

    def total_paid(self, term, year):
        FeePayment = apps.get_model('finance', 'FeePayment')
        return FeePayment.objects.filter(student=self, term=term, year=year).aggregate(total=models.Sum('amount_paid'))['total'] or 0

    def balance(self, term, year):
        return self.total_fees_due(term, year) - self.total_paid(term, year)

# ANNOUNCEMENTS FOR PARENTS PORTAL
class Announcement(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title


# 🎓 GRADE SCALE (FOR REPORT CARDS)
class GradeScale(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE)
    min_score = models.FloatField()
    max_score = models.FloatField()
    grade = models.CharField(max_length=5)
    points = models.IntegerField(default=0)

    def __str__(self):
        return f"{self.grade} ({self.min_score}-{self.max_score}) [{self.points}]"


# 🧾 EXAM RESULT SUMMARY PER TERM
class ExamResult(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    term = models.CharField(max_length=20)
    year = models.IntegerField()
    total_marks = models.FloatField()
    average = models.FloatField()
    position_in_class = models.IntegerField(null=True, blank=True)

    def __str__(self):
        return f"{self.student} - {self.term} {self.year}"


# Mapping of which students take which subjects
class SubjectAllocation(models.Model):
    subject = models.ForeignKey('Subject', on_delete=models.CASCADE, related_name='allocations')
    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='subject_allocations')
    # Snapshot of student's classroom and admission number at allocation time
    classroom = models.ForeignKey(ClassRoom, on_delete=models.SET_NULL, null=True, blank=True)
    stream = models.ForeignKey(Stream, on_delete=models.SET_NULL, null=True, blank=True)
    admission_number = models.CharField(max_length=50, blank=True)
    # Snapshot of student's name at allocation time
    student_name = models.CharField(max_length=200, blank=True)

    class Meta:
        unique_together = ('subject', 'student')

    def __str__(self):
        return f"{self.student} -> {self.subject}"

    def clean(self):
        super().clean()
        school = self.student.school
        if not school or school.system_type != 'CBE':
            return

        student_level_raw = self.student.classroom.level.name if self.student.classroom and self.student.classroom.level else None
        student_level = school.resolve_cbe_level(student_level_raw) if hasattr(school, 'resolve_cbe_level') else student_level_raw
        if student_level == 'Primary':
            class_name = self.student.classroom.name if self.student.classroom else ''
            match = re.search(r"(\d+)", class_name or '')
            if match:
                try:
                    grade = int(match.group(1))
                    if 1 <= grade <= 3:
                        student_level = 'Lower Primary'
                    elif 4 <= grade <= 6:
                        student_level = 'Upper Primary'
                except ValueError:
                    pass
        subject_level = self.subject.education_level.name if self.subject.education_level else None

        if student_level and not school.allows_level(student_level):
            raise ValidationError(f"This school is not configured for {student_level} classes.")

        if student_level == 'Senior':
            if not school.allows_pathways():
                raise ValidationError('Senior pathway features are disabled for this school.')
            try:
                from academics.models import StudentPathway
                pathway = StudentPathway.objects.filter(student=self.student).first()
            except Exception:
                pathway = None

            if not pathway:
                raise ValidationError('Senior students must select a pathway before subject registration.')

            if self.subject.pathway and pathway and cast(Any, pathway).pathway_id != cast(Any, self.subject).pathway_id:
                raise ValidationError('Senior students can only register subjects within their pathway.')

        if student_level == 'Junior':
            if subject_level != 'Junior':
                raise ValidationError('Junior students can only register Junior learning areas.')

        if student_level in ('Lower Primary', 'Upper Primary', 'Primary'):
            if subject_level not in (student_level, 'Primary'):
                raise ValidationError(f'{student_level} students can only register {student_level} learning areas.')

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)


# Assignment of teachers to subjects and (optionally) classrooms and streams
class TeacherAssignment(models.Model):
    teacher = models.ForeignKey('Teacher', on_delete=models.CASCADE, related_name='assignments')
    subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
    classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE, null=True, blank=True)
    stream = models.ForeignKey(Stream, on_delete=models.SET_NULL, null=True, blank=True)
    # If stream is NULL, teacher teaches all streams in this class+subject

    class Meta:
        unique_together = ('teacher', 'subject', 'classroom', 'stream')

    def __str__(self):
        cls = f" ({self.classroom})" if self.classroom else ""
        return f"{self.teacher} -> {self.subject}{cls}"


# 🏫 STREAM CLASS TEACHER - Tracks class teacher per stream
class StreamClassTeacher(models.Model):
    classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE, related_name='stream_class_teachers')
    stream = models.ForeignKey(Stream, on_delete=models.CASCADE)  # Each classroom can have multiple streams
    teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True)

    class Meta:
        unique_together = ('classroom', 'stream')

    def __str__(self):
        return f"{self.classroom} - {self.stream.name}: {self.teacher}"


# 📅 EXAM MODEL
class Exam(models.Model):
    TERM_CHOICES = [
        ('Term 1', 'Term 1'),
        ('Term 2', 'Term 2'),
        ('Term 3', 'Term 3'),
    ]

    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='exams', null=True, blank=True)

    title = models.CharField(max_length=100)
    year = models.IntegerField()
    term = models.CharField(max_length=20, choices=TERM_CHOICES)
    start_date = models.DateField()
    end_date = models.DateField()
    marks_entry_locked = models.BooleanField(default=False)
    weight_percent = models.DecimalField(max_digits=5, decimal_places=2, default=0)

    def __str__(self):
        return f"{self.title} ({self.year} {self.term})"


# 📆 TERM DATES PER SCHOOL
class TermDate(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='term_dates')
    year = models.IntegerField()
    TERM_CHOICES = [
        ('Term 1', 'Term 1'),
        ('Term 2', 'Term 2'),
        ('Term 3', 'Term 3'),
    ]
    term = models.CharField(max_length=20, choices=TERM_CHOICES)
    start_date = models.DateField()
    end_date = models.DateField()

    class Meta:
        unique_together = ('school', 'year', 'term')

    def __str__(self):
        return f"{self.school.name} {self.term} {self.year}: {self.start_date} - {self.end_date}"


# 📆 SCHOOL CALENDAR EVENTS
class SchoolCalendarEvent(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='calendar_events')
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True, default='')
    start_date = models.DateField()
    end_date = models.DateField(null=True, blank=True)
    color = models.CharField(max_length=7, default='#f59e0b')
    send_to_parents = models.BooleanField(default=False)
    send_to_teachers = models.BooleanField(default=False)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_calendar_events')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['start_date', 'end_date', 'title']

    def __str__(self):
        return f"{self.school.name} - {self.title} ({self.start_date})"


class AttendanceRegister(models.Model):
    STATUS_DRAFT = 'DRAFT'
    STATUS_SUBMITTED = 'SUBMITTED'
    STATUS_CHOICES = (
        (STATUS_DRAFT, 'Draft'),
        (STATUS_SUBMITTED, 'Submitted'),
    )

    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='attendance_registers')
    classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE, related_name='attendance_registers')
    stream = models.ForeignKey(Stream, on_delete=models.SET_NULL, null=True, blank=True, related_name='attendance_registers')
    date = models.DateField()
    status = models.CharField(max_length=12, choices=STATUS_CHOICES, default=STATUS_DRAFT)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='attendance_created')
    submitted_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-date', '-id']
        unique_together = ('school', 'classroom', 'stream', 'date')

    def __str__(self):
        return f"{self.school.name} {self.classroom} {self.date}"


class AttendanceEntry(models.Model):
    STATUS_PRESENT = 'PRESENT'
    STATUS_ABSENT = 'ABSENT'
    STATUS_LATE = 'LATE'
    STATUS_EXCUSED = 'EXCUSED'
    STATUS_CHOICES = (
        (STATUS_PRESENT, 'Present'),
        (STATUS_ABSENT, 'Absent'),
        (STATUS_LATE, 'Late'),
        (STATUS_EXCUSED, 'Excused'),
    )

    register = models.ForeignKey(AttendanceRegister, on_delete=models.CASCADE, related_name='entries')
    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='attendance_entries')
    status = models.CharField(max_length=12, choices=STATUS_CHOICES, default=STATUS_PRESENT)
    remarks = models.TextField(blank=True, default='')

    class Meta:
        unique_together = ('register', 'student')

    def __str__(self):
        return f"{self.student} - {self.get_status_display()}"


# Site-level configuration (singleton pattern not enforced here; admin can keep one active entry)
class SiteConfig(models.Model):
    """Holds site-wide assets such as the logo uploaded by a superuser/admin.

    The templates use the most recently created SiteConfig if present.
    """
    site_name = models.CharField(max_length=200, default='SkulPlus')
    logo = models.ImageField(upload_to='site_logo/', null=True, blank=True)
    favicon = models.ImageField(upload_to='site_logo/', null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'Site Configuration'
        verbose_name_plural = 'Site Configuration'

    def __str__(self):
        return f"SiteConfig ({self.site_name})"


class MarkSheet(models.Model):
    term = models.CharField(max_length=20)
    exam = models.ForeignKey('Exam', on_delete=models.CASCADE)
    school_class = models.ForeignKey('ClassRoom', on_delete=models.CASCADE)
    subject = models.ForeignKey('Subject', on_delete=models.CASCADE)
    out_of = models.IntegerField(default=100)

    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
    ]
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')

    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('exam', 'school_class', 'subject')

    def __str__(self):
        return f"{self.exam} - {self.school_class} - {self.subject}"

    def is_multi_paper(self):
        """Check if this marksheet uses multiple papers (for Junior and above)"""
        if not self.school_class or not self.school_class.level:
            return False
        level_name = self.school_class.level.name
        # Junior is grades 7-9, Senior 10-12
        return level_name in ('Junior', 'Senior')


class Paper(models.Model):
    marksheet = models.ForeignKey(MarkSheet, on_delete=models.CASCADE, related_name='papers')
    name = models.CharField(max_length=50)  # e.g., "Paper 1", "Paper 2"
    out_of = models.IntegerField(default=100)
    weight = models.DecimalField(max_digits=5, decimal_places=2, default=1.0)  # Weight as decimal, e.g., 0.5 for 50%

    class Meta:
        unique_together = ('marksheet', 'name')
        ordering = ['name']

    def __str__(self):
        return f"{self.marksheet} - {self.name}"


class PaperMark(models.Model):
    paper = models.ForeignKey(Paper, on_delete=models.CASCADE, related_name='marks')
    student = models.ForeignKey('Student', on_delete=models.CASCADE)
    score = models.FloatField(null=True, blank=True)

    class Meta:
        unique_together = ('paper', 'student')

    def __str__(self):
        return f"{self.paper} - {self.student}: {self.score}"


class StudentMark(models.Model):
    marksheet = models.ForeignKey(MarkSheet, related_name='marks', on_delete=models.CASCADE)
    student = models.ForeignKey('Student', on_delete=models.CASCADE)
    score = models.FloatField(null=True, blank=True)
    level = models.CharField(max_length=10, blank=True, default='')
    points = models.IntegerField(null=True, blank=True)
    comment_text = models.TextField(blank=True, default='')
    comment_manual = models.BooleanField(default=False)

    class Meta:
        unique_together = ('marksheet', 'student')


class CompetencyComment(models.Model):
    EDUCATION_LEVEL_CHOICES = (
        ('Primary', 'Primary'),
        ('Lower Primary', 'Lower Primary'),
        ('Upper Primary', 'Upper Primary'),
        ('Junior', 'Junior'),
    )

    education_level = models.CharField(max_length=20, choices=EDUCATION_LEVEL_CHOICES)
    subject = models.ForeignKey('Subject', null=True, blank=True, on_delete=models.SET_NULL, related_name='competency_comments')
    performance_level = models.CharField(max_length=10)
    comment_text = models.TextField()

    class Meta:
        ordering = ['education_level', 'performance_level']

    def __str__(self):
        subject_name = self.subject.name if self.subject else 'General'
        return f"{self.education_level} {subject_name} {self.performance_level}"


class ClassCompetencyComment(models.Model):
    classroom = models.ForeignKey('ClassRoom', on_delete=models.CASCADE, related_name='cbc_comments')
    subject = models.ForeignKey('Subject', null=True, blank=True, on_delete=models.SET_NULL, related_name='class_cbc_comments')
    performance_level = models.CharField(max_length=20)
    variant_index = models.IntegerField(default=1)
    comment_text = models.TextField()
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ('classroom', 'subject', 'performance_level', 'variant_index')

    def __str__(self):
        subject_name = self.subject.name if self.subject else 'General'
        return f"{self.classroom} {subject_name} {self.performance_level}"


class LearningStrand(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='learning_strands')
    education_level = models.ForeignKey('EducationLevel', on_delete=models.CASCADE)
    name = models.CharField(max_length=120)

    class Meta:
        ordering = ['education_level__name', 'name']
        unique_together = ('school', 'education_level', 'name')

    def __str__(self):
        return f"{self.name} ({self.education_level.name})"


class SubStrand(models.Model):
    learning_strand = models.ForeignKey(LearningStrand, on_delete=models.CASCADE, related_name='sub_strands')
    name = models.CharField(max_length=120)

    class Meta:
        ordering = ['name']
        unique_together = ('learning_strand', 'name')

    def __str__(self):
        return f"{self.learning_strand.name} - {self.name}"


class StudentCompetency(models.Model):
    # CBC (Kenyan CBE) levels
    LEVEL_CBC_EMERGING = 'CBC_EMERGING'
    LEVEL_CBC_DEVELOPING = 'CBC_DEVELOPING'
    LEVEL_CBC_APPROACHING = 'CBC_APPROACHING'
    LEVEL_CBC_MEETING = 'CBC_MEETING'
    LEVEL_CBC_EXCEEDING = 'CBC_EXCEEDING'
    # Cambridge levels
    LEVEL_CAM_BEGINNING = 'CAM_BEGINNING'
    LEVEL_CAM_DEVELOPING = 'CAM_DEVELOPING'
    LEVEL_CAM_SECURE = 'CAM_SECURE'
    LEVEL_CAM_ADVANCED = 'CAM_ADVANCED'
    LEVEL_CAM_MASTERY = 'CAM_MASTERY'

    LEVEL_CHOICES = (
        (LEVEL_CBC_EMERGING, 'Emerging'),
        (LEVEL_CBC_DEVELOPING, 'Developing'),
        (LEVEL_CBC_APPROACHING, 'Approaching Expectation'),
        (LEVEL_CBC_MEETING, 'Meeting Expectation'),
        (LEVEL_CBC_EXCEEDING, 'Exceeding Expectation'),
        (LEVEL_CAM_BEGINNING, 'Beginning'),
        (LEVEL_CAM_DEVELOPING, 'Developing'),
        (LEVEL_CAM_SECURE, 'Secure'),
        (LEVEL_CAM_ADVANCED, 'Advanced'),
        (LEVEL_CAM_MASTERY, 'Mastery'),
    )

    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='competencies')
    exam = models.ForeignKey('Exam', on_delete=models.CASCADE, related_name='competencies')
    learning_strand = models.ForeignKey(LearningStrand, on_delete=models.CASCADE)
    sub_strand = models.ForeignKey(SubStrand, on_delete=models.CASCADE)
    level = models.CharField(max_length=20, choices=LEVEL_CHOICES)
    comment_text = models.TextField(blank=True, default='')
    comment_manual = models.BooleanField(default=False)
    recorded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('student', 'exam', 'sub_strand')


class StudentCompetencySummary(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='competency_summaries')
    exam = models.ForeignKey('Exam', on_delete=models.CASCADE, related_name='competency_summaries')
    overall_comment = models.TextField(blank=True, default='')
    comment_manual = models.BooleanField(default=False)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ('student', 'exam')


def _validate_pdf_size(file_obj):
    if not file_obj:
        return
    max_size = 5 * 1024 * 1024
    if file_obj.size > max_size:
        raise ValidationError('PDF must not exceed 5MB.')


def _validate_resource_size(file_obj):
    if not file_obj:
        return
    max_size = 10 * 1024 * 1024
    if file_obj.size > max_size:
        raise ValidationError('Resource file must not exceed 10MB.')


class Assignment(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE, related_name='assignments')
    classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE, related_name='assignments')
    subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='assignments')
    teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, related_name='uploaded_assignments')
    term = models.CharField(max_length=20, choices=Exam.TERM_CHOICES)
    year = models.IntegerField()
    title = models.CharField(max_length=160, blank=True, default='')
    document = models.FileField(
        upload_to='assignments/',
        validators=[FileExtensionValidator(['pdf']), _validate_pdf_size],
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-year', '-term', '-created_at']
        unique_together = ('school', 'classroom', 'subject', 'term', 'year')

    def __str__(self):
        return f'{self.classroom} {self.subject} {self.term} {self.year}'

    def clean(self):
        super().clean()
        if self.document and not self.document.name.lower().endswith('.pdf'):
            raise ValidationError({'document': 'Only PDF files are allowed.'})


class LearningResource(models.Model):
    CURRICULUM_CBE = 'CBE'
    CURRICULUM_CAMBRIDGE = 'CAMBRIDGE'
    CURRICULUM_CHOICES = (
        (CURRICULUM_CBE, 'CBE'),
        (CURRICULUM_CAMBRIDGE, 'Cambridge'),
    )

    RESOURCE_EXAM = 'EXAM'
    RESOURCE_NOTES = 'NOTES'
    RESOURCE_REVISION = 'REVISION'
    RESOURCE_SCHEME = 'SCHEME'
    RESOURCE_PAST_PAPER = 'PAST_PAPER'
    RESOURCE_OTHER = 'OTHER'
    RESOURCE_TYPE_CHOICES = (
        (RESOURCE_EXAM, 'Exam'),
        (RESOURCE_NOTES, 'Notes'),
        (RESOURCE_REVISION, 'Revision'),
        (RESOURCE_SCHEME, 'Scheme of Work'),
        (RESOURCE_PAST_PAPER, 'Past Paper'),
        (RESOURCE_OTHER, 'Other'),
    )

    curriculum = models.CharField(max_length=20, choices=CURRICULUM_CHOICES)
    education_level = models.ForeignKey(EducationLevel, null=True, blank=True, on_delete=models.SET_NULL)
    class_name = models.CharField(max_length=60, blank=True, default='')
    subject_name = models.CharField(max_length=120, blank=True, default='')
    resource_type = models.CharField(max_length=20, choices=RESOURCE_TYPE_CHOICES, default=RESOURCE_OTHER)
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True, default='')
    file = models.FileField(
        upload_to='resources/',
        validators=[FileExtensionValidator(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']), _validate_resource_size],
    )
    cloudinary_public_id = models.CharField(max_length=300, blank=True, default='')
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['education_level__name', 'class_name', 'subject_name', 'title']

    def __str__(self):
        return f"{self.title} ({self.get_curriculum_display()})"

    @staticmethod
    def allowed_level_names(curriculum: str) -> list[str]:
        if curriculum == LearningResource.CURRICULUM_CBE:
            return ['Pre School', 'Lower Primary', 'Upper Primary', 'Junior', 'Senior']
        if curriculum == LearningResource.CURRICULUM_CAMBRIDGE:
            return [
                'Cambridge Primary',
                'Cambridge Lower Secondary',
                'Cambridge Upper Secondary (IGCSE)',
                'Cambridge Advanced (AS & A Level)',
                'Kindergarten',
                'Lower Primary',
                'Upper Primary',
                'Lower Secondary',
                'Upper Secondary (IGCSE)',
                'A Level',
            ]
        return []

    def clean(self):
        super().clean()
        if self.education_level_id:
            allowed = self.allowed_level_names(self.curriculum)
            if allowed and self.education_level and self.education_level.name not in allowed:
                raise ValidationError({'education_level': 'Selected education level is not valid for this curriculum.'})


