from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone

from secondary.models.policies import SecondaryComputationPolicy


class ALevelCompetencyScale(models.Model):
    class Code(models.TextChoices):
        EXCEEDS = "E", "Exceeds"
        MEETS = "M", "Meets"
        APPROACHING = "A", "Approaching"
        BELOW = "B", "Below"

    policy = models.ForeignKey(
        SecondaryComputationPolicy,
        on_delete=models.CASCADE,
        related_name="a_level_scales",
    )
    code = models.CharField(max_length=2, choices=Code.choices)
    descriptor = models.CharField(max_length=120)
    point_value = models.DecimalField(max_digits=4, decimal_places=2, default=Decimal("0.00"))
    min_weighted_point = models.DecimalField(max_digits=4, decimal_places=2)
    max_weighted_point = models.DecimalField(max_digits=4, decimal_places=2)
    display_order = models.PositiveSmallIntegerField(default=0)

    class Meta:
        unique_together = ("policy", "code")
        ordering = ["display_order", "-point_value"]
        db_table = "app_alevelcompetencyscale"

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

    def clean(self):
        if self.policy_id and self.policy.level != SecondaryComputationPolicy.Level.UPPER_SECONDARY:
            raise ValidationError("A-Level competency scale requires an Upper Secondary policy.")
        if self.min_weighted_point > self.max_weighted_point:
            raise ValidationError("Minimum weighted point cannot exceed maximum weighted point.")


class ALevelComponentWeight(models.Model):
    class ComponentType(models.TextChoices):
        SCHOOL_BASED_ASSESSMENT = "SBA", "School-Based Assessment"
        MODULAR_EXAM = "MODULAR_EXAM", "Modular Exam"
        PRACTICAL_PROJECT = "PRACTICAL_PROJECT", "Practical / Project"
        CONTEMPORARY_STUDIES = "CONTEMPORARY_STUDIES", "Contemporary Studies"
        OTHER = "OTHER", "Other"

    policy = models.ForeignKey(
        SecondaryComputationPolicy,
        on_delete=models.CASCADE,
        related_name="a_level_component_weights",
    )
    subject = models.ForeignKey("app.Subject", on_delete=models.CASCADE, related_name="a_level_component_weights")
    component_type = models.CharField(max_length=40, choices=ComponentType.choices)
    weight = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
    is_required = models.BooleanField(default=True)

    class Meta:
        unique_together = ("policy", "subject", "component_type")
        ordering = ["subject_id", "component_type"]
        db_table = "app_alevelcomponentweight"

    def __str__(self):
        return f"{self.subject} - {self.component_type} ({self.weight}%)"

    def clean(self):
        if self.policy_id and self.policy.level != SecondaryComputationPolicy.Level.UPPER_SECONDARY:
            raise ValidationError("A-Level component weights require an Upper Secondary policy.")
        if self.subject_id and self.policy_id and self.subject.section_id != self.policy.section_id:
            raise ValidationError("A-Level component weight subject must match policy section.")
        if self.weight <= 0 or self.weight > 100:
            raise ValidationError("Component weight must be greater than zero and not exceed 100.")


class ALevelSubjectModule(models.Model):
    subject = models.ForeignKey("app.Subject", on_delete=models.CASCADE, related_name="a_level_modules")
    code = models.CharField(max_length=30)
    name = models.CharField(max_length=120)
    module_order = models.PositiveSmallIntegerField(default=1)
    is_active = models.BooleanField(default=True)

    class Meta:
        unique_together = ("subject", "code")
        ordering = ["subject_id", "module_order", "code"]
        db_table = "app_alevelsubjectmodule"

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


class ALevelModuleAssessment(models.Model):
    policy = models.ForeignKey(
        SecondaryComputationPolicy,
        on_delete=models.CASCADE,
        related_name="a_level_assessments",
    )
    student = models.ForeignKey("app.Student", on_delete=models.CASCADE, related_name="a_level_assessments")
    subject = models.ForeignKey("app.Subject", on_delete=models.CASCADE, related_name="a_level_assessments")
    module = models.ForeignKey(
        ALevelSubjectModule,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="assessments",
    )
    component_type = models.CharField(max_length=40, choices=ALevelComponentWeight.ComponentType.choices)
    competency_code = models.CharField(max_length=2, choices=ALevelCompetencyScale.Code.choices)
    points_awarded = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)
    attempt_no = models.PositiveSmallIntegerField(default=1)
    is_retake = models.BooleanField(default=False)
    assessed_on = models.DateField(default=timezone.now)
    notes = models.TextField(blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ("student", "subject", "module", "component_type", "attempt_no")
        ordering = ["student_id", "subject_id", "component_type", "module_id", "-attempt_no", "-assessed_on"]
        db_table = "app_alevelmoduleassessment"

    def __str__(self):
        module_label = self.module.code if self.module_id else "GENERAL"
        return f"{self.student} - {self.subject} - {self.component_type} - {module_label} (Attempt {self.attempt_no})"

    def clean(self):
        if self.policy_id and self.policy.level != SecondaryComputationPolicy.Level.UPPER_SECONDARY:
            raise ValidationError("A-Level assessments require an Upper Secondary policy.")
        if self.subject_id and self.policy_id and self.subject.section_id != self.policy.section_id:
            raise ValidationError("Assessment subject section must match policy section.")
        if self.module_id and self.module.subject_id != self.subject_id:
            raise ValidationError("Selected module must belong to the selected subject.")
        if self.component_type == ALevelComponentWeight.ComponentType.MODULAR_EXAM and not self.module_id:
            raise ValidationError("Modular exam assessments must reference a subject module.")
        if self.component_type != ALevelComponentWeight.ComponentType.MODULAR_EXAM and self.module_id:
            raise ValidationError("Only modular exam assessments may reference a module.")
        if self.attempt_no < 1:
            raise ValidationError("Attempt number must be at least 1.")

    def save(self, *args, **kwargs):
        self.is_retake = self.attempt_no > 1
        if self.policy_id and self.competency_code and self.points_awarded is None:
            scale = ALevelCompetencyScale.objects.filter(
                policy=self.policy,
                code=self.competency_code,
            ).first()
            if scale:
                self.points_awarded = scale.point_value
        super().save(*args, **kwargs)
