# academics/models_grades.py
"""
Grade and Report Card models for the school management system.
Supports both Anglophone and Francophone, Primary and Nursery formats.

Each format has a unique subject/activity structure:
  - Anglophone Primary:  Activities > Sub-Activities, marks /20
  - Anglophone Nursery:  Domains > Activities > Sub-Activities, marks /20 (competency-based)
  - Francophone Primary: Domaines > Sous-Activités, marks /20
  - Francophone Nursery: Activités > Sous-Activités, marks /20
"""

from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone


# ---------------------------------------------------------------------------
# 1. ACADEMIC TERM
# ---------------------------------------------------------------------------

class AcademicTerm(models.Model):
    """
    Defines a specific term within an academic year.

    The school runs 3 terms per year, each covering 3 months.
    Months are numbered 1-9 across the year (Term1=1-3, Term2=4-6, Term3=7-9).

    Attributes:
        academic_year: e.g. "2025-2026"
        term: 1, 2, or 3
        start_date / end_date: real calendar dates
        is_current: only one term should be current at a time
    """
    academic_year = models.CharField(
        max_length=9,
        default="2025-2026",
        verbose_name=_("Academic Year")
    )
    term = models.PositiveSmallIntegerField(
        choices=[(1, 'Term 1'), (2, 'Term 2'), (3, 'Term 3')],
        verbose_name=_("Term")
    )
    start_date = models.DateField(verbose_name=_("Start Date"))
    end_date   = models.DateField(verbose_name=_("End Date"))
    is_current = models.BooleanField(
        default=False,
        verbose_name=_("Current Term"),
        help_text=_("Only one term should be marked as current.")
    )

    class Meta:
        verbose_name = _("Academic Term")
        verbose_name_plural = _("Academic Terms")
        unique_together = ['academic_year', 'term']
        ordering = ['academic_year', 'term']

    def __str__(self):
        return f"Term {self.term} — {self.academic_year}"

    def save(self, *args, **kwargs):
        """Ensure only one term is marked current."""
        if self.is_current:
            AcademicTerm.objects.exclude(pk=self.pk).update(is_current=False)
        super().save(*args, **kwargs)

    @classmethod
    def get_current(cls):
        """Return the currently active term, or None."""
        return cls.objects.filter(is_current=True).first()

    @property
    def month_range(self):
        """Return the 3 month numbers for this term (1-indexed across the year)."""
        base = (self.term - 1) * 3 + 1
        return [base, base + 1, base + 2]


# ---------------------------------------------------------------------------
# 2. SUBJECT CATALOGUE
# ---------------------------------------------------------------------------

class ReportSubject(models.Model):
    """
    Master list of all activities/subjects that appear on report cards.

    Each subject belongs to one of 4 report card types and has a domain
    (group heading printed in the left spine of the table).

    REPORT_TYPE choices:
        ap = Anglophone Primary
        an = Anglophone Nursery
        fp = Francophone Primary
        fn = Francophone Nursery
    """

    REPORT_TYPE_CHOICES = [
        ('ap', 'Anglophone Primary'),
        ('an', 'Anglophone Nursery'),
        ('fp', 'Francophone Primary'),
        ('fn', 'Francophone Nursery'),
    ]

    report_type  = models.CharField(max_length=2, choices=REPORT_TYPE_CHOICES)
    domain       = models.CharField(max_length=100, verbose_name=_("Domain / Activity Group"))
    activity     = models.CharField(max_length=100, blank=True, verbose_name=_("Activity (if nested)"))
    sub_activity = models.CharField(max_length=150, verbose_name=_("Sub-Activity / Subject"))
    order        = models.PositiveSmallIntegerField(default=0, verbose_name=_("Display Order"))

    class Meta:
        verbose_name = _("Report Subject")
        verbose_name_plural = _("Report Subjects")
        ordering = ['report_type', 'order']
        unique_together = ['report_type', 'domain', 'activity', 'sub_activity']

    def __str__(self):
        parts = [p for p in [self.domain, self.activity, self.sub_activity] if p]
        return f"[{self.get_report_type_display()}] {' > '.join(parts)}"


# ---------------------------------------------------------------------------
# 3. MONTHLY MARK
# ---------------------------------------------------------------------------

class MonthlyMark(models.Model):
    """
    A single student's mark for one sub-activity in one month.

    Months are numbered 1–9 across the school year:
        Term 1 → months 1, 2, 3
        Term 2 → months 4, 5, 6
        Term 3 → months 7, 8, 9

    Marks are out of 20 for all 4 formats.
    """

    student       = models.ForeignKey(
        'students.Student',
        on_delete=models.CASCADE,
        related_name='monthly_marks'
    )
    term          = models.ForeignKey(
        AcademicTerm,
        on_delete=models.CASCADE,
        related_name='monthly_marks'
    )
    subject       = models.ForeignKey(
        ReportSubject,
        on_delete=models.CASCADE,
        related_name='monthly_marks'
    )
    month_number  = models.PositiveSmallIntegerField(
        choices=[(i, f"Month {i}") for i in range(1, 10)],
        verbose_name=_("Month Number (1–9)")
    )
    score = models.DecimalField(
        max_digits=4,
        decimal_places=1,
        null=True,
        blank=True,
        validators=[MinValueValidator(0), MaxValueValidator(20)],
        verbose_name=_("Score (/20)")
    )

    entered_by = models.ForeignKey(
        'staff.Staff',
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name='marks_entered'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("Monthly Mark")
        verbose_name_plural = _("Monthly Marks")
        unique_together = ['student', 'term', 'subject', 'month_number']
        ordering = ['term', 'month_number', 'subject__order']

    def __str__(self):
        return (f"{self.student} | {self.subject.sub_activity} | "
                f"Month {self.month_number} | {self.score}/20")


# ---------------------------------------------------------------------------
# 4. TERM SYNTHESIS (per subject per term)
# ---------------------------------------------------------------------------

class TermSynthesis(models.Model):
    """
    Stores the computed Synthèse (term average) and Appréciation for one
    subject across a full term, plus the class teacher's per-subject remark.

    APPRECIATION_CHOICES (Francophone scale shown on report card):
        NA  = 10–10/20 — Compétences non acquises
        ECA = 11–14/20 — Compétences en cours d'acquisition
        A   = 15–17/20 — Compétences acquises
        A+  = 18–20/20 — Expert
    """

    APPRECIATION_CHOICES = [
        ('NA',  'NA  — Non Acquis (10/20)'),
        ('ECA', 'ECA — En Cours d\'Acquisition (11–14/20)'),
        ('A',   'A   — Acquis (15–17/20)'),
        ('A+',  'A+  — Expert (18–20/20)'),
        ('',    '—'),
    ]

    student        = models.ForeignKey(
        'students.Student',
        on_delete=models.CASCADE,
        related_name='term_syntheses'
    )
    term           = models.ForeignKey(
        AcademicTerm,
        on_delete=models.CASCADE,
        related_name='term_syntheses'
    )
    subject        = models.ForeignKey(
        ReportSubject,
        on_delete=models.CASCADE,
        related_name='term_syntheses'
    )
    synthese_score = models.DecimalField(
        max_digits=4, decimal_places=2,
        null=True, blank=True,
        verbose_name=_("Synthèse Score (/20)")
    )
    appreciation   = models.CharField(
        max_length=3,
        choices=APPRECIATION_CHOICES,
        blank=True,
        verbose_name=_("Appréciation")
    )
    remark         = models.CharField(
        max_length=200, blank=True,
        verbose_name=_("Remark")
    )

    class Meta:
        verbose_name = _("Term Synthesis")
        verbose_name_plural = _("Term Syntheses")
        unique_together = ['student', 'term', 'subject']

    def __str__(self):
        return f"{self.student} | {self.subject.sub_activity} | Term {self.term.term} synthesis"

    def compute_from_monthly(self):
        """
        Auto-calculate synthese_score as average of this term's 3 monthly marks
        and set appreciation based on Francophone scale.
        """
        marks = MonthlyMark.objects.filter(
            student=self.student,
            term=self.term,
            subject=self.subject,
            score__isnull=False
        )
        scores = [float(m.score) for m in marks]
        if scores:
            avg = sum(scores) / len(scores)
            self.synthese_score = round(avg, 2)
            if avg <= 10:
                self.appreciation = 'NA'
            elif avg <= 14:
                self.appreciation = 'ECA'
            elif avg <= 17:
                self.appreciation = 'A'
            else:
                self.appreciation = 'A+'
        self.save()


# ---------------------------------------------------------------------------
# 5. REPORT CARD (one per student per term)
# ---------------------------------------------------------------------------

class ReportCard(models.Model):
    """
    The master report card record for one student in one term.

    Aggregates all subject marks into totals/averages, stores
    attendance summary, teacher comments, and the final PDF file.

    The report_type field determines which HTML template is used
    for PDF generation.
    """

    REPORT_TYPE_CHOICES = [
        ('ap', 'Anglophone Primary'),
        ('an', 'Anglophone Nursery'),
        ('fp', 'Francophone Primary'),
        ('fn', 'Francophone Nursery'),
    ]

    PROMOTION_CHOICES = [
        ('promoted',  'Promoted'),
        ('repeat',    'Repeat / Redouble'),
        ('dismissed', 'Dismissed / Exclu(e)'),
        ('pending',   'Pending'),
    ]

    CONDUCT_CHOICES = [
        ('good',      'Good'),
        ('fair',      'Fair'),
        ('poor',      'Poor'),
        ('excellent', 'Excellent'),
    ]

    # ---- Identity ----
    student      = models.ForeignKey(
        'students.Student',
        on_delete=models.CASCADE,
        related_name='report_cards'
    )
    term         = models.ForeignKey(
        AcademicTerm,
        on_delete=models.CASCADE,
        related_name='report_cards'
    )
    school_class = models.ForeignKey(
        'academics.SchoolClass',
        on_delete=models.CASCADE,
        related_name='report_cards'
    )
    report_type  = models.CharField(
        max_length=2,
        choices=REPORT_TYPE_CHOICES,
        verbose_name=_("Report Card Type")
    )

    # ---- Computed totals (cached) ----
    total_marks   = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    average       = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    position      = models.PositiveIntegerField(null=True, blank=True,
                        verbose_name=_("Position in Class"))
    total_general = models.DecimalField(max_digits=7, decimal_places=2, default=0,
                        verbose_name=_("Total General"))

    # ---- Annual results (populated on Term 3 card) ----
    term1_average = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    term2_average = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    term3_average = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    annual_average = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    annual_position = models.PositiveIntegerField(null=True, blank=True)

    # ---- Attendance ----
    days_present = models.PositiveIntegerField(default=0)
    days_absent  = models.PositiveIntegerField(default=0)
    days_late    = models.PositiveIntegerField(default=0)

    # ---- Conduct & Comments ----
    conduct              = models.CharField(
        max_length=10, choices=CONDUCT_CHOICES, blank=True,
        verbose_name=_("Conduct")
    )
    class_teacher_remark = models.TextField(
        blank=True, verbose_name=_("Class Teacher Remark / OBS. Enseignant(e)")
    )
    head_teacher_remark  = models.TextField(
        blank=True, verbose_name=_("Head Teacher Remark / OBS. Direction")
    )

    # ---- Promotion decision ----
    promotion_status = models.CharField(
        max_length=10,
        choices=PROMOTION_CHOICES,
        default='pending',
        verbose_name=_("Promotion Decision")
    )
    promoted_to_class = models.CharField(
        max_length=50, blank=True,
        verbose_name=_("Promoted to Class (if promoted)")
    )
    next_term_begins  = models.DateField(
        null=True, blank=True,
        verbose_name=_("Next Term Begins")
    )

    # ---- Francophone conduct checkboxes (from doc 11 page 2) ----
    discipline_brutal = models.BooleanField(default=False, verbose_name=_("Discipline Brutal(e)"))
    absenteisme       = models.BooleanField(default=False, verbose_name=_("Absentéisme"))
    barvade           = models.BooleanField(default=False, verbose_name=_("Barvade(e)"))

    # ---- PDF file ----
    pdf_file     = models.FileField(
        upload_to='report_cards/%Y/%m/',
        null=True, blank=True,
        verbose_name=_("Generated PDF")
    )
    generated_at = models.DateTimeField(null=True, blank=True)

    # ---- Metadata ----
    created_at   = models.DateTimeField(auto_now_add=True)
    updated_at   = models.DateTimeField(auto_now=True)
    is_finalised = models.BooleanField(
        default=False,
        help_text=_("Lock record from further editing once signed off.")
    )

    class Meta:
        verbose_name = _("Report Card")
        verbose_name_plural = _("Report Cards")
        unique_together = ['student', 'term']
        ordering = ['-term__academic_year', 'term__term', 'student__last_name']
        indexes = [
            models.Index(fields=['student', 'term']),
            models.Index(fields=['school_class', 'term']),
            models.Index(fields=['report_type']),
        ]

    def __str__(self):
        return (f"{self.student.get_full_name()} | "
                f"{self.get_report_type_display()} | "
                f"Term {self.term.term} {self.term.academic_year}")

    # ------------------------------------------------------------------ helpers

    def compute_totals(self):
        """
        Recalculate total_marks, average from TermSynthesis records
        and save. Call this after all syntheses are finalised.
        """
        syntheses = TermSynthesis.objects.filter(
            student=self.student,
            term=self.term,
            synthese_score__isnull=False
        )
        scores = [float(s.synthese_score) for s in syntheses]
        if scores:
            self.total_marks   = sum(scores)
            self.average       = round(sum(scores) / len(scores), 2)
            self.total_general = self.total_marks
        self.save(update_fields=['total_marks', 'average', 'total_general'])

    def compute_class_positions(self):
        """
        Rank all students in the same class/term by average descending.
        Updates position field for every ReportCard in the class.
        """
        cards = ReportCard.objects.filter(
            term=self.term,
            school_class=self.school_class
        ).order_by('-average')
        for rank, card in enumerate(cards, start=1):
            card.position = rank
            card.save(update_fields=['position'])

    def get_appreciation(self):
        """Return Francophone appreciation label for overall average."""
        avg = float(self.average)
        if avg <= 10:   return 'NA'
        elif avg <= 14: return 'ECA'
        elif avg <= 17: return 'A'
        else:           return 'A+'

    def mark_generated(self, file_path):
        """Record that a PDF has been generated."""
        self.pdf_file     = file_path
        self.generated_at = timezone.now()
        self.save(update_fields=['pdf_file', 'generated_at'])
